Skip to content

Commit

Permalink
[RN] If base/config knows a domain, then the app knows it
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
lyubomir committed May 14, 2018
1 parent 631f51d commit 75fe3e3
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 72 deletions.
132 changes: 60 additions & 72 deletions react/features/app/actions.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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.
*
Expand All @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -246,45 +215,64 @@ 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;
},
/* onRejected */ error => {
// 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();
}
};
}
45 changes: 45 additions & 0 deletions react/features/base/config/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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;
};
}
8 changes: 8 additions & 0 deletions react/features/base/config/constants.js
Original file line number Diff line number Diff line change
@@ -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';
34 changes: 34 additions & 0 deletions react/features/base/config/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import _ from 'lodash';

import { _CONFIG_STORE_PREFIX } from './constants';
import parseURLParams from './parseURLParams';

declare var $: Object;
Expand Down Expand Up @@ -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 */

/**
Expand Down
60 changes: 60 additions & 0 deletions react/features/base/config/middleware.js
Original file line number Diff line number Diff line change
@@ -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}.
Expand All @@ -13,13 +17,69 @@ 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);
}

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.
Expand Down

0 comments on commit 75fe3e3

Please sign in to comment.