dce-reactkit 3.12.1-beta-cross-server.2 → 3.12.3
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/.vscode/settings.json +3 -1
- package/README.md +0 -20
- package/dist/cjs/index.js +356 -406
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/components/Modal/ModalProps.d.ts +2 -0
- package/dist/cjs/types/components/TabBox.d.ts +1 -0
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +10 -10
- package/dist/cjs/types/helpers/getWordCount.d.ts +10 -0
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +13 -15
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +6 -0
- package/dist/cjs/types/index.d.ts +2 -3
- package/dist/cjs/types/server/initServer.d.ts +0 -11
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +1 -13
- package/dist/esm/index.js +357 -406
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/components/Modal/ModalProps.d.ts +2 -0
- package/dist/esm/types/components/TabBox.d.ts +1 -0
- package/dist/esm/types/helpers/genRouteHandler.d.ts +10 -10
- package/dist/esm/types/helpers/getWordCount.d.ts +10 -0
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +13 -15
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +6 -0
- package/dist/esm/types/index.d.ts +2 -3
- package/dist/esm/types/server/initServer.d.ts +0 -11
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +1 -13
- package/dist/index.d.ts +39 -62
- package/package.json +2 -5
- package/src/components/IntelliTable.tsx +1 -1
- package/src/components/ItemPicker/NestableItemList.tsx +1 -0
- package/src/components/ItemPicker/index.tsx +96 -0
- package/src/components/Modal/ModalProps.ts +4 -0
- package/src/components/Modal/index.tsx +59 -20
- package/src/components/TabBox.tsx +27 -9
- package/src/helpers/genRouteHandler.ts +95 -55
- package/src/helpers/getWordCount.ts +26 -0
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +41 -42
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +5 -3
- package/src/index.ts +2 -4
- package/src/server/initServer.ts +0 -18
- package/src/types/ReactKitErrorCode.tsx +1 -13
- package/dist/cjs/types/helpers/dataSigner.d.ts +0 -38
- package/dist/cjs/types/server/initCrossServerCredentialCollection.d.ts +0 -8
- package/dist/cjs/types/types/CrossServerCredential.d.ts +0 -11
- package/dist/esm/types/helpers/dataSigner.d.ts +0 -38
- package/dist/esm/types/server/initCrossServerCredentialCollection.d.ts +0 -8
- package/dist/esm/types/types/CrossServerCredential.d.ts +0 -11
- package/genEncodedSecret.ts +0 -28
- package/src/helpers/dataSigner.ts +0 -232
- package/src/server/initCrossServerCredentialCollection.ts +0 -16
- package/src/types/CrossServerCredential.ts +0 -16
|
@@ -14,6 +14,9 @@ import React, { useState, useEffect, useRef } from 'react';
|
|
|
14
14
|
import ReactDOM from 'react-dom';
|
|
15
15
|
|
|
16
16
|
// Import other components
|
|
17
|
+
import LoadingSpinner from '../LoadingSpinner';
|
|
18
|
+
|
|
19
|
+
// Import helpers
|
|
17
20
|
import waitMs from '../../helpers/waitMs';
|
|
18
21
|
|
|
19
22
|
// Import types
|
|
@@ -41,6 +44,7 @@ const BASE_Z_INDEX_ON_TOP = 2000000000;
|
|
|
41
44
|
|
|
42
45
|
// Constants
|
|
43
46
|
const MS_TO_ANIMATE = 200; // Animation duration
|
|
47
|
+
const MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR = 1000;
|
|
44
48
|
|
|
45
49
|
// Modal type to list of buttons
|
|
46
50
|
const modalTypeToModalButtonTypes: {
|
|
@@ -256,6 +260,8 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
256
260
|
dontAllowBackdropExit,
|
|
257
261
|
dontShowXButton,
|
|
258
262
|
onTopOfOtherModals,
|
|
263
|
+
isLoading,
|
|
264
|
+
isLoadingCancelable,
|
|
259
265
|
} = props;
|
|
260
266
|
|
|
261
267
|
// Determine if no header either
|
|
@@ -266,6 +272,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
266
272
|
// True if animation is in use
|
|
267
273
|
const [animatingIn, setAnimatingIn] = useState(true);
|
|
268
274
|
const [animatingPop, setAnimatingPop] = useState(false);
|
|
275
|
+
const [showModal, setShowModal] = useState(false);
|
|
269
276
|
|
|
270
277
|
/* -------------- Refs -------------- */
|
|
271
278
|
|
|
@@ -298,6 +305,15 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
298
305
|
if (mounted.current) {
|
|
299
306
|
setAnimatingIn(false);
|
|
300
307
|
}
|
|
308
|
+
if (isLoading) {
|
|
309
|
+
// wait before showing modal
|
|
310
|
+
await waitMs(MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR);
|
|
311
|
+
if (mounted.current) {
|
|
312
|
+
setShowModal(true);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
setShowModal(true);
|
|
316
|
+
}
|
|
301
317
|
})();
|
|
302
318
|
|
|
303
319
|
return () => {
|
|
@@ -400,6 +416,17 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
400
416
|
animationClass = 'Modal-animating-pop';
|
|
401
417
|
}
|
|
402
418
|
|
|
419
|
+
// default to show close button when not loading and not show close button when loading
|
|
420
|
+
// unless downShowXButton or isLoadingCancelable
|
|
421
|
+
const showCloseButton = (
|
|
422
|
+
// The modal must have an onClose function
|
|
423
|
+
onClose
|
|
424
|
+
// ...and the close button is allowed to be visible
|
|
425
|
+
&& !dontShowXButton
|
|
426
|
+
// ...and should not be shown if the modal is loading and not cancelable
|
|
427
|
+
&& !(isLoading && !isLoadingCancelable)
|
|
428
|
+
);
|
|
429
|
+
|
|
403
430
|
// Render the modal
|
|
404
431
|
const contentToRender = (
|
|
405
432
|
<div
|
|
@@ -436,6 +463,8 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
436
463
|
handleClose(ModalButtonType.Cancel);
|
|
437
464
|
}}
|
|
438
465
|
/>
|
|
466
|
+
|
|
467
|
+
{showModal && (
|
|
439
468
|
<div
|
|
440
469
|
className={`modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`}
|
|
441
470
|
style={{
|
|
@@ -498,7 +527,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
498
527
|
{title}
|
|
499
528
|
</h5>
|
|
500
529
|
|
|
501
|
-
{
|
|
530
|
+
{showCloseButton && (
|
|
502
531
|
<button
|
|
503
532
|
type="button"
|
|
504
533
|
className="Modal-x-button btn-close"
|
|
@@ -518,25 +547,34 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
518
547
|
)}
|
|
519
548
|
</div>
|
|
520
549
|
)}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
550
|
+
|
|
551
|
+
<div
|
|
552
|
+
className="modal-body"
|
|
553
|
+
style={{
|
|
554
|
+
color: (
|
|
555
|
+
isDarkModeOn()
|
|
556
|
+
? 'white'
|
|
557
|
+
: undefined
|
|
558
|
+
),
|
|
559
|
+
backgroundColor: (
|
|
560
|
+
isDarkModeOn()
|
|
561
|
+
? '#444'
|
|
562
|
+
: undefined
|
|
563
|
+
),
|
|
564
|
+
}}
|
|
565
|
+
>
|
|
566
|
+
{
|
|
567
|
+
isLoading
|
|
568
|
+
? (
|
|
569
|
+
<>
|
|
570
|
+
<LoadingSpinner />
|
|
571
|
+
<span className="sr-only">Content loading</span>
|
|
572
|
+
</>
|
|
573
|
+
)
|
|
574
|
+
: children
|
|
575
|
+
}
|
|
576
|
+
</div>
|
|
577
|
+
|
|
540
578
|
{footer && (
|
|
541
579
|
<div
|
|
542
580
|
className="modal-footer pt-1 pb-1"
|
|
@@ -563,6 +601,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
563
601
|
)}
|
|
564
602
|
</div>
|
|
565
603
|
</div>
|
|
604
|
+
)}
|
|
566
605
|
</div>
|
|
567
606
|
);
|
|
568
607
|
|
|
@@ -15,6 +15,8 @@ type Props = {
|
|
|
15
15
|
title: React.ReactNode,
|
|
16
16
|
// Children/contents inside the box
|
|
17
17
|
children: React.ReactNode,
|
|
18
|
+
// Children to display on the top right of the tab box
|
|
19
|
+
topRightChildren?: React.ReactNode,
|
|
18
20
|
// If true, don't add margin below the tab box
|
|
19
21
|
noBottomMargin?: boolean,
|
|
20
22
|
// If true, don't add padding to bottom of tab box
|
|
@@ -49,7 +51,9 @@ const style = `
|
|
|
49
51
|
|
|
50
52
|
/* Container for Title */
|
|
51
53
|
.TabBox-title-container {
|
|
52
|
-
|
|
54
|
+
display: flex;
|
|
55
|
+
justify-content: space-between;
|
|
56
|
+
align-items: center;
|
|
53
57
|
position: relative;
|
|
54
58
|
left: 0;
|
|
55
59
|
text-align: left;
|
|
@@ -82,6 +86,19 @@ const style = `
|
|
|
82
86
|
background: #fdfdfd;
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
.TabBox-title-right-container {
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: row;
|
|
92
|
+
align-items: bottom;
|
|
93
|
+
height: 2.4rem;
|
|
94
|
+
overflow: visible;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.TabBox-title-right-contents {
|
|
98
|
+
margin-right: 0.5rem;
|
|
99
|
+
margin-bottom: 0.2rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
/* Make the TabBox's Children Appear Above Title if Overlap Occurs */
|
|
86
103
|
.TabBox-children {
|
|
87
104
|
position: relative;
|
|
@@ -98,11 +115,10 @@ const TabBox: React.FC<Props> = (props) => {
|
|
|
98
115
|
/* -------------------------------- Setup ------------------------------- */
|
|
99
116
|
/*------------------------------------------------------------------------*/
|
|
100
117
|
|
|
101
|
-
/* -------------- Props ------------- */
|
|
102
|
-
|
|
103
118
|
const {
|
|
104
119
|
title,
|
|
105
120
|
children,
|
|
121
|
+
topRightChildren,
|
|
106
122
|
noBottomPadding,
|
|
107
123
|
noBottomMargin,
|
|
108
124
|
} = props;
|
|
@@ -111,21 +127,23 @@ const TabBox: React.FC<Props> = (props) => {
|
|
|
111
127
|
/* ------------------------------- Render ------------------------------- */
|
|
112
128
|
/*------------------------------------------------------------------------*/
|
|
113
129
|
|
|
114
|
-
/*----------------------------------------*/
|
|
115
|
-
/* --------------- Main UI -------------- */
|
|
116
|
-
/*----------------------------------------*/
|
|
117
|
-
|
|
118
|
-
// Full UI
|
|
119
130
|
return (
|
|
120
131
|
<div className={`TabBox-container ${noBottomMargin ? '' : 'mb-2'}`}>
|
|
121
132
|
{/* Style */}
|
|
122
133
|
<style>{style}</style>
|
|
123
134
|
|
|
124
|
-
{/* Title */}
|
|
135
|
+
{/* Title Row with Left and Right sections */}
|
|
125
136
|
<div className="TabBox-title-container">
|
|
126
137
|
<div className="TabBox-title">
|
|
127
138
|
{title}
|
|
128
139
|
</div>
|
|
140
|
+
{topRightChildren && (
|
|
141
|
+
<div className="TabBox-title-right-container">
|
|
142
|
+
<div className="TabBox-title-right-contents">
|
|
143
|
+
{topRightChildren}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
129
147
|
</div>
|
|
130
148
|
|
|
131
149
|
{/* Contents */}
|
|
@@ -17,7 +17,6 @@ import genErrorPage from '../html/genErrorPage';
|
|
|
17
17
|
import genInfoPage from '../html/genInfoPage';
|
|
18
18
|
import parseUserAgent from './parseUserAgent';
|
|
19
19
|
import getTimeInfoInET from './getTimeInfoInET';
|
|
20
|
-
import { parseSignedPack } from './dataSigner';
|
|
21
20
|
|
|
22
21
|
// Import shared types
|
|
23
22
|
import LogFunction from '../types/LogFunction';
|
|
@@ -30,7 +29,6 @@ import LogSourceSpecificInfo from '../types/Log/LogSourceSpecificInfo';
|
|
|
30
29
|
import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
|
|
31
30
|
import LogAction from '../types/LogAction';
|
|
32
31
|
import LogLevel from '../types/LogLevel';
|
|
33
|
-
import ErrorWithCode from '../errors/ErrorWithCode';
|
|
34
32
|
|
|
35
33
|
/**
|
|
36
34
|
* Generate an express API route handler
|
|
@@ -39,15 +37,14 @@ import ErrorWithCode from '../errors/ErrorWithCode';
|
|
|
39
37
|
* @param opts.paramTypes map containing the types for each parameter that is
|
|
40
38
|
* included in the request (map: param name => type)
|
|
41
39
|
* @param opts.handler function that processes the request
|
|
42
|
-
* @param [opts.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* If crossServerScope is defined, this is always true
|
|
40
|
+
* @param [opts.skipSessionCheck] if true, skip the session check (allow users
|
|
41
|
+
* to not be logged in and launched via LTI)
|
|
42
|
+
* @param [opts.allowedHosts] if included, only allow requests from these hosts
|
|
43
|
+
* (start a hostname with a "*" to only check the end of the hostname)
|
|
44
|
+
* you can include just one string instead of an array
|
|
45
|
+
* @param [opts.bannedHosts] if included, do not allow requests from these hosts
|
|
46
|
+
* (start a hostname with a "*" to only check the end of the hostname)
|
|
47
|
+
* you can include just one string instead of an array
|
|
51
48
|
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
52
49
|
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
53
50
|
* where the error message is prefixed with this string. For example,
|
|
@@ -110,67 +107,106 @@ const genRouteHandler = (
|
|
|
110
107
|
logServerEvent: LogFunction,
|
|
111
108
|
},
|
|
112
109
|
) => any,
|
|
113
|
-
crossServerScope?: string,
|
|
114
110
|
skipSessionCheck?: boolean,
|
|
111
|
+
allowedHosts?: string[] | string,
|
|
112
|
+
bannedHosts?: string[] | string,
|
|
115
113
|
unhandledErrorMessagePrefix?: string,
|
|
116
114
|
},
|
|
117
115
|
) => {
|
|
118
116
|
// Return a route handler
|
|
119
117
|
return async (req: any, res: any, next: () => void) => {
|
|
120
|
-
/*----------------------------------------*/
|
|
121
|
-
/* ------------- Preparation ------------ */
|
|
122
|
-
/*----------------------------------------*/
|
|
123
|
-
|
|
124
118
|
// Output params
|
|
125
119
|
const output: { [k in string]: any } = {};
|
|
126
120
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
crossServerScope = opts.crossServerScope ?? null;
|
|
131
|
-
}
|
|
121
|
+
/*----------------------------------------*/
|
|
122
|
+
/* ----------- Hostname Check ----------- */
|
|
123
|
+
/*----------------------------------------*/
|
|
132
124
|
|
|
133
|
-
//
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
||
|
|
125
|
+
// Get hostnames
|
|
126
|
+
const originURL = String(
|
|
127
|
+
req.get('origin')
|
|
128
|
+
|| req.headers.origin
|
|
129
|
+
|| req.headers.referer,
|
|
137
130
|
);
|
|
131
|
+
const originHostname = (
|
|
132
|
+
originURL
|
|
133
|
+
// Remove protocol
|
|
134
|
+
.replace(/(^\w+:|^)\/\//, '')
|
|
135
|
+
// Remove port
|
|
136
|
+
.replace(/:\d+$/, '')
|
|
137
|
+
);
|
|
138
|
+
const serverHostname = String(req.hostname);
|
|
139
|
+
|
|
140
|
+
// Check allowed
|
|
141
|
+
if (opts.allowedHosts) {
|
|
142
|
+
// Only accept requests from allowed hosts
|
|
143
|
+
const allowedArray = (
|
|
144
|
+
Array.isArray(opts.allowedHosts)
|
|
145
|
+
? opts.allowedHosts
|
|
146
|
+
: [opts.allowedHosts]
|
|
147
|
+
);
|
|
138
148
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
...req.query,
|
|
145
|
-
...req.params,
|
|
146
|
-
};
|
|
149
|
+
// Check if server is localhost
|
|
150
|
+
if (serverHostname === 'localhost') {
|
|
151
|
+
// Allow localhost
|
|
152
|
+
allowedArray.push('localhost');
|
|
153
|
+
}
|
|
147
154
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
// Check if current host is allowed
|
|
156
|
+
const allowed = allowedArray.some((allowedHost) => {
|
|
157
|
+
if (allowedHost.startsWith('*')) {
|
|
158
|
+
// Check end of hostname
|
|
159
|
+
return originHostname.endsWith(allowedHost.substring(1));
|
|
160
|
+
}
|
|
151
161
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
162
|
+
// Check full hostname
|
|
163
|
+
return originHostname.toLowerCase() === allowedHost.toLowerCase();
|
|
164
|
+
});
|
|
155
165
|
|
|
156
|
-
// If
|
|
157
|
-
if (!
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
// If not allowed, return error
|
|
167
|
+
if (!allowed) {
|
|
168
|
+
return handleError(
|
|
169
|
+
res,
|
|
170
|
+
{
|
|
171
|
+
message: 'You are not allowed to access this endpoint.',
|
|
172
|
+
code: ReactKitErrorCode.HostNotAllowed,
|
|
173
|
+
status: 403,
|
|
174
|
+
},
|
|
161
175
|
);
|
|
162
176
|
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check banned
|
|
180
|
+
if (opts.bannedHosts) {
|
|
181
|
+
// Do not allow requests from banned hosts
|
|
182
|
+
const bannedArray = (
|
|
183
|
+
Array.isArray(opts.bannedHosts)
|
|
184
|
+
? opts.bannedHosts
|
|
185
|
+
: [opts.bannedHosts]
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Check if current host is banned
|
|
189
|
+
const banned = bannedArray.some((bannedHost) => {
|
|
190
|
+
if (bannedHost.startsWith('*')) {
|
|
191
|
+
// Check end of hostname
|
|
192
|
+
return originHostname.endsWith(bannedHost.substring(1));
|
|
193
|
+
}
|
|
163
194
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
method: req.method ?? 'GET',
|
|
167
|
-
path: req.path,
|
|
168
|
-
scope: crossServerScope,
|
|
169
|
-
signedPack,
|
|
195
|
+
// Check full hostname
|
|
196
|
+
return originHostname.toLowerCase() === bannedHost.toLowerCase();
|
|
170
197
|
});
|
|
171
198
|
|
|
172
|
-
//
|
|
173
|
-
|
|
199
|
+
// If banned, return error
|
|
200
|
+
if (banned) {
|
|
201
|
+
return handleError(
|
|
202
|
+
res,
|
|
203
|
+
{
|
|
204
|
+
message: 'You are not allowed to access this endpoint.',
|
|
205
|
+
code: ReactKitErrorCode.HostBanned,
|
|
206
|
+
status: 403,
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
}
|
|
174
210
|
}
|
|
175
211
|
|
|
176
212
|
/*----------------------------------------*/
|
|
@@ -183,7 +219,11 @@ const genRouteHandler = (
|
|
|
183
219
|
const [name, type] = paramList[i];
|
|
184
220
|
|
|
185
221
|
// Find the value as a string
|
|
186
|
-
const value =
|
|
222
|
+
const value = (
|
|
223
|
+
req.params[name]
|
|
224
|
+
|| req.query[name]
|
|
225
|
+
|| req.body[name]
|
|
226
|
+
);
|
|
187
227
|
|
|
188
228
|
// Parse
|
|
189
229
|
if (type === ParamType.Boolean || type === ParamType.BooleanOptional) {
|
|
@@ -366,7 +406,7 @@ const genRouteHandler = (
|
|
|
366
406
|
// Not launched
|
|
367
407
|
(!launched || !launchInfo)
|
|
368
408
|
// Not skipping the session check
|
|
369
|
-
&& !skipSessionCheck
|
|
409
|
+
&& !opts.skipSessionCheck
|
|
370
410
|
) {
|
|
371
411
|
return handleError(
|
|
372
412
|
res,
|
|
@@ -397,7 +437,7 @@ const genRouteHandler = (
|
|
|
397
437
|
)
|
|
398
438
|
)
|
|
399
439
|
// Not skipping the session check
|
|
400
|
-
&& !skipSessionCheck
|
|
440
|
+
&& !opts.skipSessionCheck
|
|
401
441
|
) {
|
|
402
442
|
return handleError(
|
|
403
443
|
res,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const punctuationRegex = /[!@#$%^&*(),.?\/;:'"\-\[\]]/g;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get number of words in string
|
|
5
|
+
* @author Gardenia Liu
|
|
6
|
+
* @author Allison Zhang
|
|
7
|
+
* @author Gabe Abrams
|
|
8
|
+
* @param text the string to check
|
|
9
|
+
* @returns number of words in the string
|
|
10
|
+
*/
|
|
11
|
+
const getWordCount = (text: string): number => {
|
|
12
|
+
const trimmedTextWithoutPunctuation = (
|
|
13
|
+
text
|
|
14
|
+
// Remove leading and trailing whitespace
|
|
15
|
+
.trim()
|
|
16
|
+
// Remove punctuation
|
|
17
|
+
.replace(punctuationRegex, '')
|
|
18
|
+
);
|
|
19
|
+
if (trimmedTextWithoutPunctuation.length === 0) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return trimmedTextWithoutPunctuation.split(/\s+/g).length;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default getWordCount;
|
|
@@ -1,72 +1,71 @@
|
|
|
1
|
-
// Import
|
|
2
|
-
import
|
|
1
|
+
// Import custom error
|
|
2
|
+
import ErrorWithCode from '../../errors/ErrorWithCode';
|
|
3
3
|
|
|
4
4
|
// Import shared types
|
|
5
|
-
import ErrorWithCode from '../../errors/ErrorWithCode';
|
|
6
5
|
import ReactKitErrorCode from '../../types/ReactKitErrorCode';
|
|
6
|
+
|
|
7
|
+
// Import other helpers
|
|
7
8
|
import sendServerToServerRequest from './sendServerToServerRequest';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* Send a server-to-server request from this sever to another server that uses
|
|
12
|
+
* dce-reactkit [for server only]
|
|
11
13
|
* @author Gabe Abrams
|
|
12
14
|
* @param opts object containing all arguments
|
|
13
|
-
* @param opts.
|
|
14
|
-
* @param opts.path the path of the other server's endpoint
|
|
15
|
-
* @param opts.
|
|
16
|
-
* @param [opts.
|
|
17
|
-
*
|
|
18
|
-
* @
|
|
19
|
-
* credential secret
|
|
20
|
-
* @param [opts.params={}] query/body parameters to include
|
|
21
|
-
* @param [opts.responseType=JSON] the response type from the other server
|
|
15
|
+
* @param opts.host - the host of the other server
|
|
16
|
+
* @param opts.path - the path of the other server's endpoint
|
|
17
|
+
* @param [opts.method=GET] - the method of the endpoint
|
|
18
|
+
* @param [opts.params] - query/body parameters to include
|
|
19
|
+
* @param [opts.headers] - headers to include
|
|
20
|
+
* @returns response from server
|
|
22
21
|
*/
|
|
23
22
|
const visitEndpointOnAnotherServer = async (
|
|
24
23
|
opts: {
|
|
25
|
-
method: 'GET' | 'POST' | 'DELETE' | 'PUT',
|
|
26
|
-
path: string,
|
|
27
24
|
host: string,
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
path: string,
|
|
26
|
+
method?: ('GET' | 'POST' | 'DELETE' | 'PUT'),
|
|
30
27
|
params?: { [key in string]: any },
|
|
31
|
-
|
|
28
|
+
headers?: { [k in string]: any },
|
|
32
29
|
},
|
|
33
30
|
): Promise<any> => {
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
// Remove properties with undefined values
|
|
32
|
+
let params: { [key in string]: any } | undefined;
|
|
33
|
+
if (opts.params) {
|
|
34
|
+
params = Object.fromEntries(
|
|
35
|
+
Object
|
|
36
|
+
.entries(opts.params)
|
|
37
|
+
.filter(([, value]) => {
|
|
38
|
+
return value !== undefined;
|
|
39
|
+
}),
|
|
43
40
|
);
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
// Automatically JSONify arrays and objects
|
|
44
|
+
if (params) {
|
|
45
|
+
params = Object.fromEntries(
|
|
46
|
+
Object
|
|
47
|
+
.entries(params)
|
|
48
|
+
.map(([key, value]) => {
|
|
49
|
+
if (Array.isArray(value) || typeof value === 'object') {
|
|
50
|
+
return [key, JSON.stringify(value)];
|
|
51
|
+
}
|
|
52
|
+
return [key, value];
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
54
56
|
|
|
55
57
|
// Send the request
|
|
56
58
|
const response = await sendServerToServerRequest({
|
|
57
|
-
path: opts.path,
|
|
58
59
|
host: opts.host,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
responseType: opts.responseType,
|
|
60
|
+
path: opts.path,
|
|
61
|
+
method: opts.method ?? 'GET',
|
|
62
|
+
params,
|
|
64
63
|
});
|
|
65
64
|
|
|
66
65
|
// Check for failure
|
|
67
66
|
if (!response || !response.body) {
|
|
68
67
|
throw new ErrorWithCode(
|
|
69
|
-
'We didn\'t get a response from the
|
|
68
|
+
'We didn\'t get a response from the server. Please check your internet connection.',
|
|
70
69
|
ReactKitErrorCode.NoResponse,
|
|
71
70
|
);
|
|
72
71
|
}
|
|
@@ -15,6 +15,9 @@ import ErrorWithCode from '../../errors/ErrorWithCode';
|
|
|
15
15
|
* @param [opts.host] host to send request to
|
|
16
16
|
* @param [opts.method=GET] http method to use
|
|
17
17
|
* @param [opts.params] body/data to include in the request
|
|
18
|
+
* @param [opts.headers] headers to include in the request
|
|
19
|
+
* @param [opts.sendCrossDomainCredentials=true if in development mode] if true,
|
|
20
|
+
* send cross-domain credentials even if not in dev mode
|
|
18
21
|
* @param [opts.responseType=JSON] expected response type
|
|
19
22
|
* @returns { body, status, headers } on success
|
|
20
23
|
*/
|
|
@@ -24,6 +27,7 @@ const sendServerToServerRequest = async (
|
|
|
24
27
|
host?: string,
|
|
25
28
|
method?: ('GET' | 'POST' | 'PUT' | 'DELETE'),
|
|
26
29
|
params?: { [k in string]: any },
|
|
30
|
+
headers?: { [k in string]: any },
|
|
27
31
|
responseType?: 'Text' | 'JSON',
|
|
28
32
|
},
|
|
29
33
|
): Promise<{
|
|
@@ -66,9 +70,7 @@ const sendServerToServerRequest = async (
|
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
// Update headers
|
|
69
|
-
const headers
|
|
70
|
-
[k: string]: any,
|
|
71
|
-
} = {};
|
|
73
|
+
const headers = opts.headers || {};
|
|
72
74
|
let data: string | null | { [k: string]: any } | undefined = null;
|
|
73
75
|
if (!headers['Content-Type']) {
|
|
74
76
|
// Form encoded
|
package/src/index.ts
CHANGED
|
@@ -70,7 +70,6 @@ import onlyKeepLetters from './helpers/onlyKeepLetters';
|
|
|
70
70
|
import parallelLimit from './helpers/parallelLimit';
|
|
71
71
|
import logClientEvent, { setClientEventMetadataPopulator } from './helpers/logClientEvent';
|
|
72
72
|
import initLogCollection from './server/initLogCollection';
|
|
73
|
-
import initCrossServerCredentialCollection from './server/initCrossServerCredentialCollection';
|
|
74
73
|
import getMonthName from './helpers/getMonthName';
|
|
75
74
|
import genCSV from './helpers/genCSV';
|
|
76
75
|
import canReviewLogs from './helpers/canReviewLogs';
|
|
@@ -96,6 +95,7 @@ import someAsync from './helpers/asyncArrayFunctions/someAsync';
|
|
|
96
95
|
import capitalize from './helpers/capitalize';
|
|
97
96
|
import shuffleArray from './helpers/shuffleArray';
|
|
98
97
|
import visitEndpointOnAnotherServer from './helpers/visitEndpointOnAnotherServer';
|
|
98
|
+
import getWordCount from './helpers/getWordCount';
|
|
99
99
|
|
|
100
100
|
// Import types
|
|
101
101
|
import ModalButtonType from './types/ModalButtonType';
|
|
@@ -114,7 +114,6 @@ import LogMetadataType from './types/LogMetadataType';
|
|
|
114
114
|
import LogFunction from './types/LogFunction';
|
|
115
115
|
import IntelliTableColumn from './types/IntelliTableColumn';
|
|
116
116
|
import DropdownItemType from './types/DropdownItemType';
|
|
117
|
-
import CrossServerCredential from './types/CrossServerCredential';
|
|
118
117
|
|
|
119
118
|
// Component-specific-types
|
|
120
119
|
import PickableItem from './components/ItemPicker/types/PickableItem';
|
|
@@ -203,6 +202,7 @@ export {
|
|
|
203
202
|
someAsync,
|
|
204
203
|
capitalize,
|
|
205
204
|
shuffleArray,
|
|
205
|
+
getWordCount,
|
|
206
206
|
// Client helpers
|
|
207
207
|
initClient,
|
|
208
208
|
visitServerEndpoint,
|
|
@@ -218,7 +218,6 @@ export {
|
|
|
218
218
|
handleError,
|
|
219
219
|
handleSuccess,
|
|
220
220
|
initLogCollection,
|
|
221
|
-
initCrossServerCredentialCollection,
|
|
222
221
|
addDBEditorEndpoints,
|
|
223
222
|
visitEndpointOnAnotherServer,
|
|
224
223
|
// Types
|
|
@@ -237,7 +236,6 @@ export {
|
|
|
237
236
|
LogFunction,
|
|
238
237
|
IntelliTableColumn,
|
|
239
238
|
DropdownItemType,
|
|
240
|
-
CrossServerCredential,
|
|
241
239
|
// Component-specific-types
|
|
242
240
|
PickableItem,
|
|
243
241
|
DBEntry,
|