@carbonorm/carbonreact 3.3.14 → 3.4.1
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.
- package/compileValidSQL.cjs +3 -1
- package/compileValidSQL.tsx +15 -8
- package/dist/CarbonReact.d.ts +4 -1
- package/dist/api/hoc/useEffectOnce.d.ts +1 -0
- package/dist/components/Alert/Alert.d.ts +1 -1
- package/dist/components/WebSocket/CarbonWebSocket.d.ts +9 -0
- package/dist/index.cjs.css +45 -40
- package/dist/index.cjs.css.map +1 -1
- package/dist/index.cjs.js +244 -68
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.css +45 -40
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +242 -70
- package/dist/index.esm.js.map +1 -1
- package/package.json +7 -4
- package/src/CarbonReact.tsx +15 -2
- package/src/api/hoc/useEffectOnce.tsx +33 -0
- package/src/components/Alert/Alert.tsx +11 -12
- package/src/components/Errors/BackendThrowable.tsx +25 -1
- package/src/components/WebSocket/CarbonWebSocket.tsx +181 -0
- package/src/index.ts +3 -0
- package/src/variables/bootstrap.module.css +45 -40
- package/src/variables/bootstrap.module.css.map +1 -1
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carbonorm/carbonreact",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"browser": "dist/index.umd.js",
|
|
5
5
|
"module": "dist/index.esm.js",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@carbonorm/carbonnode": "^1.
|
|
10
|
+
"@carbonorm/carbonnode": "^1.2.4",
|
|
11
11
|
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
|
12
12
|
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
|
13
13
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
|
@@ -70,16 +70,19 @@
|
|
|
70
70
|
"scripts": {
|
|
71
71
|
"prepublishOnly": "npm run build",
|
|
72
72
|
"c6": "wget https://raw.githubusercontent.com/RichardTMiles/CarbonPHP/lts/view/C6.tsx -O src/variables/C6.tsx",
|
|
73
|
-
"build": "rm -rf ./dist/ && npm run build:css && npm run build:index && rollup -c &&
|
|
73
|
+
"build": "rm -rf ./dist/ && npm run build:css && npm run build:index && rollup -c && npm run build:compileValidSQL",
|
|
74
74
|
"dev": "rollup -c -w",
|
|
75
75
|
"test": "node test/test.js",
|
|
76
76
|
"pretest": "npm run build",
|
|
77
77
|
"build:index": "npx barrelsby --directory ./src --delete --exclude '(jestHoc|\\.test|\\.d).(js|tsx?)$' --exportDefault --verbose --mode top",
|
|
78
78
|
"build:css": "sass ./src:./src && typed-scss-modules 'src/**/*.?(s)css' --nameFormat all --exportType default src",
|
|
79
|
-
"build:compileValidSQL": "
|
|
79
|
+
"build:compileValidSQL": "tsc compileValidSQL.tsx && mv compileValidSQL.js compileValidSQL.cjs"
|
|
80
80
|
},
|
|
81
81
|
"files": [
|
|
82
82
|
"compileValidSQL.tsx",
|
|
83
|
+
"compileValidSQL.cjs",
|
|
84
|
+
"generateRestBindings.tsx",
|
|
85
|
+
"generateRestBindings.cjs",
|
|
83
86
|
"dist",
|
|
84
87
|
"src"
|
|
85
88
|
]
|
package/src/CarbonReact.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import 'react-toastify/dist/ReactToastify.min.css';
|
|
|
9
9
|
import BackendThrowable from 'components/Errors/BackendThrowable';
|
|
10
10
|
import Nest from 'components/Nest/Nest';
|
|
11
11
|
import {initialRestfulObjectsState, iRestfulObjectArrayTypes} from "variables/C6";
|
|
12
|
+
import CarbonWebSocket from "./components/WebSocket/CarbonWebSocket";
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
|
|
@@ -33,6 +34,16 @@ export const initialCarbonReactState: iCarbonReactState & iRestfulObjectArrayTyp
|
|
|
33
34
|
...initialRestfulObjectsState,
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
// @link https://stackoverflow.com/questions/3710204/how-to-check-if-a-string-is-a-valid-json-string
|
|
38
|
+
export function isJsonString(str) {
|
|
39
|
+
try {
|
|
40
|
+
JSON.parse(str);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
const CarbonReact= class <P = {}, S = {}> extends Component<{
|
|
37
48
|
children?: ReactNode | ReactNode[],
|
|
38
49
|
} & P, S & iCarbonReactState> {
|
|
@@ -42,6 +53,9 @@ const CarbonReact= class <P = {}, S = {}> extends Component<{
|
|
|
42
53
|
} & any, any & iCarbonReactState>;
|
|
43
54
|
|
|
44
55
|
static lastLocation = window.location.pathname;
|
|
56
|
+
static websocketUrl = (window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host + ':8888/ws';
|
|
57
|
+
static websocketTimeoutSeconds : number = 250;
|
|
58
|
+
static websocketHeartbeatSeconds : number = 250;
|
|
45
59
|
|
|
46
60
|
// @link https://github.com/welldone-software/why-did-you-render
|
|
47
61
|
// noinspection JSUnusedGlobalSymbols
|
|
@@ -71,8 +85,6 @@ const CarbonReact= class <P = {}, S = {}> extends Component<{
|
|
|
71
85
|
return CarbonReact.instance.state;
|
|
72
86
|
}
|
|
73
87
|
|
|
74
|
-
websocketTimeout = 5000;
|
|
75
|
-
|
|
76
88
|
shouldComponentUpdate(
|
|
77
89
|
nextProps: Readonly<any>,
|
|
78
90
|
nextState: Readonly<iCarbonReactState>,
|
|
@@ -117,6 +129,7 @@ const CarbonReact= class <P = {}, S = {}> extends Component<{
|
|
|
117
129
|
|
|
118
130
|
return <BrowserRouter>
|
|
119
131
|
<GlobalHistory/>
|
|
132
|
+
<CarbonWebSocket />
|
|
120
133
|
{this.props.children}
|
|
121
134
|
<ToastContainer/>
|
|
122
135
|
</BrowserRouter>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
// @link https://dev.to/ag-grid/react-18-avoiding-use-effect-getting-called-twice-4i9e
|
|
4
|
+
export const useEffectOnce = (effect: () => void | (() => void)) => {
|
|
5
|
+
const effectFn = useRef<() => void | (() => void)>(effect);
|
|
6
|
+
const destroyFn = useRef<void | (() => void)>();
|
|
7
|
+
const effectCalled = useRef(false);
|
|
8
|
+
const rendered = useRef(false);
|
|
9
|
+
const [, setVal] = useState<number>(0);
|
|
10
|
+
|
|
11
|
+
if (effectCalled.current) {
|
|
12
|
+
rendered.current = true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!effectCalled.current) {
|
|
17
|
+
destroyFn.current = effectFn.current();
|
|
18
|
+
effectCalled.current = true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setVal(val => val + 1);
|
|
22
|
+
|
|
23
|
+
return () => {
|
|
24
|
+
if (!rendered.current) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (destroyFn.current) {
|
|
29
|
+
destroyFn.current();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
}
|
|
@@ -18,16 +18,6 @@ export interface iAlertButtonOptions {
|
|
|
18
18
|
color: "default" | "primary" | "secondary" | "inherit" | "danger" | "info" | "success" | "warning" | undefined,
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function addAlert(props: iAlert) {
|
|
22
|
-
|
|
23
|
-
CarbonReact.instance.setState(previousState => ({
|
|
24
|
-
alertsWaiting: previousState.alertsWaiting.length === 0
|
|
25
|
-
? [props]
|
|
26
|
-
: [...previousState.alertsWaiting, props]
|
|
27
|
-
}))
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
21
|
export interface iAlert {
|
|
32
22
|
title: string,
|
|
33
23
|
text: string,
|
|
@@ -38,11 +28,20 @@ export interface iAlert {
|
|
|
38
28
|
then?: (value: string | undefined) => void,
|
|
39
29
|
timeout?: number,
|
|
40
30
|
footerText?: string,
|
|
41
|
-
|
|
42
31
|
intercept?: boolean,
|
|
43
32
|
backendThrowable?: { [key: string]: any },
|
|
44
33
|
}
|
|
45
34
|
|
|
35
|
+
export function addAlert(props: iAlert) {
|
|
36
|
+
|
|
37
|
+
CarbonReact.instance.setState(previousState => ({
|
|
38
|
+
alertsWaiting: previousState.alertsWaiting.length === 0
|
|
39
|
+
? [props]
|
|
40
|
+
: [...previousState.alertsWaiting, props]
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
46
45
|
export default function Alert() {
|
|
47
46
|
|
|
48
47
|
const {alertsWaiting, backendThrowable} = CarbonReact.instance.state
|
|
@@ -73,7 +72,7 @@ export default function Alert() {
|
|
|
73
72
|
title: "Oh no! An issue occurred!",
|
|
74
73
|
text: backendThrowable?.['DropInGaming\\PHP\\Errors\\DropException'] ?? 'An unknown issue occurred. Please try again.',
|
|
75
74
|
timeout: 0,
|
|
76
|
-
footerText: hideExpandInformation ? '' : 'These alert footer options are only shown
|
|
75
|
+
footerText: hideExpandInformation ? '' : 'These alert footer options are only shown in development environments. Click "Expand" to see more details.',
|
|
77
76
|
buttons: buttons,
|
|
78
77
|
// backendThrowable has its own custom component that can be expanded (called in the Bootstrap component)
|
|
79
78
|
// our then function will be called when the user clicks on the button providing the option to expand or close
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import styles from './style.module.scss';
|
|
2
|
-
|
|
2
|
+
import OutsideClickHandler from 'react-outside-click-handler';
|
|
3
3
|
import CarbonReact from "../../CarbonReact";
|
|
4
4
|
import {ReactElement} from "react";
|
|
5
5
|
|
|
@@ -13,7 +13,31 @@ export default () : ReactElement => {
|
|
|
13
13
|
|
|
14
14
|
return <div className={styles.maintenanceHero}>
|
|
15
15
|
<h1 className={styles.httpStatusCode}>{currentThrowable?.status || 500}</h1>
|
|
16
|
+
<OutsideClickHandler
|
|
17
|
+
onOutsideClick={() => bootstrap.setState(currentState => ({ backendThrowable: currentState.backendThrowable.slice(1) }))}>
|
|
18
|
+
<div className={styles.centeredContainer}>
|
|
19
|
+
{Object.keys(currentThrowable).map((key, index) => {
|
|
16
20
|
|
|
21
|
+
const valueIsString = typeof currentThrowable[key] === 'string';
|
|
22
|
+
|
|
23
|
+
const valueIsCode = 'THROWN NEAR' === key;
|
|
24
|
+
|
|
25
|
+
return <div key={index}>
|
|
26
|
+
<div className={styles.errorTextGeneral}> > <span className={styles.errorKeys}>{key}</span>:
|
|
27
|
+
{valueIsString
|
|
28
|
+
? (valueIsCode ? <div
|
|
29
|
+
style={{ backgroundColor: 'black', fontSize: 'xx-small' }}
|
|
30
|
+
dangerouslySetInnerHTML={{ __html: currentThrowable[key] }} /> :
|
|
31
|
+
<i className={styles.errorValues}>"{currentThrowable[key]}"</i>)
|
|
32
|
+
: ''}
|
|
33
|
+
</div>
|
|
34
|
+
{valueIsString
|
|
35
|
+
? ''
|
|
36
|
+
: <pre className={styles.errorPre}>{JSON.stringify(currentThrowable[key], undefined, 4)}</pre>}
|
|
37
|
+
</div>;
|
|
38
|
+
})}
|
|
39
|
+
</div>
|
|
40
|
+
</OutsideClickHandler>
|
|
17
41
|
</div>;
|
|
18
42
|
|
|
19
43
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import CarbonReact, {isJsonString} from "CarbonReact";
|
|
2
|
+
import {addAlert} from "../Alert/Alert";
|
|
3
|
+
import {useEffectOnce} from "../../api/hoc/useEffectOnce";
|
|
4
|
+
import {tC6Tables} from "@carbonorm/carbonnode";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @function connect
|
|
9
|
+
* This function establishes a connection with the websocket and also ensures constant reconnection if connection closes
|
|
10
|
+
**/
|
|
11
|
+
export function initiateWebsocket({TABLES = undefined}: {TABLES?: tC6Tables} = {}) {
|
|
12
|
+
|
|
13
|
+
const {websocket} = CarbonReact.instance.state;
|
|
14
|
+
|
|
15
|
+
if (!("WebSocket" in window)) {
|
|
16
|
+
|
|
17
|
+
// todo - store that this has been shown in the state
|
|
18
|
+
addAlert({
|
|
19
|
+
title: 'Browser does not support websockets, live updates will fail. You may need to refresh the page to see the newest content.',
|
|
20
|
+
text: 'Please use a modern browser.',
|
|
21
|
+
icon: 'warning',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (false === (undefined === websocket || null === websocket)) {
|
|
27
|
+
|
|
28
|
+
return;
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
let connectInterval;
|
|
34
|
+
|
|
35
|
+
const connection = new WebSocket(CarbonReact.websocketUrl);
|
|
36
|
+
|
|
37
|
+
console.log("Connecting websocket url", CarbonReact.websocketUrl);
|
|
38
|
+
|
|
39
|
+
CarbonReact.instance.setState({
|
|
40
|
+
websocket: connection
|
|
41
|
+
}, () => {
|
|
42
|
+
|
|
43
|
+
connection.onopen = () => {
|
|
44
|
+
|
|
45
|
+
console.log('WebSocket Client Connected To :: ' + CarbonReact.websocketUrl);
|
|
46
|
+
|
|
47
|
+
clearTimeout(connectInterval); // clear Interval on open of websocket connection
|
|
48
|
+
|
|
49
|
+
function heartbeat() {
|
|
50
|
+
|
|
51
|
+
const {websocket} = CarbonReact.instance.state;
|
|
52
|
+
|
|
53
|
+
if (!websocket) return;
|
|
54
|
+
|
|
55
|
+
if (websocket.readyState !== 1) return;
|
|
56
|
+
|
|
57
|
+
websocket.send("ping");
|
|
58
|
+
|
|
59
|
+
setTimeout(heartbeat, CarbonReact.websocketHeartbeatSeconds * 1000);
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
heartbeat();
|
|
64
|
+
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
connection.onmessage = (message: MessageEvent<string> ) => {
|
|
68
|
+
|
|
69
|
+
const parsedData = isJsonString(message?.data) ? JSON.parse(message?.data) : message?.data;
|
|
70
|
+
|
|
71
|
+
CarbonReact.instance.setState((prevState: Readonly<any>) => ({
|
|
72
|
+
websocketEvents: prevState.websocketEvents.concat(message),
|
|
73
|
+
websocketData: prevState.websocketData.concat(parsedData), // JSON.parse no good - base64?
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
console.log('going to impl TABLES', TABLES)
|
|
77
|
+
|
|
78
|
+
/*if (undefined !== TABLES) {
|
|
79
|
+
|
|
80
|
+
TABLES.
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
}*/
|
|
84
|
+
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
window.addEventListener("focus", () => initiateWebsocket());
|
|
88
|
+
|
|
89
|
+
// websocket onclose event listener
|
|
90
|
+
connection.addEventListener('close', event => {
|
|
91
|
+
|
|
92
|
+
let reason;
|
|
93
|
+
|
|
94
|
+
console.log(
|
|
95
|
+
`Socket is closed.`,
|
|
96
|
+
event.reason, event);
|
|
97
|
+
|
|
98
|
+
const retry = () => {
|
|
99
|
+
|
|
100
|
+
const retrySeconds = Math.min(5000, (CarbonReact.websocketTimeoutSeconds + CarbonReact.websocketTimeoutSeconds) * 1000)
|
|
101
|
+
|
|
102
|
+
CarbonReact.websocketTimeoutSeconds = retrySeconds;
|
|
103
|
+
|
|
104
|
+
console.log(`WebSocket reconnect will be attempted in ${retrySeconds} second(s).`)
|
|
105
|
+
|
|
106
|
+
connectInterval = setTimeout(() => initiateWebsocket(), retrySeconds);
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// See https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1
|
|
111
|
+
switch (event.code) {
|
|
112
|
+
case 1000:
|
|
113
|
+
reason = "Normal closure, meaning that the purpose for which the connection was established has been fulfilled.";
|
|
114
|
+
break;
|
|
115
|
+
case 1001:
|
|
116
|
+
retry(); //call check function after timeout
|
|
117
|
+
reason = "An endpoint is \"going away\", such as a server going down or a browser having navigated away from a page.";
|
|
118
|
+
break;
|
|
119
|
+
case 1002:
|
|
120
|
+
reason = "An endpoint is terminating the connection due to a protocol error";
|
|
121
|
+
break;
|
|
122
|
+
case 1003:
|
|
123
|
+
reason = "An endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it receives a binary message).";
|
|
124
|
+
break;
|
|
125
|
+
case 1004:
|
|
126
|
+
reason = "Reserved. The specific meaning might be defined in the future.";
|
|
127
|
+
break;
|
|
128
|
+
case 1005:
|
|
129
|
+
reason = "No status code was actually present.";
|
|
130
|
+
break;
|
|
131
|
+
case 1006:
|
|
132
|
+
retry();
|
|
133
|
+
reason = "The connection was closed abnormally, e.g., without sending or receiving a close control frame";
|
|
134
|
+
break;
|
|
135
|
+
case 1007:
|
|
136
|
+
reason = "An endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the message (e.g., non-UTF-8 [https://www.rfc-editor.org/rfc/rfc3629] data within a text message).";
|
|
137
|
+
break;
|
|
138
|
+
case 1008:
|
|
139
|
+
reason = "An endpoint is terminating the connection because it has received a message that \"violates its policy\". This reason is given either if there is no other suitable reason, or if there is a need to hide specific details about the policy.";
|
|
140
|
+
break;
|
|
141
|
+
case 1009:
|
|
142
|
+
reason = "An endpoint is terminating the connection because it has received a message that is too big for it to process.";
|
|
143
|
+
break;
|
|
144
|
+
case 1010:
|
|
145
|
+
reason = "An endpoint (client) is terminating the connection because it has expected the server to negotiate one or more extension, but the server didn't return them in the response message of the WebSocket handshake. <br /> Specifically, the extensions that are needed are: " + event.reason;
|
|
146
|
+
break;
|
|
147
|
+
case 1011:
|
|
148
|
+
reason = "A server is terminating the connection because it encountered an un expected condition that prevented it from fulfilling the request.";
|
|
149
|
+
break;
|
|
150
|
+
case 1015:
|
|
151
|
+
reason = "The connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified).";
|
|
152
|
+
break;
|
|
153
|
+
default:
|
|
154
|
+
reason = "Unknown reason";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log("The connection was closed for reason: " + reason);
|
|
158
|
+
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// websocket onerror event listener
|
|
162
|
+
connection.addEventListener('websocket error', (e: Event) => {
|
|
163
|
+
console.error("Socket encountered error: ", e, JSON.stringify(e));
|
|
164
|
+
connection.close();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default function () {
|
|
172
|
+
|
|
173
|
+
useEffectOnce(() => {
|
|
174
|
+
|
|
175
|
+
initiateWebsocket()
|
|
176
|
+
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return null
|
|
180
|
+
|
|
181
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
export { default as CarbonReact } from "./CarbonReact";
|
|
6
6
|
export * from "./CarbonReact";
|
|
7
|
+
export * from "./api/hoc/useEffectOnce";
|
|
7
8
|
export { default as Alert } from "./components/Alert/Alert";
|
|
8
9
|
export * from "./components/Alert/Alert";
|
|
9
10
|
export { default as AccessDenied } from "./components/Errors/AccessDenied";
|
|
@@ -20,6 +21,8 @@ export { default as Nest } from "./components/Nest/Nest";
|
|
|
20
21
|
export * from "./components/Nest/Nest";
|
|
21
22
|
export { default as Popup } from "./components/Popup/Popup";
|
|
22
23
|
export * from "./components/Popup/Popup";
|
|
24
|
+
export { default as CarbonWebSocket } from "./components/WebSocket/CarbonWebSocket";
|
|
25
|
+
export * from "./components/WebSocket/CarbonWebSocket";
|
|
23
26
|
export * from "./hoc/GlobalHistory";
|
|
24
27
|
export * from "./hoc/KeysMatching";
|
|
25
28
|
export { default as addValidSQL } from "./hoc/addValidSQL";
|