FE-626: Added base logic. (#1)

This commit is contained in:
Ildar Galeev 2018-07-20 17:21:52 +03:00 committed by GitHub
parent d6946aa383
commit 1911820ad5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 293 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea/
/dist/
/node_modules/

2
.npmignore Normal file
View File

@ -0,0 +1,2 @@
node_modules/
/npm-debug.log

111
package-lock.json generated Normal file
View File

@ -0,0 +1,111 @@
{
"name": "cross-origin-communicator",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.11"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"dev": true,
"requires": {
"glob": "7.1.2"
}
},
"typescript": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
}

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "cross-origin-communicator",
"version": "1.0.0",
"description": "",
"scripts": {
"prepublish": "npm run build",
"prebuild": "npm run clean",
"build": "tsc",
"clean": "rimraf dist"
},
"private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/rbkmoney/cross-origin-communicator.git"
},
"author": "Ildar Galeev",
"license": "",
"devDependencies": {
"rimraf": "^2.6.2",
"typescript": "^2.9.2"
}
}

4
src/constants.ts Normal file
View File

@ -0,0 +1,4 @@
export enum Constants {
initializerHandName = 'communicator-initializer-hand',
listenerHandName = 'communicator-listener-hand'
}

3
src/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './listen';
export * from './initialize';
export * from './transport';

34
src/initialize.ts Normal file
View File

@ -0,0 +1,34 @@
import { RealTransport } from './real-transport';
import { Transport } from './transport';
import { log } from './log';
import { Constants } from './constants';
export const initialize = (target: Window, origin: string, transportName: string, isLog: boolean = false): Promise<Transport> => {
let interval: number;
return new Promise((resolve, reject) => {
const reply = (e: any) => {
if (e.data === Constants.listenerHandName) {
if (isLog) {
log('initializer receive listener hand');
}
window.clearInterval(interval);
window.removeEventListener('message', reply, false);
return resolve(new RealTransport(target, origin, transportName));
}
};
window.addEventListener('message', reply, false);
let attempt = 0;
const doSend = () => {
attempt++;
target.postMessage(Constants.initializerHandName, origin);
if (isLog) {
log('initializer send handshake attempt');
}
if (attempt === 10) {
clearInterval(interval);
return reject('communicator handshake failed');
}
};
interval = window.setInterval(doSend, 500);
});
};

40
src/listen.ts Normal file
View File

@ -0,0 +1,40 @@
import { RealTransport } from './real-transport';
import { Transport } from './transport';
import { log } from './log';
import { Constants } from './constants';
const timeout = (ms: number): Promise<Transport> =>
new Promise((resolve, reject) => {
const id = setTimeout(() => {
clearTimeout(id);
reject('listener handshake timed out in ' + ms + 'ms.');
}, ms);
});
const handleHandshake = (transportName: string, isLog: boolean = false): Promise<Transport> =>
new Promise((resolve) => {
const shake = (e: MessageEvent) => {
if (e && e.data === Constants.initializerHandName) {
const target = e.source;
target.postMessage(Constants.listenerHandName, e.origin);
if (isLog) {
log('listener receive initializer hand');
}
window.removeEventListener('message', shake, false);
return resolve(new RealTransport(target, e.origin, transportName));
}
};
window.addEventListener('message', shake, false);
});
const handleHandshakeWithTimeout = (transportName: string, ms: number, isLog: boolean = false): Promise<Transport> =>
Promise.race<Transport>([
timeout(ms),
handleHandshake(transportName, isLog)
]);
export const listen = (transportName: string, ms: number = null, isLog: boolean = false): Promise<Transport> => {
return ms === null || ms === undefined
? handleHandshake(transportName, isLog)
: handleHandshakeWithTimeout(transportName, ms, isLog);
};

3
src/log.ts Normal file
View File

@ -0,0 +1,3 @@
const logPrefix = '[communicator]';
export const log = (message: string) => console.info(`${logPrefix} ${message}`);

50
src/real-transport.ts Normal file
View File

@ -0,0 +1,50 @@
import { Transport } from './transport';
const parse = (data: any): any => {
let parsed;
try {
parsed = JSON.parse(data);
} catch (e) {} // tslint:disable-line:no-empty
return parsed;
};
const transportListener = (transportName: string, events: any, e: MessageEvent) => {
const parsed = parse(e.data);
if (parsed && (parsed.name in events)) {
if (parsed.transport === transportName) {
events[parsed.name].call(this, parsed.data);
}
}
};
export class RealTransport implements Transport {
private readonly events: any = {};
private readonly transportListener: any;
constructor(
private readonly target: Window,
private readonly origin: string,
private readonly transportName = 'default-communicator-transport'
) {
this.transportListener = transportListener.bind(this, this.transportName, this.events);
window.addEventListener('message', this.transportListener, false);
}
emit(eventName: string, data?: object): void {
const serialized = JSON.stringify({
data,
name: eventName,
transport: this.transportName
});
this.target.postMessage(serialized, this.origin);
}
on(eventName: string, callback: (data: object) => void): void {
this.events[eventName] = callback;
}
destroy(): void {
window.removeEventListener('message', this.transportListener, false);
}
}

7
src/transport.ts Normal file
View File

@ -0,0 +1,7 @@
export interface Transport {
emit(eventName: string, data?: object): void;
on(eventName: string, callback: (data: object) => void): void;
destroy(): void;
}

12
tsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"lib": ["es2015", "dom"]
}
}