[RN] If base/config knows a domain, then the app knows it

Knowledge is power, man!

The config.js cache predates the feature base/known-domains.
Technically, it's also able to recall more domains that the feature
recent-list can (because the latter limits its entries).
This commit is contained in:
Lyubo Marinov 2018-05-14 15:49:00 -05:00
parent 631f51d627
commit 75fe3e3b9d
5 changed files with 207 additions and 72 deletions

View File

@ -1,7 +1,13 @@
/* @flow */
import { setRoom } from '../base/conference';
import { configWillLoad, loadConfigError, setConfig } from '../base/config';
import {
configWillLoad,
loadConfigError,
restoreConfig,
setConfig,
storeConfig
} from '../base/config';
import { setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet';
import { parseURIString } from '../base/util';
@ -24,48 +30,6 @@ export function appNavigate(uri: ?string) {
_appNavigateToOptionalLocation(dispatch, getState, parseURIString(uri));
}
/**
* Redirects to another page generated by replacing the path in the original URL
* with the given path.
*
* @param {(string)} pathname - The path to navigate to.
* @returns {Function}
*/
export function redirectWithStoredParams(pathname: string) {
return (dispatch: Dispatch<*>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
const newLocationURL = new URL(locationURL.href);
newLocationURL.pathname = pathname;
window.location.assign(newLocationURL.toString());
};
}
/**
* Reloads the page by restoring the original URL.
*
* @returns {Function}
*/
export function reloadWithStoredParams() {
return (dispatch: Dispatch<*>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
const windowLocation = window.location;
const oldSearchString = windowLocation.search;
windowLocation.replace(locationURL.toString());
if (window.self !== window.top
&& locationURL.search === oldSearchString) {
// NOTE: Assuming that only the hash or search part of the URL will
// be changed!
// location.reload will not trigger redirect/reload for iframe when
// only the hash params are changed. That's why we need to call
// reload in addition to replace.
windowLocation.reload();
}
};
}
/**
* Triggers an in-app navigation to a specific location URI.
*
@ -89,7 +53,7 @@ function _appNavigateToMandatoryLocation(
dispatch(configWillLoad(newLocation));
return (
_loadConfig(newLocation)
_loadConfig(dispatch, getState, newLocation)
.then(
config => loadConfigSettled(/* error */ undefined, config),
error => loadConfigSettled(error, /* config */ undefined))
@ -214,12 +178,17 @@ export function appWillUnmount(app: Object) {
/**
* Loads config.js from a specific host.
*
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
* @param {Function} getState - The redux {@code getState} function.
* @param {Object} location - The location URI which specifies the host to load
* the config.js from.
* @private
* @returns {Promise<Object>}
*/
function _loadConfig({ contextRoot, host, protocol, room }) {
function _loadConfig(
dispatch: Dispatch<*>,
getState: Function,
{ contextRoot, host, protocol, room }) {
// XXX As the mobile/React Native app does not employ config on the
// WelcomePage, do not download config.js from the deployment when
// navigating to the WelcomePage - the perceived/visible navigation will be
@ -246,21 +215,9 @@ function _loadConfig({ contextRoot, host, protocol, room }) {
/* eslint-enable no-param-reassign */
const key = `config.js/${baseURL}`;
return loadConfig(url).then(
/* onFulfilled */ config => {
// Try to store the configuration in localStorage. If the deployment
// specified 'getroom' as a function, for example, it does not make
// sense to and it will not be stored.
try {
if (typeof window.config === 'undefined'
|| window.config !== config) {
window.localStorage.setItem(key, JSON.stringify(config));
}
} catch (e) {
// Ignore the error because the caching is optional.
}
dispatch(storeConfig(baseURL, config));
return config;
},
@ -268,23 +225,54 @@ function _loadConfig({ contextRoot, host, protocol, room }) {
// XXX The (down)loading of config failed. Try to use the last
// successfully fetched for that deployment. It may not match the
// shard.
let storage;
const config = restoreConfig(baseURL);
try {
// XXX Even reading the property localStorage of window may
// throw an error (which is user agent-specific behavior).
storage = window.localStorage;
const config = storage.getItem(key);
if (config) {
return JSON.parse(config);
}
} catch (e) {
// Somehow incorrect data ended up in the storage. Clean it up.
storage && storage.removeItem(key);
if (config) {
return config;
}
throw error;
});
}
/**
* Redirects to another page generated by replacing the path in the original URL
* with the given path.
*
* @param {(string)} pathname - The path to navigate to.
* @returns {Function}
*/
export function redirectWithStoredParams(pathname: string) {
return (dispatch: Dispatch<*>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
const newLocationURL = new URL(locationURL.href);
newLocationURL.pathname = pathname;
window.location.assign(newLocationURL.toString());
};
}
/**
* Reloads the page by restoring the original URL.
*
* @returns {Function}
*/
export function reloadWithStoredParams() {
return (dispatch: Dispatch<*>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
const windowLocation = window.location;
const oldSearchString = windowLocation.search;
windowLocation.replace(locationURL.toString());
if (window.self !== window.top
&& locationURL.search === oldSearchString) {
// NOTE: Assuming that only the hash or search part of the URL will
// be changed!
// location.reload will not trigger redirect/reload for iframe when
// only the hash params are changed. That's why we need to call
// reload in addition to replace.
windowLocation.reload();
}
};
}

View File

@ -2,11 +2,15 @@
import type { Dispatch } from 'redux';
import { addKnownDomains } from '../known-domains';
import { parseURIString } from '../util';
import {
CONFIG_WILL_LOAD,
LOAD_CONFIG_ERROR,
SET_CONFIG
} from './actionTypes';
import { _CONFIG_STORE_PREFIX } from './constants';
import { setConfigFromURLParams } from './functions';
/**
@ -87,3 +91,44 @@ export function setConfig(config: Object = {}) {
});
};
}
/**
* Stores a specific Jitsi Meet config.js object into {@code localStorage}.
*
* @param {string} baseURL - The base URL from which the config.js was
* downloaded.
* @param {Object} config - The Jitsi Meet config.js to store.
* @returns {Function}
*/
export function storeConfig(baseURL: string, config: Object) {
return (dispatch: Dispatch<*>) => {
// Try to store the configuration in localStorage. If the deployment
// specified 'getroom' as a function, for example, it does not make
// sense to and it will not be stored.
let b = false;
try {
if (typeof window.config === 'undefined'
|| window.config !== config) {
window.localStorage.setItem(
`${_CONFIG_STORE_PREFIX}/${baseURL}`,
JSON.stringify(config));
b = true;
}
} catch (e) {
// Ignore the error because the caching is optional.
}
// If base/config knows a domain, then the app knows it.
if (b) {
try {
dispatch(addKnownDomains(parseURIString(baseURL).host));
} catch (e) {
// Ignore the error because the fiddling with "known domains" is
// a side effect here.
}
}
return b;
};
}

View File

@ -0,0 +1,8 @@
/**
* The prefix of the {@code localStorage} key into which {@link storeConfig}
* stores and from which {@link restoreConfig} restores.
*
* @protected
* @type string
*/
export const _CONFIG_STORE_PREFIX = 'config.js';

View File

@ -2,6 +2,7 @@
import _ from 'lodash';
import { _CONFIG_STORE_PREFIX } from './constants';
import parseURLParams from './parseURLParams';
declare var $: Object;
@ -238,6 +239,39 @@ function _getWhitelistedJSON(configName, configJSON) {
return _.pick(configJSON, WHITELISTED_KEYS);
}
/**
* Restores a Jitsi Meet config.js from {@code localStorage} if it was
* previously downloaded from a specific {@code baseURL} and stored with
* {@link storeConfig}.
*
* @param {string} baseURL - The base URL from which the config.js was
* previously downloaded and stored with {@code storeConfig}.
* @returns {?Object} The Jitsi Meet config.js which was previously downloaded
* from {@code baseURL} and stored with {@code storeConfig} if it was restored;
* otherwise, {@code undefined}.
*/
export function restoreConfig(baseURL: string): ?Object {
let storage;
const key = `${_CONFIG_STORE_PREFIX}/${baseURL}`;
try {
// XXX Even reading the property localStorage of window may throw an
// error (which is user agent-specific behavior).
storage = window.localStorage;
const config = storage.getItem(key);
if (config) {
return JSON.parse(config) || undefined;
}
} catch (e) {
// Somehow incorrect data ended up in the storage. Clean it up.
storage && storage.removeItem(key);
}
return undefined;
}
/* eslint-disable max-params */
/**

View File

@ -1,8 +1,12 @@
// @flow
import { APP_WILL_MOUNT } from '../../app';
import { addKnownDomains } from '../known-domains';
import { MiddlewareRegistry } from '../redux';
import { parseURIString } from '../util';
import { SET_CONFIG } from './actionTypes';
import { _CONFIG_STORE_PREFIX } from './constants';
/**
* The middleware of the feature {@code base/config}.
@ -13,6 +17,9 @@ import { SET_CONFIG } from './actionTypes';
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case APP_WILL_MOUNT:
return _appWillMount(store, next, action);
case SET_CONFIG:
return _setConfig(store, next, action);
}
@ -20,6 +27,59 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
/**
* Notifies the feature {@code base/config} that the {@link APP_WILL_MOUNT}
* redux action is being {@code dispatch}ed in a specific redux store.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action which is being {@code dispatch}ed
* in the specified {@code store}.
* @private
* @returns {*} The return value of {@code next(action)}.
*/
function _appWillMount(store, next, action) {
const result = next(action);
// It's an opportune time to transfer the feature base/config's knowledge
// about "known domains" (which is local to the feature) to the feature
// base/known-domains (which is global to the app).
//
// XXX Since the feature base/config predates the feature calendar-sync and,
// consequently, the feature known-domains, it's possible for the feature
// base/config to know of domains which the feature known-domains is yet to
// discover.
const { localStorage } = window;
if (localStorage) {
const prefix = `${_CONFIG_STORE_PREFIX}/`;
const knownDomains = [];
for (let i = 0; /* localStorage.key(i) */; ++i) {
const key = localStorage.key(i);
if (key) {
let baseURL;
if (key.startsWith(prefix)
&& (baseURL = key.substring(prefix.length))) {
const uri = parseURIString(baseURL);
let host;
uri && (host = uri.host) && knownDomains.push(host);
}
} else {
break;
}
}
knownDomains.length && store.dispatch(addKnownDomains(knownDomains));
}
return result;
}
/**
* Notifies the feature {@code base/config} that the {@link SET_CONFIG} redux
* action is being {@code dispatch}ed in a specific redux store.