Commit 07ed0e31 authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza
Browse files

Module: GG-CE Client core module with models, services and utilities

parent 9baf4e0c
......@@ -13,7 +13,8 @@
"clean": "lerna run clean"
},
"workspaces": [
"packages/i18n"
"packages/i18n",
"packages/ui-core"
],
"devDependencies": {
"lerna": "^3.20.2"
......
{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}
],
"react"
],
"compact": false,
"retainLines": true,
"minified": false,
"inputSourceMap": false,
"sourceMaps": false,
"plugins": [
"transform-object-rest-spread",
"babel-plugin-syntax-dynamic-import",
[
"./react-loadable-custom/babel.js",
{
"importName": "utilities/CustomReactLoadable"
}
]
]
}
module.exports = {
preset: 'ts-jest',
snapshotSerializers: ['enzyme-to-json/serializer'],
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
testRegex: '/test/.*\\.test.(ts|tsx)$',
moduleDirectories: ['node_modules', 'src'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'#/(.*)': '<rootDir>/test/$1',
},
setupFiles: ['<rootDir>/test/setupTests.ts'],
collectCoverage: true,
collectCoverageFrom: ['src/**/*.{ts,tsx}']
};
{
"name": "@grin-global/ui-core",
"version": "0.0.1",
"license": "Apache-2.0",
"scripts": {
"clean": "rimraf target",
"test": "jest"
},
"dependencies": {
"axios": "^0.19.2",
"connected-react-router": "^6.6.1",
"cross-env": "^7.0.0",
"express": "^4.17.1",
"express-http-proxy": "^1.6.0",
"history": "^4.10.1",
"i18next": "^19.0.3",
"i18next-browser-languagedetector": "^4.0.1",
"i18next-express-middleware": "^1.9.1",
"i18next-sync-fs-backend": "^1.1.1",
"i18next-xhr-backend": "^3.2.2",
"immutability-helper": "^3.0.1",
"js-md5": "^0.7.3",
"path": "^0.12.7",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-i18next": "^11.3.1",
"react-loadable": "latest",
"react-redux": "^7.1.3",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"redux": "^4.0.5",
"redux-form": "^8.2.6",
"redux-saga": "^1.1.3",
"redux-thunk": "^2.3.0",
"url-template": "^2.0.8"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.4",
"@types/enzyme": "^3.10.5",
"@types/jest": "^25.1.2",
"@types/node": "13.5.3",
"@types/react": "16.9.19",
"@types/react-router": "5.1.4",
"@types/react-router-dom": "5.1.3",
"@types/redux-form": "8.2.0",
"@types/webpack-env": "^1.15.1",
"@typescript-eslint/eslint-plugin": "^2.17.0",
"@typescript-eslint/eslint-plugin-tslint": "^2.17.0",
"@typescript-eslint/parser": "^2.18.0",
"awesome-typescript-loader": "^5.2.1",
"babel-eslint": "^10.0.3",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-react-transform": "^3.0.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-typescript": "^7.0.0-alpha.19",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "^7.0.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"enzyme-to-json": "^3.4.4",
"es-cookie": "^1.3.2",
"es6-promise": "^4.2.8",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.10.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.20.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prefer-arrow": "^1.1.7",
"eslint-plugin-react": "^7.18.0",
"fast-glob": "^3.1.1",
"file-system": "^2.2.2",
"html-webpack-exclude-assets-plugin": "0.0.7",
"html-webpack-plugin": "^3.2.0",
"i18next-scanner": "^2.10.3",
"jest": "^25.1.0",
"jsdom": "^16.1.0",
"jsdom-global": "^3.0.2",
"lerna": "^3.20.2",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"react-hot-loader": "^4.12.18",
"react-test-renderer": "^16.12.0",
"redux-mock-store": "^1.5.4",
"rimraf": "^3.0.1",
"script-ext-html-webpack-plugin": "^2.1.4",
"terser-webpack-plugin": "^2.3.4",
"ts-jest": "^25.2.0",
"ts-node": "^8.6.2",
"tslint": "^6.0.0",
"tslint-loader": "^3.5.4",
"tslint-react": "^4.2.0",
"typescript": "^3.7.5",
"webpack": "^4.41.5",
"webpack-chunk-hash": "^0.6.0",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1",
"webpack-hot-middleware": "^2.25.0",
"webpack-manifest-plugin": "^2.2.0",
"webpack-merge": "^4.2.2"
}
}
'use strict';
// Copy of node_modules/react-loadable/lib/babel.js, MIT licensed.
// COPYRIGHT (c) 2017-present James Kyle <me@thejameskyle.com>
//
// Added our configurable source name
exports.__esModule = true;
exports.default = function(_ref) {
const t = _ref.types;
const template = _ref.template;
return {
visitor: {
ImportDeclaration: function ImportDeclaration(path) {
const source = path.node.source.value;
const opts = { importName: 'react-loadable', ...this.opts };
// use importName from opts, default to 'react-loader'
if (source !== opts.importName) {return;}
const defaultSpecifier = path.get('specifiers').find(function(specifier) {
return specifier.isImportDefaultSpecifier();
});
if (!defaultSpecifier) {return;}
const bindingName = defaultSpecifier.node.local.name;
const binding = path.scope.getBinding(bindingName);
binding.referencePaths.forEach(function(refPath) {
let callExpression = refPath.parentPath;
if (callExpression.isMemberExpression() && callExpression.node.computed === false && callExpression.get('property').isIdentifier({ name: 'Map' })) {
callExpression = callExpression.parentPath;
}
if (!callExpression.isCallExpression()) {return;}
const args = callExpression.get('arguments');
if (args.length !== 1) {throw callExpression.error;}
const options = args[0];
if (!options.isObjectExpression()) {return;}
const properties = options.get('properties');
const propertiesMap = {};
properties.forEach(function(property) {
const key = property.get('key');
propertiesMap[key.node.name] = property;
});
if (propertiesMap.webpack) {
// console.log('Babeling has webpack prop', propertiesMap);
return;
}
const loaderMethod = propertiesMap.loader.get('value');
const dynamicImports = [];
loaderMethod.traverse({
Import: function Import(path) {
dynamicImports.push(path.parentPath);
}
});
// console.log('Babeling dynamicImports', dynamicImports);
if (!dynamicImports.length) {return;}
propertiesMap.loader.insertAfter(t.objectProperty(t.identifier('webpack'), t.arrowFunctionExpression([], t.arrayExpression(dynamicImports.map(function(dynamicImport) {
return t.callExpression(t.memberExpression(t.identifier('require'), t.identifier('resolveWeak')), [dynamicImport.get('arguments')[0].node]);
})))));
propertiesMap.loader.insertAfter(t.objectProperty(t.identifier('modules'), t.arrayExpression(dynamicImports.map(function(dynamicImport) {
return dynamicImport.get('arguments')[0].node;
}))));
});
}
}
};
};
import { CONFIGURE_APPLICATION, SET_LANG } from '@grin-global/ui-core/constants/applicationConfig';
export const configure = (config: object) => ({
type: CONFIGURE_APPLICATION,
payload: config,
});
export const receiveLang = (lang: string) => ({
type: SET_LANG,
payload: lang,
});
import * as cookies from 'es-cookie';
import * as jwt from 'jsonwebtoken';
import { call, put, select, takeEvery } from 'redux-saga/effects';
// action
import navigateTo from '@grin-global/ui-core/action/navigation';
// constants
import {
LOGIN_APP, LOGIN_USER,
LOGIN_APP_SAGA, LOGIN_USER_SAGA, LOGOUT_USER_SAGA,
} from '@grin-global/ui-core/constants/login';
import { ROLE_CLIENT } from '@grin-global/ui-core/constants/userRoles';
// service
import { LoginService } from '@grin-global/ui-core/service/LoginService';
// utilities
import { clearCookies, saveCookies } from '@grin-global/ui-core/utilities';
import { log } from '@grin-global/ui-core/utilities/debug';
import { configureBackendApi } from '@grin-global/ui-core/utilities/requestUtils';
export const loginSagas = [
takeEvery(LOGIN_APP_SAGA, loginAppSaga),
takeEvery(LOGIN_USER_SAGA, loginUserSaga),
takeEvery(LOGOUT_USER_SAGA, logoutUserSaga),
];
// #loginUser
export const loginUserAction = (username, password, needRedirect = false) => ({
type: LOGIN_USER_SAGA,
payload: {
username,
password,
needRedirect,
},
});
function* loginUserSaga(action) {
const { username, password, needRedirect } = action.payload;
// log('Trying login', username);
try {
yield call(loginRequest, username, password);
if (needRedirect) {
yield call(navigateTo, '/');
}
window.location.reload();
return false;
} catch (e) {
return ({ error: e, errorDescription: e.message });
}
}
function* loginRequest(username, password) {
const userData = yield call(LoginService.login, username, password);
const apiUrl = yield select((state) => state.applicationConfig.apiUrl);
const tokenData = jwt.decode(userData.access_token);
saveCookies(
{ access_token: userData.access_token, ...userData, ...tokenData },
userData.exp * 1000 || new Date().getTime() + userData.expires_in * 1000,
apiUrl,
);
return put(loginUserSuccess({ ...userData, ...tokenData }));
}
export const loginUserSuccess = (payload) => ({
type: LOGIN_USER,
...payload,
});
// #logoutUser
export const logoutUserAction = () => ({
type: LOGOUT_USER_SAGA,
});
function* logoutUserSaga() {
const token = yield select((state) => state.login.access_token);
yield call(LoginService.logout, token);
clearCookies();
const appTokenData = yield call(loginAppAction);
const apiUrl = yield select((state) => state.applicationConfig.apiUrl);
saveCookies(appTokenData, appTokenData.exp * 1000 || new Date().getTime() + appTokenData.expires_in * 1000, apiUrl);
window.location.replace('/');
}
export function checkAccessTokens(dispatch, getState) {
const cookieToken: string = typeof window !== 'undefined' && cookies.get('access_token');
console.log('Application config: ', getState().applicationConfig);
const applicationLogin = () =>
LoginService.loginApp()
.then((data) => {
console.log('loginApp token', data);
saveCookies({
access_token: data.access_token,
authorities: [ROLE_CLIENT],
}, data.exp * 1000 || new Date().getTime() + data.expires_in * 1000, getState().applicationConfig.apiUrl);
return dispatch(loginApp(data));
})
.catch((error) => {
log('Something went wrong', error);
});
if (cookieToken) {
try {
const parsedTokenData = jwt.decode(cookieToken);
dispatch(loginUserSuccess({ access_token: cookieToken, ...parsedTokenData }));
return Promise.resolve();
} catch (e) {
console.log('Error while parsing token: ', e.message);
clearCookies();
return applicationLogin();
}
} else {
clearCookies();
return applicationLogin();
}
}
// #loginApp
const loginAppAction = () => (dispatch) => ({
type: LOGIN_APP_SAGA,
});
function* loginAppSaga() {
const clientId = yield select((state) => state.applicationConfig.clientId);
const appToken = yield call(LoginService.loginApp, clientId);
yield call(loginApp, appToken);
}
function* loginApp(d) {
// console.log('Login app', d);
configureBackendApi({ accessToken: d.access_token });
yield put({
type: LOGIN_APP,
authorities: [ROLE_CLIENT],
access_token: d.access_token,
...jwt.decode(d.access_token),
});
}
const refreshToken = () => (dispatch, getState) => {
const refreshToken = getState().login.refresh_token;
return LoginService.refreshAccessToken(refreshToken)
.then((accessToken) => {
// console.log('Refreshed access_token', accessToken);
dispatch(loginUserSuccess({ ...accessToken }));
// console.log('Setting access_token expire in ', timeout, 'ms');
saveCookies({ ...accessToken, ...jwt.decode(accessToken.access_token) }, accessToken.exp * 1000 || new Date().getTime() + accessToken.expires_in * 1000, getState().applicationConfig.apiUrl);
return accessToken;
})
.catch((err) => {
clearCookies();
// setTimeout(() => window.location.reload(), 20);
});
};
const refreshAuthTokenAuto = () => (dispatch, getState) => {
const timeout = getState().login.expires_in
? getState().login.expires_in * 1000
: getState().login.exp * 1000 - Date.now();
setTimeout(() =>
dispatch(refreshToken())
.then(() => dispatch(refreshAuthTokenAuto())),
timeout - 5 * 1000,
);
};
export {
loginAppAction, loginApp, refreshToken, refreshAuthTokenAuto,
};
import { push } from 'connected-react-router';
import { stringify } from 'query-string';
const IN_BROWSER = typeof window !== 'undefined';
export function navigateTo(path: string, query?: object) {
return (dispatch, getState) => {
if (! IN_BROWSER) {
console.log('Not navigating anywhere while on the server!');
return;
}
const location = getState().router.location.pathname;
const search = getState().router.location.search;
if (`${location}${search}` !== `${path}${query ? stringify(query) : ''}`) {
if (!query) {
dispatch(push(path ? path : ''));
} else {
dispatch(push(`${ path ? path : '' }?` + stringify(query)));
}
}
};
}
export default navigateTo;
import { takeEvery } from 'redux-saga/effects';
import { loginSagas } from '@grin-global/ui-core/action/login';
export const coreSagas = [
process.env.NODE_ENV === 'development' ? takeEvery((action) => /^api\//.test(action.type), logApi) : {},
...loginSagas,
];
function* logApi(action) {
console.log('Api call to ', action.type);
console.log('Params: ', action.params);
// const state = yield select();
// console.log('State after', state);
}
export const CONFIGURE_APPLICATION = 'CONFIGURE_APPLICATION';
export const SET_LANG = 'SET_LANG';
export const LOGIN_USER = 'LOGIN_USER';
export const LOGIN_APP = 'LOGIN_APP';
export const CHECK_TOKEN = 'CHECK_TOKEN';
export const VERIFY_GOOGLE_TOKEN = 'VERIFY_GOOGLE_TOKEN';
// saga
export const LOGIN_USER_SAGA = 'saga/login/LOGIN_USER';
export const LOGOUT_USER_SAGA = 'saga/login/LOGOUT_USER_SAGA';
export const LOGIN_APP_SAGA = 'saga/login/LOGIN_APP';
// form
export const USER_LOGIN_FORM = 'Form/Login/USER_LOGIN_FORM';
export const REGISTRATION_FORM = 'Form/Login/REGISTRATION_FORM';
export const FORGOT_PASSWORD_FORM = 'Form/user/FORGOT_PASSWORD';
export const RESET_PASSWORD_FORM = 'Form/user/RESET_PASSWORD';
export const CANCEL_RESET_PASSWORD_FORM = 'Form/user/CANCEL_RESET_PASSWORD';
export const VALIDATE_EMAIL_FORM = 'Form/user/VALIDATE_EMAIL_FORM';
export const SET_PAGE_TITLE = 'SET_PAGE_TITLE';
const ROLE_CLIENT = 'ROLE_CLIENT';
const ROLE_USER = 'ROLE_USER';
const ROLE_ADMINISTRATOR = 'ROLE_ADMINISTRATOR';
const ROLE_VETTEDUSER = 'ROLE_VETTEDUSER';
export { ROLE_CLIENT, ROLE_USER, ROLE_ADMINISTRATOR, ROLE_VETTEDUSER };
export class Permissions {
public create: boolean;
public read: boolean;
public write: boolean;
public delete: boolean;
public manage: boolean;
public isPublic: boolean;
public getClassname(): string {
return 'org.genesys.blocks.security.serialization.Permissions';
}
public grantNone() {
this.create = false;
this.read = false;
this.write = false;
this.delete = false;
this.manage = false;
return this;
}
public static grantNone(permissions) {
permissions.create = false;
permissions.read = false;
permissions.write = false;
permissions.delete = false;
permissions.manage = false;
return permissions;
}
}
export interface IUserPermissions {
_permissions?: Permissions;
}
/*
* Defined in Swagger as '#/definitions/AclClass'
*/
class AclClass {
public aclClass: string;
public id: number;
}
export default AclClass;
import AclSid from '@grin-global/ui-core/model/acl/AclSid';
/*
* Defined in Swagger as '#/definitions/AclEntry'
*/
class AclEntry {
public aceOrder: number;
public aclSid: AclSid;
public auditFailure: boolean;
public auditSuccess: boolean;
public granting: boolean;
public id: number;
public mask: number;
}
export default AclEntry;
import AclClass from '@grin-global/ui-core/model/acl/AclClass';
import AclSid from '@grin-global/ui-core/model/acl/AclSid';
import SidPermissions from '@grin-global/ui-core/model/acl/SidPermissions';