Merge pull request #2511 from matrix-org/jryans/rm-team-server

Remove support for team servers
This commit is contained in:
J. Ryan Stinnett 2019-01-25 16:48:24 -06:00 committed by GitHub
commit a07ba49641
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 41 additions and 655 deletions

View file

@ -27,7 +27,6 @@ import UserActivity from './UserActivity';
import Presence from './Presence';
import dis from './dispatcher';
import DMRoomMap from './utils/DMRoomMap';
import RtsClient from './RtsClient';
import Modal from './Modal';
import sdk from './index';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
@ -224,7 +223,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
//
// The plan is to gradually move the localStorage access done here into
// SessionStore to avoid bugs where the view becomes out-of-sync with
// localStorage (e.g. teamToken, isGuest etc.)
// localStorage (e.g. isGuest etc.)
async function _restoreFromLocalStorage() {
if (!localStorage) {
return false;
@ -286,15 +285,6 @@ function _handleLoadSessionFailure(e) {
});
}
let rtsClient = null;
export function initRtsClient(url) {
if (url) {
rtsClient = new RtsClient(url);
} else {
rtsClient = null;
}
}
/**
* Transitions to a logged-in state using the given credentials.
*
@ -333,7 +323,7 @@ async function _doSetLoggedIn(credentials, clearStorage) {
);
// This is dispatched to indicate that the user is still in the process of logging in
// because `teamPromise` may take some time to resolve, breaking the assumption that
// because async code may take some time to resolve, breaking the assumption that
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
// later than MatrixChat might assume.
//
@ -347,10 +337,6 @@ async function _doSetLoggedIn(credentials, clearStorage) {
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl, credentials.identityServerUrl);
// Resolves by default
let teamPromise = Promise.resolve(null);
if (localStorage) {
try {
_persistCredentialsToLocalStorage(credentials);
@ -367,27 +353,13 @@ async function _doSetLoggedIn(credentials, clearStorage) {
} catch (e) {
console.warn("Error using local storage: can't persist session!", e);
}
if (rtsClient && !credentials.guest) {
teamPromise = rtsClient.login(credentials.userId).then((body) => {
if (body.team_token) {
localStorage.setItem("mx_team_token", body.team_token);
}
return body.team_token;
}, (err) => {
console.warn(`Failed to get team token on login: ${err}` );
return null;
});
}
} else {
console.warn("No local storage available: can't persist session!");
}
MatrixClientPeg.replaceUsingCreds(credentials);
teamPromise.then((teamToken) => {
dis.dispatch({action: 'on_logged_in', teamToken: teamToken});
});
dis.dispatch({ action: 'on_logged_in' });
await startMatrixClient();
return MatrixClientPeg.get();

View file

@ -1,104 +0,0 @@
import 'whatwg-fetch';
let fetchFunction = fetch;
function checkStatus(response) {
if (!response.ok) {
return response.text().then((text) => {
throw new Error(text);
});
}
return response;
}
function parseJson(response) {
return response.json();
}
function encodeQueryParams(params) {
return '?' + Object.keys(params).map((k) => {
return k + '=' + encodeURIComponent(params[k]);
}).join('&');
}
const request = (url, opts) => {
if (opts && opts.qs) {
url += encodeQueryParams(opts.qs);
delete opts.qs;
}
if (opts && opts.body) {
if (!opts.headers) {
opts.headers = {};
}
opts.body = JSON.stringify(opts.body);
opts.headers['Content-Type'] = 'application/json';
}
return fetchFunction(url, opts)
.then(checkStatus)
.then(parseJson);
};
export default class RtsClient {
constructor(url) {
this._url = url;
}
getTeamsConfig() {
return request(this._url + '/teams');
}
/**
* Track a referral with the Riot Team Server. This should be called once a referred
* user has been successfully registered.
* @param {string} referrer the user ID of one who referred the user to Riot.
* @param {string} sid the sign-up identity server session ID .
* @param {string} clientSecret the sign-up client secret.
* @returns {Promise} a promise that resolves to { team_token: 'sometoken' } upon
* success.
*/
trackReferral(referrer, sid, clientSecret) {
return request(this._url + '/register',
{
body: {
referrer: referrer,
session_id: sid,
client_secret: clientSecret,
},
method: 'POST',
},
);
}
getTeam(teamToken) {
return request(this._url + '/teamConfiguration',
{
qs: {
team_token: teamToken,
},
},
);
}
/**
* Signal to the RTS that a login has occurred and that a user requires their team's
* token.
* @param {string} userId the user ID of the user who is a member of a team.
* @returns {Promise} a promise that resolves to { team_token: 'sometoken' } upon
* success.
*/
login(userId) {
return request(this._url + '/login',
{
qs: {
user_id: userId,
},
},
);
}
// allow fetch to be replaced, for testing.
static setFetch(fn) {
fetchFunction = fn;
}
}

