login page is displayed now

This commit is contained in:
or-else
2018-10-25 14:02:53 +03:00
parent 358f9eb970
commit e139c1371e
15 changed files with 251 additions and 164 deletions
+3 -1
View File
@@ -1,5 +1,7 @@
import { PACKAGE_VERSION } from './version.js';
// Name of this application, used in the User-Agent.
export const APP_NAME = 'TinodeWeb/' + (package_version || '0.15');
export const APP_NAME = 'TinodeWeb/' + (PACKAGE_VERSION || '0.15');
// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own
export const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K';
+9
View File
@@ -0,0 +1,9 @@
// Get these values for your project from the https://console.firebase.google.com/
export const FIREBASE_INIT = {
// Project Settings -> Cloud Messaging, "Sender ID".
messagingSenderId: "114126160546",
// Project Settings -> Cloud Messaging -> Web configuration -> Web Push certificates.
// This value IS NOT included in the generated js config you can download from
// the firebase console. It needs to be added separately.
messagingVapidKey: "BOgQVPOMzIMXUpsYGpbVkZoEBc0ifKY_f2kSU5DNDGYI6i6CoKqqxDd7w7PJ3FaGRBgVGJffldETumOx831jl58"
};
+6 -1
View File
@@ -3,9 +3,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { IntlProvider } from 'react-intl';
import TinodeWeb from './views/tinode-web.jsx';
ReactDOM.render(
React.createElement(TinodeWeb, null),
<IntlProvider
locale={navigator.language}>
<TinodeWeb />
</IntlProvider>,
document.getElementById('mountPoint')
);
+4 -4
View File
@@ -1,7 +1,7 @@
import {KNOWN_HOSTS, DEFAULT_HOST} from '../config.js';
import { KNOWN_HOSTS, DEFAULT_HOST } from '../config.js';
// Detect server address from the URL
function detectServerAddress() {
export function detectServerAddress() {
let host = DEFAULT_HOST;
if (typeof window.location == 'object') {
if (window.location.protocol == 'file:' || window.location.hostname == 'localhost') {
@@ -14,14 +14,14 @@ function detectServerAddress() {
}
// Detect if the page is served over HTTPS.
function isSecureConnection() {
export function isSecureConnection() {
if (typeof window.location == 'object') {
return window.location.protocol == 'https:';
}
return false;
}
function isLocalHost() {
export function isLocalHost() {
if (typeof window.location == 'object') {
return window.location.hostname == 'localhost';
}
+21
View File
@@ -0,0 +1,21 @@
// Helper functions for storing values in localStorage.
// By default localStorage can store only strings, not objects or other types.
export default class LocalStorageUtil {
// Replace old object with the new one.
static setObject(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
// Get stored object.
static getObject(key) {
let value = localStorage.getItem(key);
return value && JSON.parse(value);
}
// Partially or wholly update stored object.
static updateObject(key, value) {
let oldVal = localStorage.getObject(key);
localStorage.setObject(key, Object.assign(oldVal || {}, value));
}
}
+57 -56
View File
@@ -1,67 +1,68 @@
// Helper functions for hash navigation.
// Utility class for hash navigation.
// Parse hash as in http://www.example.com/path#hash as if it were
// path and arguments.
export function parseUrlHash(hash) {
// Split path from args, path -> parts[0], args->path[1]
let parts = hash.split('?', 2);
let params = {};
let path = [];
if (parts[0]) {
path = parts[0].substr(1).split('/');
}
if (parts[1]) {
parts[1].split('&').forEach(function(part) {
let item = part.split('=');
if (item[0]) {
params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]);
}
});
}
return {path: path, params: params};
}
export function navigateTo(url) {
window.location.hash = url;
}
export function composeUrlHash(path, params) {
var url = path.join('/');
var args = [];
for (var key in params) {
if (params.hasOwnProperty(key)) {
args.push(key + '=' + params[key]);
export default class HashNavigation {
static parseUrlHash(hash) {
// Split path from args, path -> parts[0], args->path[1]
let parts = hash.split('?', 2);
let params = {};
let path = [];
if (parts[0]) {
path = parts[0].substr(1).split('/');
}
if (parts[1]) {
parts[1].split('&').forEach(function(part) {
let item = part.split('=');
if (item[0]) {
params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]);
}
});
}
return {path: path, params: params};
}
if (args.length > 0) {
url += '?' + args.join('&');
static navigateTo(url) {
window.location.hash = url;
}
return url;
}
export function addUrlParam(hash, key, value) {
var parsed = parseUrlHash(hash);
parsed.params[key] = value;
return composeUrlHash(parsed.path, parsed.params);
}
static composeUrlHash(path, params) {
var url = path.join('/');
var args = [];
for (var key in params) {
if (params.hasOwnProperty(key)) {
args.push(key + '=' + params[key]);
}
}
if (args.length > 0) {
url += '?' + args.join('&');
}
return url;
}
export function removeUrlParam(hash, key) {
var parsed = parseUrlHash(hash);
delete parsed.params[key];
return composeUrlHash(parsed.path, parsed.params);
}
static addUrlParam(hash, key, value) {
var parsed = parseUrlHash(hash);
parsed.params[key] = value;
return composeUrlHash(parsed.path, parsed.params);
}
export function setUrlSidePanel(hash, sidepanel) {
var parsed = parseUrlHash(hash);
parsed.path[0] = sidepanel;
return composeUrlHash(parsed.path, parsed.params);
}
static removeUrlParam(hash, key) {
var parsed = parseUrlHash(hash);
delete parsed.params[key];
return composeUrlHash(parsed.path, parsed.params);
}
export function setUrlTopic(hash, topic) {
var parsed = parseUrlHash(hash);
parsed.path[1] = topic;
// Close InfoView on topic change.
delete parsed.params.info;
return composeUrlHash(parsed.path, parsed.params);
static setUrlSidePanel(hash, sidepanel) {
var parsed = parseUrlHash(hash);
parsed.path[0] = sidepanel;
return composeUrlHash(parsed.path, parsed.params);
}
static setUrlTopic(hash, topic) {
var parsed = parseUrlHash(hash);
parsed.path[1] = topic;
// Close InfoView on topic change.
delete parsed.params.info;
return composeUrlHash(parsed.path, parsed.params);
}
}
// END of hash navigation helper functions
+3 -3
View File
@@ -1,5 +1,5 @@
// Short representation of time in the past.
function shortDateFormat(then) {
export function shortDateFormat(then) {
var locale = window.navigator.userLanguage || window.navigator.language;
var now = new Date();
if (then.getFullYear() == now.getFullYear()) {
@@ -15,7 +15,7 @@ function shortDateFormat(then) {
}
// Convert a number of bytes to human-readable format.
function bytesToHumanSize(bytes) {
export function bytesToHumanSize(bytes) {
if (!bytes || bytes == 0) {
return '0 Bytes';
}
@@ -29,7 +29,7 @@ function bytesToHumanSize(bytes) {
// Get 32 bit integer hash value for a string. Ideally it should produce the same value
// as Java's String#hash().
function stringHash(value) {
export function stringHash(value) {
var hash = 0;
value = '' + value;
for (var i = 0; i < value.length; i++) {
+5 -3
View File
@@ -1,11 +1,13 @@
// Login form
import React from 'react';
import {FormattedMessage} from 'react-intl';
import { FormattedMessage } from 'react-intl';
import CheckBox from '../widgets/checkbox.jsx';
import VisiblePassword from '../widgets/visible-password.jsx';
import LocalStorageUtil from '../lib/local-storage.js';
export default class LoginView extends React.Component {
constructor(props) {
super(props);
@@ -14,7 +16,7 @@ export default class LoginView extends React.Component {
login: props.login,
password: '',
hostName: props.serverAddress,
saveToken: localStorage.getObject('keep-logged-in')
saveToken: LocalStorageUtil.getObject('keep-logged-in')
};
this.handleLoginChange = this.handleLoginChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
@@ -31,7 +33,7 @@ export default class LoginView extends React.Component {
}
handleToggleSaveToken() {
localStorage.setObject('keep-logged-in', !this.state.saveToken);
LocalStorageUtil.setObject('keep-logged-in', !this.state.saveToken);
this.setState({saveToken: !this.state.saveToken});
}
+4 -1
View File
@@ -1,7 +1,10 @@
/* This is just a static page to display when no conversation is selected. */
import React from 'react';
import Tinode from 'tinode-sdk';
import { APP_NAME } from '../config.js';
export default class LogoView extends React.PureComponent {
render() {
var version = APP_NAME + ' (' + Tinode.getLibrary() + ')';
-1
View File
@@ -1,5 +1,4 @@
/* A password reset form */
import React from 'react';
import VisiblePassword from '../widgets/visible-password.jsx';
+54 -44
View File
@@ -3,7 +3,9 @@ import React from 'react';
import 'firebase/app';
import 'firebase/messaging';
import { FIREBASE_INIT } from '../../firebase-init.js'
import { FIREBASE_INIT } from '../firebase-init.js'
import Tinode from 'tinode-sdk';
import ContextMenu from '../widgets/context-menu.jsx';
@@ -11,6 +13,11 @@ import InfoView from './info-view.jsx';
import MessagesView from './messages-view.jsx';
import SidepanelView from './sidepanel-view.jsx';
import { API_KEY, APP_NAME, MEDIA_BREAKPOINT, RECEIVED_DELAY } from '../config.js';
import { isLocalHost, isSecureConnection } from '../lib/host-name.js';
import LocalStorageUtil from '../lib/local-storage.js';
import HashNavigation from '../lib/navigation.js';
// Sound to play on message received.
export const POP_SOUND = new Audio('audio/msg.mp3');
@@ -76,7 +83,7 @@ export default class TinodeWeb extends React.Component {
}
getBlankState() {
let settings = localStorage.getObject("settings") || {};
let settings = LocalStorageUtil.getObject("settings") || {};
return {
connected: false,
@@ -89,7 +96,7 @@ export default class TinodeWeb extends React.Component {
desktopAlertsEnabled: (isSecureConnection() || isLocalHost()) &&
(typeof firebase != 'undefined') && (typeof navigator != 'undefined') &&
(typeof FIREBASE_INIT != 'undefined'),
firebaseToken: localStorage.getObject("firebase-token"),
firebaseToken: LocalStorageUtil.getObject("firebase-token"),
errorText: '',
errorLevel: null,
@@ -169,10 +176,10 @@ export default class TinodeWeb extends React.Component {
}
}
let token = localStorage.getObject('keep-logged-in') ?
localStorage.getObject('auth-token') : undefined;
let token = LocalStorageUtil.getObject('keep-logged-in') ?
LocalStorageUtil.getObject('auth-token') : undefined;
let parsedNav = parseUrlHash(window.location.hash);
let parsedNav = HashNavigation.parseUrlHash(window.location.hash);
if (token) {
// When reading from storage, date is returned as string.
token.expires = new Date(token.expires);
@@ -184,9 +191,9 @@ export default class TinodeWeb extends React.Component {
delete parsedNav.params.info;
delete parsedNav.params.tab;
parsedNav.path[0] = '';
navigateTo(composeUrlHash(parsedNav.path, parsedNav.params));
HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsedNav.path, parsedNav.params));
} else if (!parsedNav.params.token) {
navigateTo('');
HashNavigation.navigateTo('');
}
this.readTimer = null;
@@ -218,7 +225,7 @@ export default class TinodeWeb extends React.Component {
// Handle for hashchange event: display appropriate panels.
handleHashRoute() {
var hash = parseUrlHash(window.location.hash);
var hash = HashNavigation.parseUrlHash(window.location.hash);
if (hash.path && hash.path.length > 0) {
// Left-side panel selector.
if (['register','settings','edit','cred','reset','newtpk','contacts',''].includes(hash.path[0])) {
@@ -315,7 +322,7 @@ export default class TinodeWeb extends React.Component {
doLogin(login, password, cred) {
if (this.tinode.isAuthenticated()) {
// Already logged in. Go to default screen.
navigateTo("");
HashNavigation.navigateTo('');
return;
}
// Sanitize and package credentail.
@@ -344,30 +351,30 @@ export default class TinodeWeb extends React.Component {
// Login failed, report error.
this.setState({loginDisabled: false, credMethod: undefined, credCode: undefined});
this.handleError(err.message, 'err');
localStorage.removeItem('auth-token');
navigateTo('');
LocalStorageUtil.removeItem('auth-token');
HashNavigation.navigateTo('');
});
} else {
// No login credentials provided.
// Make sure we are on the login page.
navigateTo('');
HashNavigation.navigateTo('');
this.setState({loginDisabled: false});
}
}
handleCredentialsRequest(params) {
var parsed = parseUrlHash(window.location.hash);
var parsed = HashNavigation.parseUrlHash(window.location.hash);
parsed.path[0] = 'cred';
parsed.params['method'] = params.cred[0];
navigateTo(composeUrlHash(parsed.path, parsed.params));
HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));
}
handleLoginSuccessful() {
this.handleError('', null);
// Refresh authentication token.
if (localStorage.getObject('keep-logged-in')) {
localStorage.setObject('auth-token', this.tinode.getAuthToken());
if (LocalStorageUtil.getObject('keep-logged-in')) {
LocalStorageUtil.setObject('auth-token', this.tinode.getAuthToken());
}
// Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.
var me = this.tinode.getMeTopic();
@@ -387,11 +394,11 @@ export default class TinodeWeb extends React.Component {
withDesc().
build()
).catch((err) => {
localStorage.removeItem('auth-token');
LocalStorageUtil.removeItem('auth-token');
this.handleError(err.message, 'err');
navigateTo('');
HashNavigation.navigateTo('');
});
navigateTo(setUrlSidePanel(window.location.hash, 'contacts'));
HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'contacts'));
}
handleDisconnect(err) {
@@ -575,7 +582,7 @@ export default class TinodeWeb extends React.Component {
topicSelectedOnline: online,
topicSelectedAcs: acs
});
navigateTo(setUrlTopic('', topicName));
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', topicName));
}
} else {
// Currently selected contact deleted
@@ -588,7 +595,7 @@ export default class TinodeWeb extends React.Component {
showInfoPanel: false
});
if (this.state.topicSelected) {
navigateTo(setUrlTopic('', null));
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', null));
}
}
}
@@ -598,7 +605,7 @@ export default class TinodeWeb extends React.Component {
this.setState({
mobilePanel: 'sidepanel'
});
navigateTo(setUrlTopic(window.location.hash, null));
HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, null));
}
// User is sending a message, either plain text or a drafty object, possibly
@@ -631,7 +638,7 @@ export default class TinodeWeb extends React.Component {
// User chose a Sign Up menu item.
handleNewAccount() {
navigateTo(setUrlSidePanel(window.location.hash, 'register'));
HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'register'));
}
// Actual registration of a new account.
@@ -673,7 +680,8 @@ export default class TinodeWeb extends React.Component {
// User chose Settings menu item.
handleSettings() {
navigateTo(setUrlSidePanel(window.location.hash, this.state.myUserId ? 'edit' : 'settings'));
HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,
this.state.myUserId ? 'edit' : 'settings'));
}
// User updated global parameters.
@@ -692,12 +700,12 @@ export default class TinodeWeb extends React.Component {
serverAddress: serverAddress,
transport: transport
});
localStorage.setObject('settings', {
LocalStorageUtil.setObject('settings', {
serverAddress: serverAddress,
transport: transport
});
navigateTo(setUrlSidePanel(window.location.hash, ''));
HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, ''));
}
// Initialize desktop alerts = push notifications.
@@ -722,24 +730,24 @@ export default class TinodeWeb extends React.Component {
}).catch((err) => {
this.handleError(err.message, 'err');
this.setState({desktopAlerts: false, firebaseToken: null});
localStorage.updateObject('settings', {desktopAlerts: false});
LocalStorageUtil.updateObject('settings', {desktopAlerts: false});
console.log("Failed to get permission to notify.", err);
});
} else {
this.setState({desktopAlerts: true});
localStorage.updateObject('settings', {desktopAlerts: true});
LocalStorageUtil.updateObject('settings', {desktopAlerts: true});
}
} else if (this.state.firebaseToken) {
this.fbPush.deleteToken(this.state.firebaseToken).catch((err) => {
console.log("Unable to delete token.", err);
}).finally(() => {
localStorage.updateObject('settings', {desktopAlerts: false});
localStorage.removeItem('firebase-token');
LocalStorageUtil.updateObject('settings', {desktopAlerts: false});
LocalStorageUtil.removeItem('firebase-token');
this.setState({desktopAlerts: false, firebaseToken: null});
});
} else {
this.setState({desktopAlerts: false, firebaseToken: null});
localStorage.updateObject('settings', {desktopAlerts: false});
LocalStorageUtil.updateObject('settings', {desktopAlerts: false});
}
}
@@ -747,10 +755,10 @@ export default class TinodeWeb extends React.Component {
this.fbPush.getToken().then((refreshedToken) => {
if (refreshedToken != this.state.firebaseToken) {
this.tinode.setDeviceToken(refreshedToken, sendToServer);
localStorage.setObject('firebase-token', refreshedToken);
LocalStorageUtil.setObject('firebase-token', refreshedToken);
}
this.setState({firebaseToken: refreshedToken, desktopAlerts: true});
localStorage.updateObject('settings', {desktopAlerts: true});
LocalStorageUtil.updateObject('settings', {desktopAlerts: true});
}).catch((err) => {
this.handleError(err.message, 'err');
console.log("Failed to retrieve firebase token", err);
@@ -759,27 +767,27 @@ export default class TinodeWeb extends React.Component {
handleToggleMessageSounds(enabled) {
this.setState({messageSounds: enabled});
localStorage.updateObject('settings', {
LocalStorageUtil.updateObject('settings', {
messageSoundsOff: !enabled
});
}
// User clicked Cancel button in Setting or Sign Up panel.
handleSidepanelCancel() {
var parsed = parseUrlHash(window.location.hash);
var parsed = HashNavigation.parseUrlHash(window.location.hash);
parsed.path[0] = this.state.myUserId ? 'contacts' : '';
if (parsed.params) {
delete parsed.params.code;
delete parsed.params.method;
delete parsed.params.tab;
}
navigateTo(composeUrlHash(parsed.path, parsed.params));
HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));
this.setState({errorText: '', errorLevel: null});
}
// User clicked a (+) menu item.
handleNewTopic() {
navigateTo(setUrlSidePanel(window.location.hash, 'newtpk'));
HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'newtpk'));
}
// Request to start a new topic. New P2P topic requires peer's name.
@@ -801,7 +809,9 @@ export default class TinodeWeb extends React.Component {
// If the current URl contains the old topic name, replace it with new.
// Update the name of the selected topic first so the navigator doen't clear
// the state.
this.setState({topicSelected: newName}, () => { navigateTo(setUrlTopic('', newName)); });
this.setState({topicSelected: newName}, () => {
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', newName));
});
}
}
@@ -853,7 +863,7 @@ export default class TinodeWeb extends React.Component {
handleLogout() {
updateFavicon(0);
localStorage.removeItem('auth-token');
LocalStorageUtil.removeItem('auth-token');
if (this.tinode) {
this.tinode.onDisconnect = undefined;
this.tinode.disconnect();
@@ -862,7 +872,7 @@ export default class TinodeWeb extends React.Component {
this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport);
this.tinode.onConnect = this.handleConnected;
this.tinode.onDisconnect = this.handleDisconnect;
navigateTo('');
HashNavigation.navigateTo('');
}
handleLeaveUnsubRequest(topicName) {
@@ -873,7 +883,7 @@ export default class TinodeWeb extends React.Component {
topic.leave(true).then((ctrl) => {
// Hide MessagesView and InfoView panels.
navigateTo(setUrlTopic(window.location.hash, ''));
HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));
}).catch((err) => {
this.handleError(err.message, 'err');
});
@@ -926,12 +936,12 @@ export default class TinodeWeb extends React.Component {
}
handleShowInfoView() {
navigateTo(addUrlParam(window.location.hash, 'info', true));
HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'info', true));
this.setState({showInfoPanel: true});
}
handleHideInfoView() {
navigateTo(removeUrlParam(window.location.hash, 'info'));
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'info'));
this.setState({showInfoPanel: false});
}