mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 10:35:20 +00:00
FE-494: Added init logic for checkout redesign. (#153)
This commit is contained in:
parent
35458e3982
commit
9d1b69843a
@ -36,7 +36,9 @@
|
||||
"@types/classnames": "~2.2.3",
|
||||
"@types/react": "~16.0.1",
|
||||
"@types/react-dom": "~16.0.1",
|
||||
"@types/react-redux": "~5.0.13",
|
||||
"@types/react-transition-group": "~1.1.0",
|
||||
"@types/url-parse": "~1.1.0",
|
||||
"autoprefixer": "~6.7.7",
|
||||
"awesome-typescript-loader": "~3.2.3",
|
||||
"babel-core": "~6.24.1",
|
||||
@ -71,6 +73,7 @@
|
||||
"node-sass": "~4.5.1",
|
||||
"react-addons-test-utils": "~15.5.1",
|
||||
"react-test-renderer": "~15.5.4",
|
||||
"redux-devtools-extension": "~2.13.2",
|
||||
"sass-loader": "~6.0.3",
|
||||
"sinon": "~2.2.0",
|
||||
"sinon-chai": "~2.10.0",
|
||||
|
2
src/app/actions/index.ts
Normal file
2
src/app/actions/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './result-action';
|
||||
export * from './type-keys';
|
13
src/app/actions/result-action/close.ts
Normal file
13
src/app/actions/result-action/close.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { TypeKeys } from '../type-keys';
|
||||
import { Dispatch } from 'redux';
|
||||
import { Result } from '../../state';
|
||||
import { ResultAction } from './result-action';
|
||||
|
||||
export function close(): Dispatch<ResultAction> {
|
||||
return (dispatch: Dispatch<ResultAction>) => {
|
||||
dispatch({
|
||||
type: TypeKeys.SET_RESULT,
|
||||
payload: Result.close
|
||||
} as ResultAction);
|
||||
};
|
||||
}
|
2
src/app/actions/result-action/index.ts
Normal file
2
src/app/actions/result-action/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './result-action';
|
||||
export * from './close';
|
8
src/app/actions/result-action/result-action.ts
Normal file
8
src/app/actions/result-action/result-action.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Action } from 'redux';
|
||||
import { TypeKeys } from '../type-keys';
|
||||
import { Result } from '../../state';
|
||||
|
||||
export interface ResultAction extends Action {
|
||||
type: TypeKeys.SET_RESULT,
|
||||
payload: Result
|
||||
}
|
3
src/app/actions/type-keys.ts
Normal file
3
src/app/actions/type-keys.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum TypeKeys {
|
||||
SET_RESULT = 'SET_RESULT'
|
||||
}
|
@ -1,14 +1,24 @@
|
||||
import * as React from 'react';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import * as styles from './close.scss';
|
||||
import {Icon} from '../../index';
|
||||
import { Icon } from '../../index';
|
||||
import { close } from '../../../actions/result-action';
|
||||
import { ResultAction } from '../../../actions/result-action/result-action';
|
||||
|
||||
export class Close extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.close}>
|
||||
<Icon icon='cross' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
interface CloseProps {
|
||||
close: () => Dispatch<ResultAction>;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch<ResultAction>) => ({
|
||||
close: bindActionCreators(close, dispatch)
|
||||
});
|
||||
|
||||
const CloseDef: React.SFC<CloseProps> = (props) =>
|
||||
(
|
||||
<div className={styles.close} onClick={props.close}>
|
||||
<Icon icon='cross'/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Close = connect(null, mapDispatchToProps)(CloseDef);
|
||||
|
@ -28,16 +28,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
.overlayContainer {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@include responsive(sm) {
|
||||
background: rgba(0, 0, 0, .7);
|
||||
transform: translateZ(-1000px);
|
||||
animation: fadein .5s;
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,10 +78,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.appearOverlay {
|
||||
@include responsive(sm) {
|
||||
animation: fadein .5s;
|
||||
}
|
||||
}
|
||||
|
||||
.enterOverlay {
|
||||
background: green;
|
||||
}
|
||||
|
||||
.leaveOverlay {
|
||||
@include responsive(sm) {
|
||||
animation: fadeout .5s;
|
||||
}
|
||||
}
|
||||
|
||||
.appearContainer {
|
||||
@include responsive(sm) {
|
||||
//animation: popup .75s;
|
||||
animation: popout 1s;
|
||||
animation: popup .75s;
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +106,7 @@
|
||||
|
||||
.leaveContainer {
|
||||
@include responsive(sm) {
|
||||
animation: rotatein 0s;
|
||||
animation: popout 1s;
|
||||
}
|
||||
}
|
||||
|
||||
|
14
src/app/components/container/container.scss.d.ts
vendored
14
src/app/components/container/container.scss.d.ts
vendored
@ -1,20 +1,24 @@
|
||||
export const mainContainer: string;
|
||||
export const overlayContainer: string;
|
||||
export const overlay: string;
|
||||
export const fadein: string;
|
||||
export const container: string;
|
||||
export const form_container: string;
|
||||
export const appearOverlay: string;
|
||||
export const fadein: string;
|
||||
export const enterOverlay: string;
|
||||
export const leaveOverlay: string;
|
||||
export const fadeout: string;
|
||||
export const appearContainer: string;
|
||||
export const popout: string;
|
||||
export const popup: string;
|
||||
export const enterContainer: string;
|
||||
export const leaveContainer: string;
|
||||
export const rotatein: string;
|
||||
export const popout: string;
|
||||
export const appearFormContainer: string;
|
||||
export const rotatein: string;
|
||||
export const enterFormContainer: string;
|
||||
export const leaveFormContainer: string;
|
||||
export const rotateout: string;
|
||||
export const fadeout: string;
|
||||
export const slidedown: string;
|
||||
export const slideup: string;
|
||||
export const growth: string;
|
||||
export const popup: string;
|
||||
export const shake: string;
|
||||
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import * as TransitionGroup from 'react-transition-group';
|
||||
import * as styles from './container.scss';
|
||||
|
||||
import {Header, Info, Footer, Form, ThreeDSContainer, ContainerLoader, Close} from '../index';
|
||||
import { Header, Info, Footer, Form, ThreeDSContainer, ContainerLoader, Close } from '../index';
|
||||
|
||||
export class Container extends React.Component {
|
||||
static getView(): string {
|
||||
@ -21,7 +21,23 @@ export class Container extends React.Component {
|
||||
const CSSTransitionGroup = TransitionGroup.CSSTransitionGroup;
|
||||
return (
|
||||
<div className={styles.mainContainer}>
|
||||
<div className={styles.overlay} />
|
||||
<CSSTransitionGroup
|
||||
component='div'
|
||||
className={styles.overlayContainer}
|
||||
transitionName={{
|
||||
appear: styles.appearOverlay,
|
||||
enter: styles.enterOverlay,
|
||||
leave: styles.leaveOverlay
|
||||
}}
|
||||
transitionEnterTimeout={1000}
|
||||
transitionLeaveTimeout={1000}
|
||||
transitionAppearTimeout={1000}
|
||||
transitionAppear={true}
|
||||
transitionEnter={true}
|
||||
transitionLeave={true}
|
||||
>
|
||||
<div className={styles.overlay}/>
|
||||
</CSSTransitionGroup>
|
||||
{Container.getView() === 'loading' ? <ContainerLoader/> : false}
|
||||
<CSSTransitionGroup
|
||||
component='div'
|
||||
@ -39,7 +55,7 @@ export class Container extends React.Component {
|
||||
>
|
||||
{Container.getView() !== 'loading' ?
|
||||
<div className={styles.container}>
|
||||
<Close />
|
||||
<Close/>
|
||||
{Container.getView() === 'default' ?
|
||||
<CSSTransitionGroup
|
||||
component='div'
|
||||
|
5
src/app/config/app-config.ts
Normal file
5
src/app/config/app-config.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class AppConfig {
|
||||
capiEndpoint: string;
|
||||
applePayMerchantID: string;
|
||||
applePayMerchantValidationEndpoint: string;
|
||||
}
|
60
src/app/config/config-resolver.ts
Normal file
60
src/app/config/config-resolver.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import * as URL from 'url-parse';
|
||||
import { Transport, PossibleEvents } from '../../communication-ts';
|
||||
import { UriSerializer } from '../../utils/uri-serializer';
|
||||
import { AppConfig, Config, InitConfig } from '.';
|
||||
import { IntegrationType } from './integration-type';
|
||||
|
||||
export class ConfigResolver {
|
||||
|
||||
static resolve(transport: Transport): Promise<Config> {
|
||||
return Promise.all([
|
||||
this.resolveInitConfig(transport),
|
||||
this.loadAppConfig()
|
||||
]).then((configs) => {
|
||||
return {
|
||||
origin: this.getOrigin(),
|
||||
initConfig: configs[0],
|
||||
appConfig: configs[1]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static loadAppConfig(): Promise<AppConfig> {
|
||||
return fetch('../appConfig.json', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
method: 'GET'
|
||||
}).then((response) => response.json());
|
||||
}
|
||||
|
||||
private static resolveInitConfig(transport: Transport): Promise<InitConfig> {
|
||||
return new Promise((resolve) => {
|
||||
this.isUriContext()
|
||||
? resolve(UriSerializer.deserialize(location.search))
|
||||
: transport.on(PossibleEvents.init, (config) => resolve(config));
|
||||
}).then((config: InitConfig) => {
|
||||
config.integrationType = this.calcIntegrationType();
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
||||
private static calcIntegrationType(): IntegrationType {
|
||||
return IntegrationType.invoice; // TODO implement here
|
||||
}
|
||||
|
||||
private static isUriContext(): boolean {
|
||||
return !!location.search;
|
||||
}
|
||||
|
||||
private static getOrigin(): string {
|
||||
const currentScript: any = document.currentScript || this.getCurrentScript();
|
||||
const url = URL(currentScript.src);
|
||||
return url.origin;
|
||||
}
|
||||
|
||||
private static getCurrentScript(): HTMLElement {
|
||||
const scripts = document.getElementsByTagName('script');
|
||||
return scripts[scripts.length - 1];
|
||||
}
|
||||
}
|
8
src/app/config/config.ts
Normal file
8
src/app/config/config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { AppConfig } from './app-config';
|
||||
import { InitConfig } from './init-config';
|
||||
|
||||
export class Config {
|
||||
origin: string;
|
||||
initConfig: InitConfig;
|
||||
appConfig: AppConfig;
|
||||
}
|
12
src/app/config/customer-init-config.ts
Normal file
12
src/app/config/customer-init-config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { InitConfig } from './init-config';
|
||||
import { IntegrationType } from './integration-type';
|
||||
|
||||
export class CustomerInitConfig extends InitConfig {
|
||||
customerID: string;
|
||||
customerAccessToken: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.integrationType = IntegrationType.customer;
|
||||
}
|
||||
}
|
4
src/app/config/hold-expiration.ts
Normal file
4
src/app/config/hold-expiration.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum HoldExpiration {
|
||||
cancel = 'cancel',
|
||||
capture = 'capture'
|
||||
}
|
9
src/app/config/index.ts
Normal file
9
src/app/config/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export * from './app-config';
|
||||
export * from './config';
|
||||
export * from './config-resolver';
|
||||
export * from './customer-init-config';
|
||||
export * from './hold-expiration';
|
||||
export * from './init-config';
|
||||
export * from './integration-type';
|
||||
export * from './invoice-init-config';
|
||||
export * from './invoice-template-init-config';
|
13
src/app/config/init-config.ts
Normal file
13
src/app/config/init-config.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { HoldExpiration } from './hold-expiration';
|
||||
import { IntegrationType } from './integration-type';
|
||||
|
||||
export class InitConfig {
|
||||
integrationType: IntegrationType;
|
||||
terminals: boolean = false;
|
||||
paymentFlowHold: boolean = false;
|
||||
holdExpiration: HoldExpiration = HoldExpiration.cancel;
|
||||
locale: string = 'auto';
|
||||
name?: string;
|
||||
description?: string;
|
||||
email?: string;
|
||||
}
|
5
src/app/config/integration-type.ts
Normal file
5
src/app/config/integration-type.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum IntegrationType {
|
||||
invoice = 'invoice',
|
||||
invoiceTemplate = 'invoiceTemplate',
|
||||
customer = 'customer'
|
||||
}
|
12
src/app/config/invoice-init-config.ts
Normal file
12
src/app/config/invoice-init-config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { InitConfig } from './init-config';
|
||||
import { IntegrationType } from './integration-type';
|
||||
|
||||
export class InvoiceInitConfig extends InitConfig {
|
||||
invoiceID: string;
|
||||
invoiceAccessToken: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.integrationType = IntegrationType.invoice;
|
||||
}
|
||||
}
|
12
src/app/config/invoice-template-init-config.ts
Normal file
12
src/app/config/invoice-template-init-config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { InitConfig } from './init-config';
|
||||
import { IntegrationType } from './integration-type';
|
||||
|
||||
export class InvoiceTemplateInitConfig extends InitConfig {
|
||||
invoiceTemplateID: string;
|
||||
invoiceTemplateAccessToken: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.integrationType = IntegrationType.invoiceTemplate;
|
||||
}
|
||||
}
|
10
src/app/configure-store.ts
Normal file
10
src/app/configure-store.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { applyMiddleware, combineReducers, createStore, Store } from 'redux';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import thunk from 'redux-thunk';
|
||||
import { resultReducer } from './reducers/result-reducer';
|
||||
|
||||
export function configureStore(initialState: any): Store<any> {
|
||||
return createStore(combineReducers({
|
||||
result: resultReducer
|
||||
}), initialState, composeWithDevTools(applyMiddleware(thunk)));
|
||||
}
|
41
src/app/finalize-app.ts
Normal file
41
src/app/finalize-app.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Transport, PossibleEvents } from '../communication-ts';
|
||||
import { Result } from './state';
|
||||
|
||||
class AppFinalizer {
|
||||
|
||||
private actionTimeout = 300;
|
||||
|
||||
constructor(private transport: Transport, private checkoutEl: HTMLElement) { }
|
||||
|
||||
close() {
|
||||
ReactDOM.unmountComponentAtNode(this.checkoutEl);
|
||||
setTimeout(() => {
|
||||
this.transport.emit(PossibleEvents.close);
|
||||
this.transport.destroy();
|
||||
}, this.actionTimeout);
|
||||
}
|
||||
|
||||
done(redirectUrl?: string, popupMode?: boolean) {
|
||||
ReactDOM.unmountComponentAtNode(this.checkoutEl);
|
||||
setTimeout(() => {
|
||||
this.transport.emit(PossibleEvents.done);
|
||||
this.transport.destroy();
|
||||
if (popupMode) {
|
||||
redirectUrl ? location.replace(redirectUrl) : window.close();
|
||||
}
|
||||
}, this.actionTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
export function finalize(result: Result, transport: Transport, checkoutEl: HTMLElement, redirectUrl?: string, popupMode?: boolean) {
|
||||
const finalizer = new AppFinalizer(transport, checkoutEl);
|
||||
switch (result) {
|
||||
case Result.close:
|
||||
finalizer.close();
|
||||
break;
|
||||
case Result.done:
|
||||
finalizer.done();
|
||||
break;
|
||||
}
|
||||
}
|
@ -1,10 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Container } from './components';
|
||||
import { Provider } from 'react-redux';
|
||||
import './styles/main.scss';
|
||||
import './styles/forms.scss';
|
||||
import { ConfigResolver } from './config/config-resolver';
|
||||
import { Child } from '../communication-ts/child';
|
||||
import { Container } from './components';
|
||||
import { configureStore } from './configure-store';
|
||||
import { finalize } from './finalize-app';
|
||||
|
||||
ReactDOM.render(
|
||||
<Container/>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
Child.resolve()
|
||||
.then((transport) =>
|
||||
Promise.all([
|
||||
transport,
|
||||
ConfigResolver.resolve(transport)
|
||||
]))
|
||||
.then((res) => {
|
||||
const app = document.getElementById('app');
|
||||
const store = configureStore({});
|
||||
store.subscribe(() => {
|
||||
const state = store.getState();
|
||||
if (state.result) {
|
||||
finalize(state.result, res[0], app);
|
||||
}
|
||||
});
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<Container/>
|
||||
</Provider>,
|
||||
app
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new Error(error); // TODO need to implement
|
||||
});
|
||||
|
13
src/app/reducers/result-reducer.ts
Normal file
13
src/app/reducers/result-reducer.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { State } from '../state/state';
|
||||
import { TypeKeys } from '../actions/type-keys';
|
||||
import { ResultAction } from '../actions/result-action/result-action';
|
||||
|
||||
export function resultReducer(s: State = null, action: ResultAction) {
|
||||
switch (action.type) {
|
||||
case TypeKeys.SET_RESULT: {
|
||||
return action.payload;
|
||||
}
|
||||
default:
|
||||
return s;
|
||||
}
|
||||
}
|
2
src/app/state/index.ts
Normal file
2
src/app/state/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './result';
|
||||
export * from './state';
|
4
src/app/state/result.ts
Normal file
4
src/app/state/result.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum Result {
|
||||
close = 'close',
|
||||
done = 'done'
|
||||
}
|
5
src/app/state/state.ts
Normal file
5
src/app/state/state.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Result } from '.';
|
||||
|
||||
export type State = {
|
||||
readonly result: Result;
|
||||
}
|
7
src/app/utils/apply-mixins.ts
Normal file
7
src/app/utils/apply-mixins.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function applyMixins(derivedCtor: any, baseCtors: any[]) {
|
||||
baseCtors.forEach(baseCtor => {
|
||||
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
|
||||
derivedCtor.prototype[name] = baseCtor.prototype[name];
|
||||
});
|
||||
});
|
||||
}
|
42
src/communication-ts/child.ts
Normal file
42
src/communication-ts/child.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {
|
||||
RealTransport,
|
||||
Transport,
|
||||
ContextResolver,
|
||||
StubTransport,
|
||||
TransportInfo
|
||||
} from '.';
|
||||
|
||||
export class Child {
|
||||
|
||||
static resolve(): Promise<Transport> {
|
||||
return new Promise((resolve) => {
|
||||
if (ContextResolver.isAvailable() && window.opener) {
|
||||
const target = window.opener;
|
||||
const context = ContextResolver.get();
|
||||
return resolve(new RealTransport(target, context.parentOrigin, window));
|
||||
} else if (!this.inIframe() && !window.opener) {
|
||||
return resolve(new StubTransport());
|
||||
} else {
|
||||
const shake = (e: MessageEvent) => {
|
||||
if (e && e.data === TransportInfo.parentHandshakeMessageName) {
|
||||
const target = e.source;
|
||||
target.postMessage(TransportInfo.childHandshakeMessageName, e.origin);
|
||||
ContextResolver.set({
|
||||
parentOrigin: e.origin
|
||||
});
|
||||
return resolve(new RealTransport(target, e.origin, window));
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', shake, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static inIframe(): boolean {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
25
src/communication-ts/context-resolver.ts
Normal file
25
src/communication-ts/context-resolver.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export class ContextResolver {
|
||||
|
||||
static set(context: any) {
|
||||
try {
|
||||
sessionStorage.setItem(this.key, JSON.stringify(context));
|
||||
/* tslint:disable:no-empty */
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
static get(): any {
|
||||
try {
|
||||
return JSON.parse(sessionStorage.getItem(this.key));
|
||||
/* tslint:disable:no-empty */
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
static isAvailable(): boolean {
|
||||
try {
|
||||
return !!JSON.parse(sessionStorage.getItem(this.key));
|
||||
/* tslint:disable:no-empty */
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
private static key = 'checkout-context';
|
||||
}
|
8
src/communication-ts/index.ts
Normal file
8
src/communication-ts/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from './child';
|
||||
export * from './context-resolver';
|
||||
export * from './transport';
|
||||
export * from './real-transport';
|
||||
export * from './stub-transport';
|
||||
export * from './transport-info';
|
||||
export * from './possible-events';
|
||||
export * from './transport-message';
|
5
src/communication-ts/possible-events.ts
Normal file
5
src/communication-ts/possible-events.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum PossibleEvents {
|
||||
init = 'init-payform',
|
||||
done = 'payment-done',
|
||||
close = 'close'
|
||||
}
|
44
src/communication-ts/real-transport.ts
Normal file
44
src/communication-ts/real-transport.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Transport, TransportInfo, PossibleEvents, TransportMessage } from '.';
|
||||
|
||||
export class RealTransport implements Transport {
|
||||
|
||||
private target: Window;
|
||||
private origin: string;
|
||||
private events: any = {};
|
||||
|
||||
constructor(target: Window, origin: string, source: Window) {
|
||||
this.target = target;
|
||||
this.origin = origin;
|
||||
source.addEventListener('message', this.listener.bind(this), false);
|
||||
}
|
||||
|
||||
emit(name: PossibleEvents, data?: any) {
|
||||
const serialized = JSON.stringify({
|
||||
data,
|
||||
name,
|
||||
transport: TransportInfo.transportName
|
||||
} as TransportMessage);
|
||||
this.target.postMessage(serialized, this.origin);
|
||||
}
|
||||
|
||||
on(eventName: PossibleEvents, callback: (data: any) => any) {
|
||||
this.events[eventName] = callback;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
window.removeEventListener('message', this.listener.bind(this), false);
|
||||
}
|
||||
|
||||
private listener(e: MessageEvent) {
|
||||
let parsed: TransportMessage;
|
||||
try {
|
||||
parsed = JSON.parse(e.data);
|
||||
/* tslint:disable:no-empty */
|
||||
} catch (e) {}
|
||||
if (parsed && (parsed.name in this.events)) {
|
||||
if (parsed.transport === TransportInfo.transportName) {
|
||||
this.events[parsed.name].call(this, parsed.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
src/communication-ts/stub-transport.ts
Normal file
17
src/communication-ts/stub-transport.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Transport, PossibleEvents } from '.';
|
||||
|
||||
export class StubTransport implements Transport {
|
||||
|
||||
emit(name: PossibleEvents, data: any) {
|
||||
console.info('transport stub emit: ', name, data);
|
||||
}
|
||||
|
||||
on(eventName: PossibleEvents, callback: (data: any) => any) {
|
||||
callback({});
|
||||
console.info('transport stub on: ', eventName, callback);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
console.info('transport stub destroy');
|
||||
}
|
||||
}
|
5
src/communication-ts/transport-info.ts
Normal file
5
src/communication-ts/transport-info.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum TransportInfo {
|
||||
transportName = 'rbkmoney-checkout',
|
||||
parentHandshakeMessageName = 'rbkmoney-checkout-handshake',
|
||||
childHandshakeMessageName = 'rbkmoney-payframe-handshake'
|
||||
}
|
7
src/communication-ts/transport-message.ts
Normal file
7
src/communication-ts/transport-message.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { TransportInfo } from '.';
|
||||
|
||||
export class TransportMessage {
|
||||
data: any;
|
||||
name: string;
|
||||
transport: TransportInfo;
|
||||
}
|
9
src/communication-ts/transport.ts
Normal file
9
src/communication-ts/transport.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { PossibleEvents } from '.';
|
||||
|
||||
export interface Transport {
|
||||
emit(name: PossibleEvents, data?: any): void;
|
||||
|
||||
on(eventName: PossibleEvents, callback: (data: any) => any): void;
|
||||
|
||||
destroy(): void;
|
||||
}
|
46
src/utils/uri-serializer.ts
Normal file
46
src/utils/uri-serializer.ts
Normal file
@ -0,0 +1,46 @@
|
||||
export class UriSerializer {
|
||||
static serialize(params: any): string {
|
||||
let urlParams = '';
|
||||
for (const prop in params) {
|
||||
if (params.hasOwnProperty(prop)) {
|
||||
const value = params[prop];
|
||||
if ((typeof value === 'function') || (value === undefined) || (value === null)) {
|
||||
continue;
|
||||
}
|
||||
if (urlParams !== '') {
|
||||
urlParams += '&';
|
||||
}
|
||||
urlParams += `${prop}=${encodeURIComponent(value)}`;
|
||||
}
|
||||
}
|
||||
return urlParams;
|
||||
}
|
||||
|
||||
static deserialize(url: string): any {
|
||||
const split = (typeof url === 'string' && url !== '') && url.split('?');
|
||||
if (!split) {
|
||||
return {};
|
||||
}
|
||||
const params = split.length > 1 ? split[1] : split[0];
|
||||
const result = JSON.parse(`{"${decodeURI(params).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"')}"}`);
|
||||
for (const prop in result) {
|
||||
if (result.hasOwnProperty(prop)) {
|
||||
const value = decodeURIComponent(result[prop]);
|
||||
if (value === 'true') {
|
||||
result[prop] = true;
|
||||
} else if (value === 'false') {
|
||||
result[prop] = false;
|
||||
} else if (value === 'undefined') {
|
||||
result[prop] = undefined;
|
||||
} else if (value === 'null') {
|
||||
result[prop] = null;
|
||||
} else if (value !== '' && !isNaN(value as any)) {
|
||||
result[prop] = parseFloat(value);
|
||||
} else {
|
||||
result[prop] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -8,7 +8,8 @@
|
||||
"target": "es5",
|
||||
"jsx": "react",
|
||||
"lib": ["es2015", "es2017", "dom"],
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
|
@ -5,6 +5,9 @@
|
||||
"member-access": [true, "no-public"],
|
||||
"trailing-comma": false,
|
||||
"ordered-imports": false,
|
||||
"no-console": [true, "log"]
|
||||
"no-console": [true, "log"],
|
||||
"max-line-length": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"interface-name": false
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user