View file

@ -30,11 +30,6 @@ class HomePage extends React.Component {
static displayName = 'HomePage';
static propTypes = {
// URL base of the team server. Optional.
teamServerUrl: PropTypes.string,
// Team token. Optional. If set, used to get the static homepage of the team
// associated. If unset, homePageUrl will be used.
teamToken: PropTypes.string,
// URL to use as the iFrame src. Defaults to /home.html.
homePageUrl: PropTypes.string,
};
@ -56,35 +51,29 @@ class HomePage extends React.Component {
componentWillMount() {
this._unmounted = false;
if (this.props.teamToken && this.props.teamServerUrl) {
this.setState({
iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`,
});
} else {
// we use request() to inline the homepage into the react component
// so that it can inherit CSS and theming easily rather than mess around
// with iframes and trying to synchronise document.stylesheets.
// we use request() to inline the homepage into the react component
// so that it can inherit CSS and theming easily rather than mess around
// with iframes and trying to synchronise document.stylesheets.
const src = this.props.homePageUrl || 'home.html';
const src = this.props.homePageUrl || 'home.html';
request(
{ method: "GET", url: src },
(err, response, body) => {
if (this._unmounted) {
return;
}
request(
{ method: "GET", url: src },
(err, response, body) => {
if (this._unmounted) {
return;
}
if (err || response.status < 200 || response.status >= 300) {
console.warn(`Error loading home page: ${err}`);
this.setState({ page: _t("Couldn't load home page") });
return;
}
if (err || response.status < 200 || response.status >= 300) {
console.warn(`Error loading home page: ${err}`);
this.setState({ page: _t("Couldn't load home page") });
return;
}
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
this.setState({ page: body });
},
);
}
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
this.setState({ page: body });
},
);
}
componentWillUnmount() {

View file

@ -63,7 +63,6 @@ const LoggedInView = React.createClass({
// transitioned to PWLU)
onRegistered: PropTypes.func,
collapsedRhs: PropTypes.bool,
teamToken: PropTypes.string,
// Used by the RoomView to handle joining rooms
viaServers: PropTypes.arrayOf(PropTypes.string),
@ -457,8 +456,6 @@ const LoggedInView = React.createClass({
pageElement = <UserSettings
onClose={this.props.onCloseAllSettings}
brand={this.props.config.brand}
referralBaseUrl={this.props.config.referralBaseUrl}
teamToken={this.props.teamToken}
/>;
break;
@ -475,15 +472,7 @@ const LoggedInView = React.createClass({
case PageTypes.HomePage:
{
// If team server config is present, pass the teamServerURL. props.teamToken
// must also be set for the team page to be displayed, otherwise the
// welcomePageUrl is used (which might be undefined).
const teamServerUrl = this.props.config.teamServerConfig ?
this.props.config.teamServerConfig.teamServerURL : null;
pageElement = <HomePage
teamServerUrl={teamServerUrl}
teamToken={this.props.teamToken}
homePageUrl={this.props.config.welcomePageUrl}
/>;
}

View file

@ -75,8 +75,8 @@ const VIEWS = {
// we have valid matrix credentials (either via an explicit login, via the
// initial re-animation/guest registration, or via a registration), and are
// now setting up a matrixclient to talk to it. This isn't an instant
// process because (a) we need to clear out indexeddb, and (b) we need to
// talk to the team server; while it is going on we show a big spinner.
// process because we need to clear out indexeddb. While it is going on we
// show a big spinner.
LOGGING_IN: 5,
// we are logged in with an active matrix client.
@ -256,42 +256,6 @@ export default React.createClass({
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
}
// To enable things like riot.im/geektime in a nicer way than rewriting the URL
// and appending a team token query parameter, use the first path segment to
// indicate a team, with "public" team tokens stored in the config teamTokenMap.
let routedTeamToken = null;
if (this.props.config.teamTokenMap) {
const teamName = window.location.pathname.split('/')[1];
if (teamName && this.props.config.teamTokenMap.hasOwnProperty(teamName)) {
routedTeamToken = this.props.config.teamTokenMap[teamName];
}
}
// Persist the team token across refreshes using sessionStorage. A new window or
// tab will not persist sessionStorage, but refreshes will.
if (this.props.startingFragmentQueryParams.team_token) {
window.sessionStorage.setItem(
'mx_team_token',
this.props.startingFragmentQueryParams.team_token,
);
}
// Use the locally-stored team token first, then as a fall-back, check to see if
// a referral link was used, which will contain a query parameter `team_token`.
this._teamToken = routedTeamToken ||
window.localStorage.getItem('mx_team_token') ||
window.sessionStorage.getItem('mx_team_token');
// Some users have ended up with "undefined" as their local storage team token,
// treat that as undefined.
if (this._teamToken === "undefined") {
this._teamToken = undefined;
}
if (this._teamToken) {
console.info(`Team token set to ${this._teamToken}`);
}
// Set up the default URLs (async)
if (this.getDefaultServerName() && !this.getDefaultHsUrl(false)) {
this.setState({loadingDefaultHomeserver: true});
@ -360,9 +324,6 @@ export default React.createClass({
linkifyMatrix.onGroupClick = this.onGroupClick;
}
const teamServerConfig = this.props.config.teamServerConfig || {};
Lifecycle.initRtsClient(teamServerConfig.teamServerURL);
// the first thing to do is to try the token params in the query-string
Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => {
if (loggedIn) {
@ -726,7 +687,7 @@ export default React.createClass({
});
break;
case 'on_logged_in':
this._onLoggedIn(payload.teamToken);
this._onLoggedIn();
break;
case 'on_logged_out':
this._onLoggedOut();
@ -1196,16 +1157,10 @@ export default React.createClass({
/**
* Called when a new logged in session has started
*
* @param {string} teamToken
*/
_onLoggedIn: async function(teamToken) {
_onLoggedIn: async function() {
this.setStateForNewView({view: VIEWS.LOGGED_IN});
if (teamToken) {
// A team member has logged in, not a guest
this._teamToken = teamToken;
dis.dispatch({action: 'view_home_page'});
} else if (this._is_registered) {
if (this._is_registered) {
this._is_registered = false;
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
@ -1261,7 +1216,6 @@ export default React.createClass({
currentRoomId: null,
page_type: PageTypes.RoomDirectory,
});
this._teamToken = null;
this._setPageSubtitle();
},
@ -1707,15 +1661,13 @@ export default React.createClass({
onReturnToAppClick: function() {
// treat it the same as if the user had completed the login
this._onLoggedIn(null);
this._onLoggedIn();
},
// returns a promise which resolves to the new MatrixClient
onRegistered: function(credentials, teamToken) {
// XXX: These both should be in state or ideally store(s) because we risk not
onRegistered: function(credentials) {
// XXX: This should be in state or ideally store(s) because we risk not
// rendering the most up-to-date view of state otherwise.
// teamToken may not be truthy
this._teamToken = teamToken;
this._is_registered = true;
return Lifecycle.setLoggedIn(credentials);
},
@ -1888,7 +1840,6 @@ export default React.createClass({
onCloseAllSettings={this.onCloseAllSettings}
onRegistered={this.onRegistered}
currentRoomId={this.state.currentRoomId}
teamToken={this._teamToken}
showCookieBar={this.state.showCookieBar}
{...this.props}
{...this.state}
@ -1929,7 +1880,6 @@ export default React.createClass({
defaultHsUrl={this.getDefaultHsUrl()}
defaultIsUrl={this.getDefaultIsUrl()}
brand={this.props.config.brand}
teamServerConfig={this.props.config.teamServerConfig}
customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()}
makeRegistrationUrl={this._makeRegistrationUrl}

View file

@ -167,13 +167,6 @@ module.exports = React.createClass({
onClose: PropTypes.func,
// The brand string given when creating email pushers
brand: PropTypes.string,
// The base URL to use in the referral link. Defaults to window.location.origin.
referralBaseUrl: PropTypes.string,
// Team token for the referral link. If falsy, the referral section will
// not appear
teamToken: PropTypes.string,
},
getDefaultProps: function() {
@ -590,27 +583,6 @@ module.exports = React.createClass({
return <GroupUserSettings />;
},
_renderReferral: function() {
const teamToken = this.props.teamToken;
if (!teamToken) {
return null;
}
if (typeof teamToken !== 'string') {
console.warn('Team token not a string');
return null;
}
const href = (this.props.referralBaseUrl || window.location.origin) +
`/#/register?referrer=${this._me}&team_token=${teamToken}`;
return (
<div>
<h3>Referral</h3>
<div className="mx_UserSettings_section">
{ _t("Refer a friend to Riot:") } <a href={href} target="_blank" rel="noopener">{ href }</a>
</div>
</div>
);
},
onLanguageChange: function(newLang) {
if (this.state.language !== newLang) {
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLang);
@ -1355,8 +1327,6 @@ module.exports = React.createClass({
{ this._renderGroupSettings() }
{ this._renderReferral() }
{ notificationArea }
{ this._renderUserInterfaceSettings() }

View file

@ -23,9 +23,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import RegistrationForm from '../../views/auth/RegistrationForm';
import RtsClient from '../../../RtsClient';
import { _t, _td } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
@ -48,13 +46,6 @@ module.exports = React.createClass({
brand: PropTypes.string,
email: PropTypes.string,
referrer: PropTypes.string,
teamServerConfig: PropTypes.shape({
// Email address to request new teams
supportEmail: PropTypes.string.isRequired,
// URL of the riot-team-server to get team configurations and track referrals
teamServerURL: PropTypes.string.isRequired,
}),
teamSelected: PropTypes.object,
// The default server name to use when the user hasn't specified
// one. This is used when displaying the defaultHsUrl in the UI.
@ -70,18 +61,11 @@ module.exports = React.createClass({
onLoginClick: PropTypes.func.isRequired,
onCancelClick: PropTypes.func,
onServerConfigChange: PropTypes.func.isRequired,
rtsClient: PropTypes.shape({
getTeamsConfig: PropTypes.func.isRequired,
trackReferral: PropTypes.func.isRequired,
getTeam: PropTypes.func.isRequired,
}),
},
getInitialState: function() {
return {
busy: false,
teamServerBusy: false,
errorText: null,
// We remember the values entered by the user because
// the registration form will be unmounted during the
@ -106,37 +90,7 @@ module.exports = React.createClass({
componentWillMount: function() {
this._unmounted = false;
this._replaceClient();
if (
this.props.teamServerConfig &&
this.props.teamServerConfig.teamServerURL &&
!this._rtsClient
) {
this._rtsClient = this.props.rtsClient || new RtsClient(this.props.teamServerConfig.teamServerURL);
this.setState({
teamServerBusy: true,
});
// GET team configurations including domains, names and icons
this._rtsClient.getTeamsConfig().then((data) => {
const teamsConfig = {
teams: data,
supportEmail: this.props.teamServerConfig.supportEmail,
};
console.log('Setting teams config to ', teamsConfig);
this.setState({
teamsConfig: teamsConfig,
teamServerBusy: false,
});
}, (err) => {
console.error('Error retrieving config for teams', err);
this.setState({
teamServerBusy: false,
});
});
}
},
onServerConfigChange: function(config) {
@ -191,7 +145,7 @@ module.exports = React.createClass({
});
},
_onUIAuthFinished: function(success, response, extra) {
_onUIAuthFinished: async function(success, response, extra) {
if (!success) {
let msg = response.message || response.toString();
// can we give a better error message?
@ -240,58 +194,15 @@ module.exports = React.createClass({
doingUIAuth: false,
});
// Done regardless of `teamSelected`. People registering with non-team emails
// will just nop. The point of this being we might not have the email address
// that the user registered with at this stage (depending on whether this
// is the client they initiated registration).
let trackPromise = Promise.resolve(null);
if (this._rtsClient && extra.emailSid) {
// Track referral if this.props.referrer set, get team_token in order to
// retrieve team config and see welcome page etc.
trackPromise = this._rtsClient.trackReferral(
this.props.referrer || '', // Default to empty string = not referred
extra.emailSid,
extra.clientSecret,
).then((data) => {
const teamToken = data.team_token;
// Store for use /w welcome pages
window.localStorage.setItem('mx_team_token', teamToken);
this._rtsClient.getTeam(teamToken).then((team) => {
console.log(
`User successfully registered with team ${team.name}`,
);
if (!team.rooms) {
return;
}
// Auto-join rooms
team.rooms.forEach((room) => {
if (room.auto_join && room.room_id) {
console.log(`Auto-joining ${room.room_id}`);
MatrixClientPeg.get().joinRoom(room.room_id);
}
});
}, (err) => {
console.error('Error getting team config', err);
});
return teamToken;
}, (err) => {
console.error('Error tracking referral', err);
});
}
trackPromise.then((teamToken) => {
return this.props.onLoggedIn({
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: this._matrixClient.getHomeserverUrl(),
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
}, teamToken);
}).then((cli) => {
return this._setupPushers(cli);
const cli = await this.props.onLoggedIn({
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: this._matrixClient.getHomeserverUrl(),
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
});
this._setupPushers(cli);
},
_setupPushers: function(matrixClient) {
@ -356,12 +267,6 @@ module.exports = React.createClass({
});
},
onTeamSelected: function(teamSelected) {
if (!this._unmounted) {
this.setState({ teamSelected });
}
},
onLoginClick: function(ev) {
ev.preventDefault();
ev.stopPropagation();
@ -418,7 +323,7 @@ module.exports = React.createClass({
poll={true}
/>
);
} else if (this.state.busy || this.state.teamServerBusy || !this.state.flows) {
} else if (this.state.busy || !this.state.flows) {
registerBody = <Spinner />;
} else {
let serverConfigSection;
@ -443,11 +348,9 @@ module.exports = React.createClass({
defaultPhoneCountry={this.state.formVals.phoneCountry}
defaultPhoneNumber={this.state.formVals.phoneNumber}
defaultPassword={this.state.formVals.password}
teamsConfig={this.state.teamsConfig}
minPasswordLength={MIN_PASSWORD_LENGTH}
onError={this.onFormValidationFailed}
onRegisterClick={this.onFormSubmit}
onTeamSelected={this.onTeamSelected}
flows={this.state.flows}
/>
{ serverConfigSection }
@ -472,12 +375,7 @@ module.exports = React.createClass({
return (
<AuthPage>
<AuthHeader
icon={this.state.teamSelected ?
this.props.teamServerConfig.teamServerURL + "/static/common/" +
this.state.teamSelected.domain + "/icon.png" :
null}
/>
<AuthHeader />
<AuthBody>
<h2>{ _t('Create your account') }</h2>
{ registerBody }

View file

@ -46,17 +46,6 @@ module.exports = React.createClass({
defaultPhoneNumber: PropTypes.string,
defaultUsername: PropTypes.string,
defaultPassword: PropTypes.string,
teamsConfig: PropTypes.shape({
// Email address to request new teams
supportEmail: PropTypes.string,
teams: PropTypes.arrayOf(PropTypes.shape({
// The displayed name of the team
"name": PropTypes.string,
// The domain of team email addresses
"domain": PropTypes.string,
})).required,
}),
minPasswordLength: PropTypes.number,
onError: PropTypes.func,
onRegisterClick: PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise
@ -75,7 +64,6 @@ module.exports = React.createClass({
getInitialState: function() {
return {
fieldValid: {},
selectedTeam: null,
// The ISO2 country code selected in the phone number entry
phoneCountry: this.props.defaultPhoneCountry,
};
@ -150,10 +138,6 @@ module.exports = React.createClass({
return true;
},
_isUniEmail: function(email) {
return email.endsWith('.ac.uk') || email.endsWith('.edu') || email.endsWith('matrix.org');
},
validateField: function(fieldID) {
const pwd1 = this.refs.password.value.trim();
const pwd2 = this.refs.passwordConfirm.value.trim();
@ -161,24 +145,6 @@ module.exports = React.createClass({
switch (fieldID) {
case FIELD_EMAIL: {
const email = this.refs.email.value;
if (this.props.teamsConfig && this._isUniEmail(email)) {
const matchingTeam = this.props.teamsConfig.teams.find(
(team) => {
return email.split('@').pop() === team.domain;
},
) || null;
this.setState({
selectedTeam: matchingTeam,
showSupportEmail: !matchingTeam,
});
this.props.onTeamSelected(matchingTeam);
} else {
this.props.onTeamSelected(null);
this.setState({
selectedTeam: null,
showSupportEmail: false,
});
}
const emailValid = email === '' || Email.looksValid(email);
if (this._authStepIsRequired('m.login.email.identity') && (!emailValid || email === '')) {
this.markFieldValid(fieldID, false, "RegistrationForm.ERR_MISSING_EMAIL");
@ -304,30 +270,6 @@ module.exports = React.createClass({
value={self.state.email} />
</div>
);
let belowEmailSection;
if (this.props.teamsConfig) {
if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
belowEmailSection = (
<p className="mx_Login_support">
Sorry, but your university is not registered with us just yet.&nbsp;
Email us on&nbsp;
<a href={"mailto:" + this.props.teamsConfig.supportEmail}>
{ this.props.teamsConfig.supportEmail }
</a>&nbsp;
to get your university signed up.
Or continue to register with Riot to enjoy our open source platform.
</p>
);
} else if (this.state.selectedTeam) {
belowEmailSection = (
<p className="mx_Login_support">
{_t("You are registering with %(SelectedTeamName)s", {
SelectedTeamName: this.state.selectedTeam.name,
})}
</p>
);
}
}
const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown');
let phoneSection;
@ -369,7 +311,6 @@ module.exports = React.createClass({
<div>
<form onSubmit={this.onSubmit}>
{ emailSection }
{ belowEmailSection }
{ phoneSection }
<input type="text" ref="username"
placeholder={placeholderUserName} defaultValue={this.props.defaultUsername}

View file

@ -193,9 +193,6 @@ export default React.createClass({
return;
}
// XXX Implement RTS /register here
const teamToken = null;
this.props.onFinished(true, {
userId: response.user_id,
deviceId: response.device_id,
@ -203,7 +200,6 @@ export default React.createClass({
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
password: this._generatedPassword,
teamToken: teamToken,
});
},

View file

@ -15,7 +15,6 @@ limitations under the License.
*/
import Skinner from './Skinner';
import RtsClient from './RtsClient';
module.exports.loadSkin = function(skinObject) {
Skinner.load(skinObject);
@ -28,7 +27,3 @@ module.exports.resetSkin = function() {
module.exports.getComponent = function(componentName) {
return Skinner.getComponent(componentName);
};
module.exports.setFetch = function(fetchFunction) {
RtsClient.setFetch(fetchFunction);
};