mirror of
https://github.com/wassname/talk.git
synced 2026-07-13 17:45:56 +08:00
Refactor postMessage service
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import { HANDLE_SUCCESSFUL_LOGIN } from 'coral-framework/constants/auth';
|
||||
import sendMessage from 'coral-framework/services/messages/sendMessage';
|
||||
import { getStaticConfiguration } from 'coral-framework/services/staticConfiguration';
|
||||
import { createPostMessage } from 'coral-framework/services/postMessage';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
try {
|
||||
const staticConfig = getStaticConfiguration();
|
||||
const { STATIC_ORIGIN: origin } = staticConfig;
|
||||
const postMessage = createPostMessage(origin);
|
||||
|
||||
// Get the auth element and parse it as JSON by decoding it.
|
||||
const auth = document.getElementById('auth');
|
||||
const doc = document.implementation.createHTMLDocument('');
|
||||
@@ -18,7 +23,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const { user, token } = data;
|
||||
|
||||
// Send the state back.
|
||||
sendMessage(HANDLE_SUCCESSFUL_LOGIN, { user, token });
|
||||
postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token });
|
||||
}
|
||||
} finally {
|
||||
// Always close the window.
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
getDefinitionName,
|
||||
getSlotFragmentSpreads,
|
||||
} from 'coral-framework/utils';
|
||||
import { withQuery } from 'coral-framework/hocs';
|
||||
import { withQuery, withPopupAuthHandler } from 'coral-framework/hocs';
|
||||
import Embed from '../components/Embed';
|
||||
import Stream from '../tabs/stream/containers/Stream';
|
||||
import AutomaticAssetClosure from './AutomaticAssetClosure';
|
||||
@@ -29,17 +29,6 @@ import t from 'coral-framework/services/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import { setActiveTab } from '../actions/embed';
|
||||
|
||||
// TODO: refactor out to a HOC
|
||||
import { HANDLE_SUCCESSFUL_LOGIN } from 'coral-framework/constants/auth';
|
||||
import {
|
||||
handleSuccessfulLogin,
|
||||
setAuthToken,
|
||||
} from 'coral-framework/actions/auth';
|
||||
import {
|
||||
subscribeToMessages,
|
||||
unsubscribeFromMessages,
|
||||
} from 'coral-framework/services/messages';
|
||||
|
||||
class EmbedContainer extends React.Component {
|
||||
static contextTypes = {
|
||||
pym: PropTypes.object,
|
||||
@@ -48,8 +37,6 @@ class EmbedContainer extends React.Component {
|
||||
subscriptions = [];
|
||||
|
||||
subscribeToUpdates(props = this.props) {
|
||||
subscribeToMessages(this.handleAuth);
|
||||
|
||||
if (props.currentUser) {
|
||||
const newSubscriptions = [
|
||||
{
|
||||
@@ -99,28 +86,8 @@ class EmbedContainer extends React.Component {
|
||||
unsubscribe() {
|
||||
this.subscriptions.forEach(unsubscribe => unsubscribe());
|
||||
this.subscriptions = [];
|
||||
unsubscribeFromMessages(this.handleAuth);
|
||||
}
|
||||
|
||||
// TODO: refactor out to a HOC
|
||||
handleAuth = ({ name, data }) => {
|
||||
if (name !== HANDLE_SUCCESSFUL_LOGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// data will contain the user and token.
|
||||
const { user, token } = data;
|
||||
const { handleSuccessfulLogin, setAuthToken } = this.props;
|
||||
|
||||
if (user && token) {
|
||||
handleSuccessfulLogin(user, token);
|
||||
} else if (token) {
|
||||
setAuthToken(token);
|
||||
} else {
|
||||
console.error('Invalid auth data supplied', data);
|
||||
}
|
||||
};
|
||||
|
||||
resubscribe(props) {
|
||||
this.unsubscribe();
|
||||
this.subscribeToUpdates(props);
|
||||
@@ -334,8 +301,6 @@ EmbedContainer.propTypes = {
|
||||
fetchAssetSuccess: PropTypes.func,
|
||||
showSignInDialog: PropTypes.bool,
|
||||
signInDialogFocus: PropTypes.bool,
|
||||
handleSuccessfulLogin: PropTypes.func.isRequired,
|
||||
setAuthToken: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
@@ -363,13 +328,12 @@ const mapDispatchToProps = dispatch =>
|
||||
blurSignInDialog,
|
||||
hideSignInDialog,
|
||||
updateStatus,
|
||||
handleSuccessfulLogin,
|
||||
setAuthToken,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
export default compose(
|
||||
withPopupAuthHandler,
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
branch(props => !props.checkedInitialLogin, renderComponent(Spinner)),
|
||||
withEmbedQuery
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as actions from '../constants/auth';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import { sendMessage } from '../services/messages';
|
||||
|
||||
function cleanAuthData(localStorage) {
|
||||
localStorage.removeItem('token');
|
||||
@@ -64,7 +63,7 @@ export const setAuthToken = token => (dispatch, _, { localStorage }) => {
|
||||
export const handleSuccessfulLogin = (user, token) => (
|
||||
dispatch,
|
||||
_,
|
||||
{ client, localStorage }
|
||||
{ client, localStorage, postMessage }
|
||||
) => {
|
||||
const { exp } = jwtDecode(token);
|
||||
|
||||
@@ -76,7 +75,7 @@ export const handleSuccessfulLogin = (user, token) => (
|
||||
// Send the message via the messages service to the window.opener if it
|
||||
// exists.
|
||||
if (window.opener) {
|
||||
sendMessage(
|
||||
postMessage.post(
|
||||
actions.HANDLE_SUCCESSFUL_LOGIN,
|
||||
{ user, token },
|
||||
window.opener
|
||||
|
||||
@@ -17,6 +17,7 @@ class TalkProvider extends React.Component {
|
||||
store: this.props.store,
|
||||
pymLocalStorage: this.props.pymLocalStorage,
|
||||
pymSessionStorage: this.props.pymSessionStorage,
|
||||
postMessage: this.props.postMessage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ TalkProvider.childContextTypes = {
|
||||
pymSessionStorage: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
store: PropTypes.object,
|
||||
postMessage: PropTypes.object,
|
||||
};
|
||||
|
||||
TalkProvider.propTypes = TalkProvider.childContextTypes;
|
||||
|
||||
@@ -10,6 +10,7 @@ export { default as withSignIn } from './withSignIn';
|
||||
export { default as withSignUp } from './withSignUp';
|
||||
export { default as withForgotPassword } from './withForgotPassword';
|
||||
export { default as withSetUsername } from './withSetUsername';
|
||||
export { default as withPopupAuthHandler } from './withPopupAuthHandler';
|
||||
export {
|
||||
default as withResendEmailConfirmation,
|
||||
} from './withResendEmailConfirmation';
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import hoistStatics from 'recompose/hoistStatics';
|
||||
import PropTypes from 'prop-types';
|
||||
import { HANDLE_SUCCESSFUL_LOGIN } from 'coral-framework/constants/auth';
|
||||
import {
|
||||
handleSuccessfulLogin,
|
||||
setAuthToken,
|
||||
} from 'coral-framework/actions/auth';
|
||||
|
||||
/**
|
||||
* WithPopupAuthHandler listens to successful logins over
|
||||
* the `postMessage` service.
|
||||
*/
|
||||
export default hoistStatics(WrappedComponent => {
|
||||
class WithPopupAuthHandler extends React.Component {
|
||||
static contextTypes = {
|
||||
store: PropTypes.object,
|
||||
postMessage: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
context.postMessage.subscribe(this.handleAuth);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.context.postMessage.unsubscribe(this.handleAuth);
|
||||
}
|
||||
|
||||
handleAuth = ({ name, data }) => {
|
||||
if (name !== HANDLE_SUCCESSFUL_LOGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { store } = this.context;
|
||||
// data will contain the user and token.
|
||||
const { user, token } = data;
|
||||
|
||||
if (user && token) {
|
||||
store.dispatch(handleSuccessfulLogin(user, token));
|
||||
} else if (token) {
|
||||
store.dispatch(setAuthToken(token));
|
||||
} else {
|
||||
console.error('Invalid auth data supplied', data);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
return WithPopupAuthHandler;
|
||||
});
|
||||
+5
-1
@@ -14,6 +14,7 @@ import { createPluginsService } from './plugins';
|
||||
import { createNotificationService } from './notification';
|
||||
import { createGraphQLRegistry } from './graphqlRegistry';
|
||||
import { createGraphQLService } from './graphql';
|
||||
import { createPostMessage } from './postMessage';
|
||||
import globalFragments from 'coral-framework/graphql/fragments';
|
||||
import {
|
||||
createStorage,
|
||||
@@ -118,7 +119,7 @@ export async function createContext({
|
||||
});
|
||||
|
||||
const staticConfig = getStaticConfiguration();
|
||||
let { LIVE_URI: liveUri } = staticConfig;
|
||||
let { LIVE_URI: liveUri, STATIC_ORIGIN: origin } = staticConfig;
|
||||
if (liveUri == null) {
|
||||
// The protocol must match the origin protocol, secure/insecure.
|
||||
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
@@ -128,6 +129,8 @@ export async function createContext({
|
||||
liveUri = `${protocol}://${location.host}${BASE_PATH}api/v1/live`;
|
||||
}
|
||||
|
||||
const postMessage = createPostMessage(origin);
|
||||
|
||||
const client = createClient({
|
||||
uri: `${BASE_PATH}api/v1/graph/ql`,
|
||||
liveUri,
|
||||
@@ -158,6 +161,7 @@ export async function createContext({
|
||||
pymLocalStorage,
|
||||
pymSessionStorage,
|
||||
inIframe,
|
||||
postMessage,
|
||||
};
|
||||
|
||||
// Load framework fragments.
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { getStaticConfiguration } from 'coral-framework/services/staticConfiguration';
|
||||
|
||||
// Load the static url from the static configuration, we'll use it to formulate
|
||||
// the authorized domain to allow the message to be sent to.
|
||||
const { STATIC_ORIGIN } = getStaticConfiguration();
|
||||
|
||||
// Export the origin that will be sent.
|
||||
export { STATIC_ORIGIN as ORIGIN };
|
||||
|
||||
// Scope all the requests made via the messages service.
|
||||
export const SCOPE = 'client';
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default as sendMessage } from './sendMessage';
|
||||
export { subscribeToMessages, unsubscribeFromMessages } from './subscriptions';
|
||||
@@ -1,13 +0,0 @@
|
||||
import { SCOPE, ORIGIN } from './constants';
|
||||
|
||||
export default (name, data, target = window.opener) => {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Serialize the message to be sent via postMessage.
|
||||
const msg = { name, data, scope: SCOPE };
|
||||
|
||||
// Send the message.
|
||||
target.postMessage(msg, ORIGIN);
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
import { SCOPE, ORIGIN } from './constants';
|
||||
|
||||
import set from 'lodash/set';
|
||||
import has from 'lodash/has';
|
||||
import get from 'lodash/get';
|
||||
import remove from 'lodash/remove';
|
||||
|
||||
// withOriginCheck wraps a given handler for a messages event with a check to
|
||||
// see if the origin matches.
|
||||
const withOriginCheck = handler => {
|
||||
return event => {
|
||||
const origin = get(event, 'origin');
|
||||
if (origin !== ORIGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scope = get(event, 'data.scope');
|
||||
if (scope !== SCOPE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the handler the event details.
|
||||
handler(event);
|
||||
};
|
||||
};
|
||||
|
||||
// withParseData will parse the data.
|
||||
const withParseData = handler => {
|
||||
return event => {
|
||||
const data = get(event, 'data.data');
|
||||
const name = get(event, 'data.name');
|
||||
if (!data || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler({ data, name, event });
|
||||
};
|
||||
};
|
||||
|
||||
// Store a reference to each listener added.
|
||||
const listeners = {};
|
||||
|
||||
export const subscribeToMessages = (handler, target = window) => {
|
||||
// If this handler is already attached to the target, detach it.
|
||||
if (has(listeners, [target, handler])) {
|
||||
unsubscribeFromMessages(handler, target);
|
||||
}
|
||||
|
||||
// Wrap the listener with a origin check.
|
||||
const listener = withOriginCheck(withParseData(handler));
|
||||
|
||||
// Save a reference to the compiled listener.
|
||||
set(listeners, [target, handler], listener);
|
||||
|
||||
// Attach the listener to the target.
|
||||
target.addEventListener('message', listener);
|
||||
};
|
||||
|
||||
export const unsubscribeFromMessages = (handler, target = window) => {
|
||||
if (!has(listeners, [target, handler])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = get(listeners, [target, handler]);
|
||||
|
||||
// Remove the listener from the target.
|
||||
target.removeEventListener('message', listener);
|
||||
|
||||
remove(listeners, [target, handler]);
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import set from 'lodash/set';
|
||||
import has from 'lodash/has';
|
||||
import get from 'lodash/get';
|
||||
import remove from 'lodash/remove';
|
||||
|
||||
/**
|
||||
* createPostMessage returns a service that deals with cross
|
||||
* window communication using the postMessage API.
|
||||
* @param {Object} options
|
||||
* @return {Object} messenger service
|
||||
*/
|
||||
export function createPostMessage(origin, scope = 'client') {
|
||||
// Store a reference to each listener added.
|
||||
const listeners = {};
|
||||
|
||||
// withOriginCheck wraps a given handler for a messages event with a check to
|
||||
// see if the origin matches.
|
||||
const withOriginCheck = handler => {
|
||||
return event => {
|
||||
if (get(event, 'origin') !== origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (get(event, 'data.scope') !== scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the handler the event details.
|
||||
handler(event);
|
||||
};
|
||||
};
|
||||
|
||||
// withParseData will parse the data.
|
||||
const withParseData = handler => {
|
||||
return event => {
|
||||
const data = get(event, 'data.data');
|
||||
const name = get(event, 'data.name');
|
||||
if (!data || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler({ data, name, event });
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
post(name, data, target = window.opener) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Serialize the message to be sent via postMessage.
|
||||
const msg = { name, data, scope };
|
||||
|
||||
// Send the message.
|
||||
target.postMessage(msg, origin);
|
||||
},
|
||||
subscribe: (handler, target = window) => {
|
||||
// If this handler is already attached to the target, detach it.
|
||||
if (has(listeners, [target, handler])) {
|
||||
this.unsubscribeFromMessages(handler, target);
|
||||
}
|
||||
|
||||
// Wrap the listener with a origin check.
|
||||
const listener = withOriginCheck(withParseData(handler));
|
||||
|
||||
// Save a reference to the compiled listener.
|
||||
set(listeners, [target, handler], listener);
|
||||
|
||||
// Attach the listener to the target.
|
||||
target.addEventListener('message', listener);
|
||||
},
|
||||
unsubscribe: (handler, target = window) => {
|
||||
if (!has(listeners, [target, handler])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = get(listeners, [target, handler]);
|
||||
|
||||
// Remove the listener from the target.
|
||||
target.removeEventListener('message', listener);
|
||||
|
||||
remove(listeners, [target, handler]);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user