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
package/dist/cjs/index.js
CHANGED
|
@@ -7,8 +7,6 @@ var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
|
|
|
7
7
|
var reactFontawesome = require('@fortawesome/react-fontawesome');
|
|
8
8
|
var ReactDOM = require('react-dom');
|
|
9
9
|
var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
|
|
10
|
-
var crypto = require('crypto');
|
|
11
|
-
var jwt = require('jwt-simple');
|
|
12
10
|
var qs = require('qs');
|
|
13
11
|
|
|
14
12
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -34,7 +32,6 @@ function _interopNamespace(e) {
|
|
|
34
32
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
35
33
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
36
34
|
var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
|
|
37
|
-
var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
|
|
38
35
|
var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
|
|
39
36
|
|
|
40
37
|
/******************************************************************************
|
|
@@ -62,7 +59,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
62
59
|
});
|
|
63
60
|
}
|
|
64
61
|
|
|
65
|
-
// Highest error code =
|
|
62
|
+
// Highest error code = DRK18
|
|
66
63
|
/**
|
|
67
64
|
* List of error codes built into the react kit
|
|
68
65
|
* @author Gabe Abrams
|
|
@@ -88,18 +85,6 @@ var ReactKitErrorCode;
|
|
|
88
85
|
ReactKitErrorCode["NotConnected"] = "DRK14";
|
|
89
86
|
ReactKitErrorCode["SelfSigned"] = "DRK15";
|
|
90
87
|
ReactKitErrorCode["ResponseParseError"] = "DRK16";
|
|
91
|
-
ReactKitErrorCode["PackUnparseable"] = "DRK28";
|
|
92
|
-
ReactKitErrorCode["PackInvalidMethod"] = "DRK19";
|
|
93
|
-
ReactKitErrorCode["PackInvalidPath"] = "DRK20";
|
|
94
|
-
ReactKitErrorCode["PackInvalidCollection"] = "DRK21";
|
|
95
|
-
ReactKitErrorCode["PackInvalidCredential"] = "DRK23";
|
|
96
|
-
ReactKitErrorCode["PackInvalidScope"] = "DRK22";
|
|
97
|
-
ReactKitErrorCode["PackInvalidTimestamp"] = "DRK24";
|
|
98
|
-
ReactKitErrorCode["PackInvalidSignature"] = "DRK25";
|
|
99
|
-
ReactKitErrorCode["PackInvalidBody"] = "DRK26";
|
|
100
|
-
ReactKitErrorCode["CrossServerNoCredentialsToSignWith"] = "DRK27";
|
|
101
|
-
ReactKitErrorCode["CrossServerNoPack"] = "DRK29";
|
|
102
|
-
ReactKitErrorCode["CrossServerNoCredentialEncodingSalt"] = "DRK30";
|
|
103
88
|
})(ReactKitErrorCode || (ReactKitErrorCode = {}));
|
|
104
89
|
var ReactKitErrorCode$1 = ReactKitErrorCode;
|
|
105
90
|
|
|
@@ -231,6 +216,98 @@ const LogBuiltInMetadata = {
|
|
|
231
216
|
},
|
|
232
217
|
};
|
|
233
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Loading spinner/indicator
|
|
221
|
+
* @author Gabe Abrams
|
|
222
|
+
*/
|
|
223
|
+
/*------------------------------------------------------------------------*/
|
|
224
|
+
/* -------------------------------- Style ------------------------------- */
|
|
225
|
+
/*------------------------------------------------------------------------*/
|
|
226
|
+
const style$a = `
|
|
227
|
+
/* Container fades in */
|
|
228
|
+
.LoadingSpinner-container {
|
|
229
|
+
animation-name: LoadingSpinner-container-fade-in;
|
|
230
|
+
animation-duration: 0.3s;
|
|
231
|
+
animation-delay: 0.4s;
|
|
232
|
+
animation-fill-mode: both;
|
|
233
|
+
animation-timing-function: ease-out;
|
|
234
|
+
}
|
|
235
|
+
@keyframes LoadingSpinner-container-fade-in {
|
|
236
|
+
0% {
|
|
237
|
+
opacity: 0;
|
|
238
|
+
}
|
|
239
|
+
100% {
|
|
240
|
+
opacity: 1;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Blips */
|
|
245
|
+
.LoadingSpinner-blip-1,
|
|
246
|
+
.LoadingSpinner-blip-2,
|
|
247
|
+
.LoadingSpinner-blip-3,
|
|
248
|
+
.LoadingSpinner-blip-4 {
|
|
249
|
+
font-size: 1.8rem;
|
|
250
|
+
opacity: 0.6;
|
|
251
|
+
margin-top: 1rem;
|
|
252
|
+
margin-bottom: 1rem;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* First Blip */
|
|
256
|
+
.LoadingSpinner-blip-1 {
|
|
257
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* Second Blip */
|
|
261
|
+
.LoadingSpinner-blip-2 {
|
|
262
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
263
|
+
-webkit-animation-delay: 0.1s;
|
|
264
|
+
animation-delay: 0.1s;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Third Blip */
|
|
268
|
+
.LoadingSpinner-blip-3 {
|
|
269
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
270
|
+
animation-delay: 0.2s;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Fourth Blip */
|
|
274
|
+
.LoadingSpinner-blip-4 {
|
|
275
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
276
|
+
animation-delay: 0.3s;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Pop Animation for Each Blip */
|
|
280
|
+
@keyframes LoadingSpinner-pop-animation {
|
|
281
|
+
0% {
|
|
282
|
+
transform: scale(1.0);
|
|
283
|
+
}
|
|
284
|
+
10% {
|
|
285
|
+
transform: scale(1.5);
|
|
286
|
+
}
|
|
287
|
+
30% {
|
|
288
|
+
transform: scale(1.0);
|
|
289
|
+
}
|
|
290
|
+
100% {
|
|
291
|
+
transform: scale(1.0);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
`;
|
|
295
|
+
/*------------------------------------------------------------------------*/
|
|
296
|
+
/* ------------------------------ Component ----------------------------- */
|
|
297
|
+
/*------------------------------------------------------------------------*/
|
|
298
|
+
const LoadingSpinner = () => {
|
|
299
|
+
/*------------------------------------------------------------------------*/
|
|
300
|
+
/* ------------------------------- Render ------------------------------- */
|
|
301
|
+
/*------------------------------------------------------------------------*/
|
|
302
|
+
// Add all four blips to a container
|
|
303
|
+
return (React__default["default"].createElement("div", { className: "text-center LoadingSpinner LoadingSpinner-container" },
|
|
304
|
+
React__default["default"].createElement("style", null, style$a),
|
|
305
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-1 me-1" }),
|
|
306
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-2 me-1" }),
|
|
307
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-3 me-1" }),
|
|
308
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-4" })));
|
|
309
|
+
};
|
|
310
|
+
|
|
234
311
|
/**
|
|
235
312
|
* Wait for a certain number of ms
|
|
236
313
|
* @author Gabe Abrams
|
|
@@ -363,6 +440,7 @@ const BASE_Z_INDEX = 1000000000;
|
|
|
363
440
|
const BASE_Z_INDEX_ON_TOP = 2000000000;
|
|
364
441
|
// Constants
|
|
365
442
|
const MS_TO_ANIMATE = 200; // Animation duration
|
|
443
|
+
const MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR = 1000;
|
|
366
444
|
// Modal type to list of buttons
|
|
367
445
|
const modalTypeToModalButtonTypes = {
|
|
368
446
|
[ModalType$1.Okay]: [
|
|
@@ -476,7 +554,7 @@ const getNextUniqueId = () => {
|
|
|
476
554
|
/*------------------------------------------------------------------------*/
|
|
477
555
|
/* -------------------------------- Style ------------------------------- */
|
|
478
556
|
/*------------------------------------------------------------------------*/
|
|
479
|
-
const style$
|
|
557
|
+
const style$9 = `
|
|
480
558
|
.Modal-backdrop {
|
|
481
559
|
position: fixed;
|
|
482
560
|
top: 0;
|
|
@@ -548,13 +626,14 @@ const Modal = (props) => {
|
|
|
548
626
|
/*------------------------------------------------------------------------*/
|
|
549
627
|
var _a;
|
|
550
628
|
/* -------------- Props ------------- */
|
|
551
|
-
const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, largeTitle, children, onClose, dontAllowBackdropExit, dontShowXButton, onTopOfOtherModals, } = props;
|
|
629
|
+
const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, largeTitle, children, onClose, dontAllowBackdropExit, dontShowXButton, onTopOfOtherModals, isLoading, isLoadingCancelable, } = props;
|
|
552
630
|
// Determine if no header either
|
|
553
631
|
const noHeader = (!title && type === ModalType$1.NoButtons);
|
|
554
632
|
/* -------------- State ------------- */
|
|
555
633
|
// True if animation is in use
|
|
556
634
|
const [animatingIn, setAnimatingIn] = React.useState(true);
|
|
557
635
|
const [animatingPop, setAnimatingPop] = React.useState(false);
|
|
636
|
+
const [showModal, setShowModal] = React.useState(false);
|
|
558
637
|
/* -------------- Refs -------------- */
|
|
559
638
|
// Keep track of whether modal is still mounted
|
|
560
639
|
const mounted = React.useRef(false);
|
|
@@ -581,6 +660,16 @@ const Modal = (props) => {
|
|
|
581
660
|
if (mounted.current) {
|
|
582
661
|
setAnimatingIn(false);
|
|
583
662
|
}
|
|
663
|
+
if (isLoading) {
|
|
664
|
+
// wait before showing modal
|
|
665
|
+
yield waitMs(MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR);
|
|
666
|
+
if (mounted.current) {
|
|
667
|
+
setShowModal(true);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
setShowModal(true);
|
|
672
|
+
}
|
|
584
673
|
}))();
|
|
585
674
|
return () => {
|
|
586
675
|
mounted.current = false;
|
|
@@ -647,6 +736,15 @@ const Modal = (props) => {
|
|
|
647
736
|
else if (animatingPop) {
|
|
648
737
|
animationClass = 'Modal-animating-pop';
|
|
649
738
|
}
|
|
739
|
+
// default to show close button when not loading and not show close button when loading
|
|
740
|
+
// unless downShowXButton or isLoadingCancelable
|
|
741
|
+
const showCloseButton = (
|
|
742
|
+
// The modal must have an onClose function
|
|
743
|
+
onClose
|
|
744
|
+
// ...and the close button is allowed to be visible
|
|
745
|
+
&& !dontShowXButton
|
|
746
|
+
// ...and should not be shown if the modal is loading and not cancelable
|
|
747
|
+
&& !(isLoading && !isLoadingCancelable));
|
|
650
748
|
// Render the modal
|
|
651
749
|
const contentToRender = (React__default["default"].createElement("div", { className: "modal show", tabIndex: -1, style: {
|
|
652
750
|
zIndex: baseZIndex,
|
|
@@ -655,7 +753,7 @@ const Modal = (props) => {
|
|
|
655
753
|
left: 0,
|
|
656
754
|
right: 0,
|
|
657
755
|
} },
|
|
658
|
-
React__default["default"].createElement("style", null, style$
|
|
756
|
+
React__default["default"].createElement("style", null, style$9),
|
|
659
757
|
React__default["default"].createElement("div", { className: `Modal-backdrop ${backdropAnimationClass}`, style: {
|
|
660
758
|
zIndex: baseZIndex + 1,
|
|
661
759
|
}, onClick: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -673,7 +771,7 @@ const Modal = (props) => {
|
|
|
673
771
|
// Handle close
|
|
674
772
|
handleClose(ModalButtonType$1.Cancel);
|
|
675
773
|
}) }),
|
|
676
|
-
React__default["default"].createElement("div", { className: `modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`, style: {
|
|
774
|
+
showModal && (React__default["default"].createElement("div", { className: `modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`, style: {
|
|
677
775
|
zIndex: baseZIndex + 2,
|
|
678
776
|
// Override sizes for even larger for XL
|
|
679
777
|
width: (size === ModalSize$1.ExtraLarge
|
|
@@ -705,7 +803,7 @@ const Modal = (props) => {
|
|
|
705
803
|
? '1.6rem'
|
|
706
804
|
: undefined),
|
|
707
805
|
} }, title),
|
|
708
|
-
|
|
806
|
+
showCloseButton && (React__default["default"].createElement("button", { type: "button", className: "Modal-x-button btn-close", "aria-label": "Close", style: {
|
|
709
807
|
backgroundColor: (isDarkModeOn()
|
|
710
808
|
? 'white'
|
|
711
809
|
: undefined),
|
|
@@ -713,14 +811,18 @@ const Modal = (props) => {
|
|
|
713
811
|
// Handle close
|
|
714
812
|
handleClose(ModalButtonType$1.Cancel);
|
|
715
813
|
} })))),
|
|
716
|
-
|
|
814
|
+
React__default["default"].createElement("div", { className: "modal-body", style: {
|
|
717
815
|
color: (isDarkModeOn()
|
|
718
816
|
? 'white'
|
|
719
817
|
: undefined),
|
|
720
818
|
backgroundColor: (isDarkModeOn()
|
|
721
819
|
? '#444'
|
|
722
820
|
: undefined),
|
|
723
|
-
} },
|
|
821
|
+
} }, isLoading
|
|
822
|
+
? (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
823
|
+
React__default["default"].createElement(LoadingSpinner, null),
|
|
824
|
+
React__default["default"].createElement("span", { className: "sr-only" }, "Content loading")))
|
|
825
|
+
: children),
|
|
724
826
|
footer && (React__default["default"].createElement("div", { className: "modal-footer pt-1 pb-1", style: {
|
|
725
827
|
color: (isDarkModeOn()
|
|
726
828
|
? 'white'
|
|
@@ -731,7 +833,7 @@ const Modal = (props) => {
|
|
|
731
833
|
borderTop: (isDarkModeOn()
|
|
732
834
|
? '0.1rem solid gray'
|
|
733
835
|
: undefined),
|
|
734
|
-
} }, footer))))));
|
|
836
|
+
} }, footer)))))));
|
|
735
837
|
// Determine which portal to use
|
|
736
838
|
const portalNumber = id.current % NUM_MODAL_PORTALS;
|
|
737
839
|
// Render in a portal
|
|
@@ -1298,7 +1400,7 @@ const showSessionExpiredMessage = () => __awaiter(void 0, void 0, void 0, functi
|
|
|
1298
1400
|
/*------------------------------------------------------------------------*/
|
|
1299
1401
|
/* -------------------------------- Style ------------------------------- */
|
|
1300
1402
|
/*------------------------------------------------------------------------*/
|
|
1301
|
-
const style$
|
|
1403
|
+
const style$8 = `
|
|
1302
1404
|
.AppWrapper-leave-to-url-notice {
|
|
1303
1405
|
opacity: 0;
|
|
1304
1406
|
|
|
@@ -1512,105 +1614,13 @@ const AppWrapper = (props) => {
|
|
|
1512
1614
|
/* --------------- Main UI -------------- */
|
|
1513
1615
|
/*----------------------------------------*/
|
|
1514
1616
|
return (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
1515
|
-
React__default["default"].createElement("style", null, style$
|
|
1617
|
+
React__default["default"].createElement("style", null, style$8),
|
|
1516
1618
|
React__default["default"].createElement("style", null, tooltipStyle),
|
|
1517
1619
|
modal,
|
|
1518
1620
|
customModalPortals,
|
|
1519
1621
|
body));
|
|
1520
1622
|
};
|
|
1521
1623
|
|
|
1522
|
-
/**
|
|
1523
|
-
* Loading spinner/indicator
|
|
1524
|
-
* @author Gabe Abrams
|
|
1525
|
-
*/
|
|
1526
|
-
/*------------------------------------------------------------------------*/
|
|
1527
|
-
/* -------------------------------- Style ------------------------------- */
|
|
1528
|
-
/*------------------------------------------------------------------------*/
|
|
1529
|
-
const style$8 = `
|
|
1530
|
-
/* Container fades in */
|
|
1531
|
-
.LoadingSpinner-container {
|
|
1532
|
-
animation-name: LoadingSpinner-container-fade-in;
|
|
1533
|
-
animation-duration: 0.3s;
|
|
1534
|
-
animation-delay: 0.4s;
|
|
1535
|
-
animation-fill-mode: both;
|
|
1536
|
-
animation-timing-function: ease-out;
|
|
1537
|
-
}
|
|
1538
|
-
@keyframes LoadingSpinner-container-fade-in {
|
|
1539
|
-
0% {
|
|
1540
|
-
opacity: 0;
|
|
1541
|
-
}
|
|
1542
|
-
100% {
|
|
1543
|
-
opacity: 1;
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
/* Blips */
|
|
1548
|
-
.LoadingSpinner-blip-1,
|
|
1549
|
-
.LoadingSpinner-blip-2,
|
|
1550
|
-
.LoadingSpinner-blip-3,
|
|
1551
|
-
.LoadingSpinner-blip-4 {
|
|
1552
|
-
font-size: 1.8rem;
|
|
1553
|
-
opacity: 0.6;
|
|
1554
|
-
margin-top: 1rem;
|
|
1555
|
-
margin-bottom: 1rem;
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
/* First Blip */
|
|
1559
|
-
.LoadingSpinner-blip-1 {
|
|
1560
|
-
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
/* Second Blip */
|
|
1564
|
-
.LoadingSpinner-blip-2 {
|
|
1565
|
-
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1566
|
-
-webkit-animation-delay: 0.1s;
|
|
1567
|
-
animation-delay: 0.1s;
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
/* Third Blip */
|
|
1571
|
-
.LoadingSpinner-blip-3 {
|
|
1572
|
-
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1573
|
-
animation-delay: 0.2s;
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
/* Fourth Blip */
|
|
1577
|
-
.LoadingSpinner-blip-4 {
|
|
1578
|
-
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1579
|
-
animation-delay: 0.3s;
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
/* Pop Animation for Each Blip */
|
|
1583
|
-
@keyframes LoadingSpinner-pop-animation {
|
|
1584
|
-
0% {
|
|
1585
|
-
transform: scale(1.0);
|
|
1586
|
-
}
|
|
1587
|
-
10% {
|
|
1588
|
-
transform: scale(1.5);
|
|
1589
|
-
}
|
|
1590
|
-
30% {
|
|
1591
|
-
transform: scale(1.0);
|
|
1592
|
-
}
|
|
1593
|
-
100% {
|
|
1594
|
-
transform: scale(1.0);
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
`;
|
|
1598
|
-
/*------------------------------------------------------------------------*/
|
|
1599
|
-
/* ------------------------------ Component ----------------------------- */
|
|
1600
|
-
/*------------------------------------------------------------------------*/
|
|
1601
|
-
const LoadingSpinner = () => {
|
|
1602
|
-
/*------------------------------------------------------------------------*/
|
|
1603
|
-
/* ------------------------------- Render ------------------------------- */
|
|
1604
|
-
/*------------------------------------------------------------------------*/
|
|
1605
|
-
// Add all four blips to a container
|
|
1606
|
-
return (React__default["default"].createElement("div", { className: "text-center LoadingSpinner LoadingSpinner-container" },
|
|
1607
|
-
React__default["default"].createElement("style", null, style$8),
|
|
1608
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-1 me-1" }),
|
|
1609
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-2 me-1" }),
|
|
1610
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-3 me-1" }),
|
|
1611
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-4" })));
|
|
1612
|
-
};
|
|
1613
|
-
|
|
1614
1624
|
/**
|
|
1615
1625
|
* A box with a tab on the top that holds buttons and other content
|
|
1616
1626
|
* @author Gabe Abrams
|
|
@@ -1642,7 +1652,9 @@ const style$7 = `
|
|
|
1642
1652
|
|
|
1643
1653
|
/* Container for Title */
|
|
1644
1654
|
.TabBox-title-container {
|
|
1645
|
-
|
|
1655
|
+
display: flex;
|
|
1656
|
+
justify-content: space-between;
|
|
1657
|
+
align-items: center;
|
|
1646
1658
|
position: relative;
|
|
1647
1659
|
left: 0;
|
|
1648
1660
|
text-align: left;
|
|
@@ -1675,6 +1687,19 @@ const style$7 = `
|
|
|
1675
1687
|
background: #fdfdfd;
|
|
1676
1688
|
}
|
|
1677
1689
|
|
|
1690
|
+
.TabBox-title-right-container {
|
|
1691
|
+
display: flex;
|
|
1692
|
+
flex-direction: row;
|
|
1693
|
+
align-items: bottom;
|
|
1694
|
+
height: 2.4rem;
|
|
1695
|
+
overflow: visible;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
.TabBox-title-right-contents {
|
|
1699
|
+
margin-right: 0.5rem;
|
|
1700
|
+
margin-bottom: 0.2rem;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1678
1703
|
/* Make the TabBox's Children Appear Above Title if Overlap Occurs */
|
|
1679
1704
|
.TabBox-children {
|
|
1680
1705
|
position: relative;
|
|
@@ -1688,19 +1713,16 @@ const TabBox = (props) => {
|
|
|
1688
1713
|
/*------------------------------------------------------------------------*/
|
|
1689
1714
|
/* -------------------------------- Setup ------------------------------- */
|
|
1690
1715
|
/*------------------------------------------------------------------------*/
|
|
1691
|
-
|
|
1692
|
-
const { title, children, noBottomPadding, noBottomMargin, } = props;
|
|
1716
|
+
const { title, children, topRightChildren, noBottomPadding, noBottomMargin, } = props;
|
|
1693
1717
|
/*------------------------------------------------------------------------*/
|
|
1694
1718
|
/* ------------------------------- Render ------------------------------- */
|
|
1695
1719
|
/*------------------------------------------------------------------------*/
|
|
1696
|
-
/*----------------------------------------*/
|
|
1697
|
-
/* --------------- Main UI -------------- */
|
|
1698
|
-
/*----------------------------------------*/
|
|
1699
|
-
// Full UI
|
|
1700
1720
|
return (React__default["default"].createElement("div", { className: `TabBox-container ${noBottomMargin ? '' : 'mb-2'}` },
|
|
1701
1721
|
React__default["default"].createElement("style", null, style$7),
|
|
1702
1722
|
React__default["default"].createElement("div", { className: "TabBox-title-container" },
|
|
1703
|
-
React__default["default"].createElement("div", { className: "TabBox-title" }, title)
|
|
1723
|
+
React__default["default"].createElement("div", { className: "TabBox-title" }, title),
|
|
1724
|
+
topRightChildren && (React__default["default"].createElement("div", { className: "TabBox-title-right-container" },
|
|
1725
|
+
React__default["default"].createElement("div", { className: "TabBox-title-right-contents" }, topRightChildren)))),
|
|
1704
1726
|
React__default["default"].createElement("div", { className: `TabBox-box ps-2 pt-2 pe-2 ${noBottomPadding ? '' : 'pb-2'}` },
|
|
1705
1727
|
React__default["default"].createElement("div", { className: "TabBox-children" }, children))));
|
|
1706
1728
|
};
|
|
@@ -2687,7 +2709,7 @@ const NestableItemList = (props) => {
|
|
|
2687
2709
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
|
|
2688
2710
|
React__default["default"].createElement(CheckboxButton, { className: `NestableItemList-CheckboxButton-${item.id}`, text: item.name, checked: item.isGroup ? allChecked(item.children) : item.checked, dashed: item.isGroup ? !noneChecked(item.children) : false, onChanged: (checked) => {
|
|
2689
2711
|
onChanged(changeChecked(item.id, checked, items));
|
|
2690
|
-
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2712
|
+
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
|
|
2691
2713
|
(item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2692
2714
|
paddingLeft: '2.2rem',
|
|
2693
2715
|
} },
|
|
@@ -2710,17 +2732,62 @@ const ItemPicker = (props) => {
|
|
|
2710
2732
|
/*------------------------------------------------------------------------*/
|
|
2711
2733
|
/* -------------- Props ------------- */
|
|
2712
2734
|
// Destructure all props
|
|
2713
|
-
const { title, items, onChanged, noBottomMargin, } = props;
|
|
2735
|
+
const { title, items, onChanged, noBottomMargin, hideSelectAllOrNoneButtons, } = props;
|
|
2714
2736
|
/*------------------------------------------------------------------------*/
|
|
2715
2737
|
/* ------------------------- Component Functions ------------------------ */
|
|
2716
2738
|
/*------------------------------------------------------------------------*/
|
|
2739
|
+
/**
|
|
2740
|
+
* Updates the checked state of an item (including all children)
|
|
2741
|
+
* @author Yuen Ler Chow
|
|
2742
|
+
* @param item the item to update
|
|
2743
|
+
* @param checked the new checked state
|
|
2744
|
+
* @returns the updated item
|
|
2745
|
+
*/
|
|
2746
|
+
const updateItemChecked = (item, checked) => {
|
|
2747
|
+
if (item.isGroup) {
|
|
2748
|
+
return Object.assign(Object.assign({}, item), { children: item.children.map((child) => {
|
|
2749
|
+
return updateItemChecked(child, checked);
|
|
2750
|
+
}) });
|
|
2751
|
+
}
|
|
2752
|
+
return Object.assign(Object.assign({}, item), { checked });
|
|
2753
|
+
};
|
|
2754
|
+
/**
|
|
2755
|
+
* Selects all items in the list
|
|
2756
|
+
* @author Yuen Ler Chow
|
|
2757
|
+
*/
|
|
2758
|
+
const handleSelectAll = () => {
|
|
2759
|
+
const updatedItems = items.map((item) => {
|
|
2760
|
+
return updateItemChecked(item, true);
|
|
2761
|
+
});
|
|
2762
|
+
onChanged(updatedItems);
|
|
2763
|
+
};
|
|
2764
|
+
/**
|
|
2765
|
+
* Deselects all items in the list
|
|
2766
|
+
* @author Yuen Ler Chow
|
|
2767
|
+
*/
|
|
2768
|
+
const handleDeselectAll = () => {
|
|
2769
|
+
const updatedItems = items.map((item) => {
|
|
2770
|
+
return updateItemChecked(item, false);
|
|
2771
|
+
});
|
|
2772
|
+
onChanged(updatedItems);
|
|
2773
|
+
};
|
|
2717
2774
|
/*------------------------------------------------------------------------*/
|
|
2718
2775
|
/* ------------------------------- Render ------------------------------- */
|
|
2719
2776
|
/*------------------------------------------------------------------------*/
|
|
2720
2777
|
/*----------------------------------------*/
|
|
2721
2778
|
/* --------------- Main UI -------------- */
|
|
2722
2779
|
/*----------------------------------------*/
|
|
2723
|
-
|
|
2780
|
+
// Select all/none
|
|
2781
|
+
const selectAllOrNone = (!hideSelectAllOrNoneButtons
|
|
2782
|
+
? (React__default["default"].createElement("div", { className: "d-flex h-100 align-items-end flex-row" },
|
|
2783
|
+
React__default["default"].createElement("div", { className: "d-flex justify-content-end" },
|
|
2784
|
+
React__default["default"].createElement("div", { className: "me-2", style: { fontSize: '1.2rem' } }, "Select"),
|
|
2785
|
+
React__default["default"].createElement("div", { className: "btn-group", role: "group" },
|
|
2786
|
+
React__default["default"].createElement("button", { type: "button", style: { borderRight: '0.1rem solid white' }, "aria-label": "Select all contexts", className: "btn btn-secondary py-0", onClick: handleSelectAll }, "All"),
|
|
2787
|
+
React__default["default"].createElement("button", { type: "button", "aria-label": "Deselect all contexts", className: "btn btn-secondary py-0", onClick: handleDeselectAll }, "None")))))
|
|
2788
|
+
: undefined);
|
|
2789
|
+
// Main UI
|
|
2790
|
+
return (React__default["default"].createElement(TabBox, { title: title, noBottomMargin: noBottomMargin, topRightChildren: selectAllOrNone },
|
|
2724
2791
|
React__default["default"].createElement("div", { style: { overflowX: 'auto' } },
|
|
2725
2792
|
React__default["default"].createElement(NestableItemList, { items: items, onChanged: onChanged }))));
|
|
2726
2793
|
};
|
|
@@ -3092,7 +3159,7 @@ const IntelliTable = (props) => {
|
|
|
3092
3159
|
param: column.param,
|
|
3093
3160
|
visible: checked,
|
|
3094
3161
|
});
|
|
3095
|
-
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.
|
|
3162
|
+
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
|
|
3096
3163
|
}),
|
|
3097
3164
|
React__default["default"].createElement("div", { className: "mt-3" }, "Or you can:"),
|
|
3098
3165
|
React__default["default"].createElement("button", { type: "button", id: `IntelliTable-${id}-select-all-columns`, className: "btn btn-secondary me-2", "aria-label": `show all columns in the ${title} table`, onClick: () => {
|
|
@@ -14064,8 +14131,6 @@ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
|
|
|
14064
14131
|
let _cacclGetLaunchInfo;
|
|
14065
14132
|
// Stored copy of dce-mango log collection
|
|
14066
14133
|
let _logCollection;
|
|
14067
|
-
// Stored copy of dce-mango cross-server credential collection
|
|
14068
|
-
let _crossServerCredentialCollection;
|
|
14069
14134
|
/*------------------------------------------------------------------------*/
|
|
14070
14135
|
/* Helpers */
|
|
14071
14136
|
/*------------------------------------------------------------------------*/
|
|
@@ -14090,15 +14155,6 @@ const cacclGetLaunchInfo = (req) => {
|
|
|
14090
14155
|
const internalGetLogCollection = () => {
|
|
14091
14156
|
return _logCollection !== null && _logCollection !== void 0 ? _logCollection : null;
|
|
14092
14157
|
};
|
|
14093
|
-
/**
|
|
14094
|
-
* Get cross-server credential collection
|
|
14095
|
-
* @author Gabe Abrams
|
|
14096
|
-
* @return cross-server credential collection if one was included during launch or null
|
|
14097
|
-
* if we don't have a cross-server credential collection (yet)
|
|
14098
|
-
*/
|
|
14099
|
-
const internalGetCrossServerCredentialCollection = () => {
|
|
14100
|
-
return _crossServerCredentialCollection !== null && _crossServerCredentialCollection !== void 0 ? _crossServerCredentialCollection : null;
|
|
14101
|
-
};
|
|
14102
14158
|
/*------------------------------------------------------------------------*/
|
|
14103
14159
|
/* Main */
|
|
14104
14160
|
/*------------------------------------------------------------------------*/
|
|
@@ -14118,14 +14174,10 @@ const internalGetCrossServerCredentialCollection = () => {
|
|
|
14118
14174
|
* userIds are allowed to review logs. If a dce-mango collection, only
|
|
14119
14175
|
* Canvas admins with entries in that collection ({ userId, ...}) are allowed
|
|
14120
14176
|
* to review logs
|
|
14121
|
-
* @param [opts.crossServerCredentialCollection] mongo collection from dce-mango to use for
|
|
14122
|
-
* storing cross-server credentials. If none is included, cross-server credentials
|
|
14123
|
-
* are not supported
|
|
14124
14177
|
*/
|
|
14125
14178
|
const initServer = (opts) => {
|
|
14126
14179
|
_cacclGetLaunchInfo = opts.getLaunchInfo;
|
|
14127
14180
|
_logCollection = opts.logCollection;
|
|
14128
|
-
_crossServerCredentialCollection = opts.crossServerCredentialCollection;
|
|
14129
14181
|
/*----------------------------------------*/
|
|
14130
14182
|
/* Logging */
|
|
14131
14183
|
/*----------------------------------------*/
|
|
@@ -14668,154 +14720,6 @@ const parseUserAgent = (userAgent) => {
|
|
|
14668
14720
|
};
|
|
14669
14721
|
};
|
|
14670
14722
|
|
|
14671
|
-
/*------------------------------------------------------------------------*/
|
|
14672
|
-
/* ------------------------------ Encoding ------------------------------ */
|
|
14673
|
-
/*------------------------------------------------------------------------*/
|
|
14674
|
-
/**
|
|
14675
|
-
* Validate a JWT token
|
|
14676
|
-
* @author Gabe Abrams
|
|
14677
|
-
* @param encodedSecret the encoded secret
|
|
14678
|
-
* @return the secret
|
|
14679
|
-
*/
|
|
14680
|
-
const decodeCredentialSecret = (encodedSecret) => {
|
|
14681
|
-
// Get the credential encoding salt
|
|
14682
|
-
const credentialEncodingSalt = process.env.REACTKIT_CRED_ENCODING_SALT;
|
|
14683
|
-
if (!credentialEncodingSalt) {
|
|
14684
|
-
throw new ErrorWithCode('Cannot decode a cross-server credential secret because the credential encoding salt was not found in env.', ReactKitErrorCode$1.CrossServerNoCredentialEncodingSalt);
|
|
14685
|
-
}
|
|
14686
|
-
// Decode the secret
|
|
14687
|
-
return jwt__default["default"].decode(encodedSecret, credentialEncodingSalt);
|
|
14688
|
-
};
|
|
14689
|
-
/*------------------------------------------------------------------------*/
|
|
14690
|
-
/* ------------------------------- Signing ------------------------------ */
|
|
14691
|
-
/*------------------------------------------------------------------------*/
|
|
14692
|
-
/**
|
|
14693
|
-
* Sign using sha 256
|
|
14694
|
-
* @author Gabe Abrams
|
|
14695
|
-
* @param opts object containing all arguments
|
|
14696
|
-
* @param opts.pack the pack to sign
|
|
14697
|
-
* @param opts.secret the reactkit secret to sign with
|
|
14698
|
-
* @return the signed hash
|
|
14699
|
-
*/
|
|
14700
|
-
const genSignature = (opts) => {
|
|
14701
|
-
// Generate signature
|
|
14702
|
-
return (crypto.createHash('sha256')
|
|
14703
|
-
.update(JSON.stringify({ pack: opts.pack, secret: opts.secret }))
|
|
14704
|
-
.digest('base64'));
|
|
14705
|
-
};
|
|
14706
|
-
/**
|
|
14707
|
-
* Sign data with a private reactkit key, package it into a signed data pack
|
|
14708
|
-
* @author Gabe Abrams
|
|
14709
|
-
* @param opts object containing all arguments
|
|
14710
|
-
* @param opts.method the method to sign
|
|
14711
|
-
* @param opts.path the http request path
|
|
14712
|
-
* @param opts.params the data in the body to sign
|
|
14713
|
-
* @param opts.key the reactkit key to sign with
|
|
14714
|
-
* @param opts.secret the reactkit secret to sign with
|
|
14715
|
-
* @return the signed data
|
|
14716
|
-
*/
|
|
14717
|
-
const createSignedPack = (opts) => {
|
|
14718
|
-
// Create a timestamp
|
|
14719
|
-
const timestamp = Date.now();
|
|
14720
|
-
// Create the pack
|
|
14721
|
-
const pack = JSON.stringify({
|
|
14722
|
-
method: opts.method,
|
|
14723
|
-
path: opts.path,
|
|
14724
|
-
params: opts.params,
|
|
14725
|
-
key: opts.key,
|
|
14726
|
-
timestamp,
|
|
14727
|
-
});
|
|
14728
|
-
// Generate signature
|
|
14729
|
-
const signature = genSignature({
|
|
14730
|
-
pack,
|
|
14731
|
-
secret: opts.secret,
|
|
14732
|
-
});
|
|
14733
|
-
// Create a signed pack
|
|
14734
|
-
const signedPack = encodeURIComponent(JSON.stringify({
|
|
14735
|
-
pack,
|
|
14736
|
-
signature,
|
|
14737
|
-
}));
|
|
14738
|
-
// Return the signed pack
|
|
14739
|
-
return signedPack;
|
|
14740
|
-
};
|
|
14741
|
-
/**
|
|
14742
|
-
* Parse signed pack. Throws an error if invalid
|
|
14743
|
-
* @author Gabe Abrams
|
|
14744
|
-
* @param opts object containing all arguments
|
|
14745
|
-
* @param opts.method the method of the data validate
|
|
14746
|
-
* @param opts.path the http request path to validate
|
|
14747
|
-
* @param opts.scope the name of the scope to validate
|
|
14748
|
-
* @param opts.signedPack the signed data pack to validate
|
|
14749
|
-
* @returns parsed and validated params
|
|
14750
|
-
*/
|
|
14751
|
-
const parseSignedPack = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14752
|
-
// Extract signature
|
|
14753
|
-
let pack;
|
|
14754
|
-
let signature;
|
|
14755
|
-
let method;
|
|
14756
|
-
let path;
|
|
14757
|
-
let key;
|
|
14758
|
-
let timestamp;
|
|
14759
|
-
let params;
|
|
14760
|
-
try {
|
|
14761
|
-
({
|
|
14762
|
-
pack,
|
|
14763
|
-
signature,
|
|
14764
|
-
} = JSON.parse(decodeURIComponent(opts.signedPack)));
|
|
14765
|
-
// Unpack
|
|
14766
|
-
({
|
|
14767
|
-
method,
|
|
14768
|
-
path,
|
|
14769
|
-
params,
|
|
14770
|
-
key,
|
|
14771
|
-
timestamp,
|
|
14772
|
-
} = JSON.parse(pack));
|
|
14773
|
-
}
|
|
14774
|
-
catch (err) {
|
|
14775
|
-
throw new ErrorWithCode('Could not validate a cross-server request because the request could not be parsed.', ReactKitErrorCode$1.PackUnparseable);
|
|
14776
|
-
}
|
|
14777
|
-
// Make sure the method and path match
|
|
14778
|
-
if (method !== opts.method) {
|
|
14779
|
-
throw new ErrorWithCode('Could not validate a cross-server request because the method did not match.', ReactKitErrorCode$1.PackInvalidMethod);
|
|
14780
|
-
}
|
|
14781
|
-
if (path !== opts.path) {
|
|
14782
|
-
throw new ErrorWithCode('Could not validate a cross-server request because the path did not match.', ReactKitErrorCode$1.PackInvalidPath);
|
|
14783
|
-
}
|
|
14784
|
-
// Make sure the timestamp was recent enough
|
|
14785
|
-
const elapsedMs = Math.abs(Date.now() - timestamp);
|
|
14786
|
-
if (elapsedMs < MINUTE_IN_MS) {
|
|
14787
|
-
throw new ErrorWithCode('Could not validate a cross-server request because the request was too old.', ReactKitErrorCode$1.PackInvalidTimestamp);
|
|
14788
|
-
}
|
|
14789
|
-
// Get the cross-server credential collection
|
|
14790
|
-
const crossServerCredentialCollection = internalGetCrossServerCredentialCollection();
|
|
14791
|
-
if (!crossServerCredentialCollection) {
|
|
14792
|
-
throw new ErrorWithCode('Could not validate a cross-server request because the cross-server credential collection was not ready in time.', ReactKitErrorCode$1.PackInvalidCollection);
|
|
14793
|
-
}
|
|
14794
|
-
// Get the cross-server credential
|
|
14795
|
-
const crossServerCredential = yield crossServerCredentialCollection.find({ key });
|
|
14796
|
-
if (!crossServerCredential) {
|
|
14797
|
-
throw new ErrorWithCode('Could not validate a cross-server request because the credential was not found.', ReactKitErrorCode$1.PackInvalidCredential);
|
|
14798
|
-
}
|
|
14799
|
-
// Make sure the scope is included
|
|
14800
|
-
const allowedScopes = crossServerCredential.scopes;
|
|
14801
|
-
if (!allowedScopes.includes(opts.scope)) {
|
|
14802
|
-
throw new ErrorWithCode('Could not validate a cross-server request because the scope was not included.', ReactKitErrorCode$1.PackInvalidScope);
|
|
14803
|
-
}
|
|
14804
|
-
// Decode the secret
|
|
14805
|
-
const secret = decodeCredentialSecret(crossServerCredential.encodedeSecret);
|
|
14806
|
-
// Generate signature
|
|
14807
|
-
const expectedSignature = genSignature({
|
|
14808
|
-
pack,
|
|
14809
|
-
secret,
|
|
14810
|
-
});
|
|
14811
|
-
// Make sure the signature matches
|
|
14812
|
-
if (signature !== expectedSignature) {
|
|
14813
|
-
throw new ErrorWithCode('Could not validate a cross-server request because the signature did not match.', ReactKitErrorCode$1.PackInvalidSignature);
|
|
14814
|
-
}
|
|
14815
|
-
// Return body
|
|
14816
|
-
return params !== null && params !== void 0 ? params : {};
|
|
14817
|
-
});
|
|
14818
|
-
|
|
14819
14723
|
/**
|
|
14820
14724
|
* Generate an express API route handler
|
|
14821
14725
|
* @author Gabe Abrams
|
|
@@ -14823,15 +14727,14 @@ const parseSignedPack = (opts) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
14823
14727
|
* @param opts.paramTypes map containing the types for each parameter that is
|
|
14824
14728
|
* included in the request (map: param name => type)
|
|
14825
14729
|
* @param opts.handler function that processes the request
|
|
14826
|
-
* @param [opts.
|
|
14827
|
-
*
|
|
14828
|
-
*
|
|
14829
|
-
*
|
|
14830
|
-
*
|
|
14831
|
-
*
|
|
14832
|
-
*
|
|
14833
|
-
*
|
|
14834
|
-
* If crossServerScope is defined, this is always true
|
|
14730
|
+
* @param [opts.skipSessionCheck] if true, skip the session check (allow users
|
|
14731
|
+
* to not be logged in and launched via LTI)
|
|
14732
|
+
* @param [opts.allowedHosts] if included, only allow requests from these hosts
|
|
14733
|
+
* (start a hostname with a "*" to only check the end of the hostname)
|
|
14734
|
+
* you can include just one string instead of an array
|
|
14735
|
+
* @param [opts.bannedHosts] if included, do not allow requests from these hosts
|
|
14736
|
+
* (start a hostname with a "*" to only check the end of the hostname)
|
|
14737
|
+
* you can include just one string instead of an array
|
|
14835
14738
|
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
14836
14739
|
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
14837
14740
|
* where the error message is prefixed with this string. For example,
|
|
@@ -14859,51 +14762,86 @@ const parseSignedPack = (opts) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
14859
14762
|
const genRouteHandler = (opts) => {
|
|
14860
14763
|
// Return a route handler
|
|
14861
14764
|
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14862
|
-
|
|
14863
|
-
/* ------------- Preparation ------------ */
|
|
14864
|
-
/*----------------------------------------*/
|
|
14865
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
14765
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
14866
14766
|
// Output params
|
|
14867
14767
|
const output = {};
|
|
14868
|
-
// Determine cross server scopes
|
|
14869
|
-
let crossServerScope = null;
|
|
14870
|
-
if (opts.crossServerScope) {
|
|
14871
|
-
crossServerScope = (_a = opts.crossServerScope) !== null && _a !== void 0 ? _a : null;
|
|
14872
|
-
}
|
|
14873
|
-
// Determine whether we're skipping the session check
|
|
14874
|
-
const skipSessionCheck = !!(opts.skipSessionCheck
|
|
14875
|
-
|| crossServerScope);
|
|
14876
|
-
// Get body from everywhere it can come from
|
|
14877
|
-
let requestBody = Object.assign(Object.assign(Object.assign({}, req.body), req.query), req.params);
|
|
14878
14768
|
/*----------------------------------------*/
|
|
14879
|
-
/*
|
|
14769
|
+
/* ----------- Hostname Check ----------- */
|
|
14880
14770
|
/*----------------------------------------*/
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14771
|
+
// Get hostnames
|
|
14772
|
+
const originURL = String(req.get('origin')
|
|
14773
|
+
|| req.headers.origin
|
|
14774
|
+
|| req.headers.referer);
|
|
14775
|
+
const originHostname = (originURL
|
|
14776
|
+
// Remove protocol
|
|
14777
|
+
.replace(/(^\w+:|^)\/\//, '')
|
|
14778
|
+
// Remove port
|
|
14779
|
+
.replace(/:\d+$/, ''));
|
|
14780
|
+
const serverHostname = String(req.hostname);
|
|
14781
|
+
// Check allowed
|
|
14782
|
+
if (opts.allowedHosts) {
|
|
14783
|
+
// Only accept requests from allowed hosts
|
|
14784
|
+
const allowedArray = (Array.isArray(opts.allowedHosts)
|
|
14785
|
+
? opts.allowedHosts
|
|
14786
|
+
: [opts.allowedHosts]);
|
|
14787
|
+
// Check if server is localhost
|
|
14788
|
+
if (serverHostname === 'localhost') {
|
|
14789
|
+
// Allow localhost
|
|
14790
|
+
allowedArray.push('localhost');
|
|
14791
|
+
}
|
|
14792
|
+
// Check if current host is allowed
|
|
14793
|
+
const allowed = allowedArray.some((allowedHost) => {
|
|
14794
|
+
if (allowedHost.startsWith('*')) {
|
|
14795
|
+
// Check end of hostname
|
|
14796
|
+
return originHostname.endsWith(allowedHost.substring(1));
|
|
14797
|
+
}
|
|
14798
|
+
// Check full hostname
|
|
14799
|
+
return originHostname.toLowerCase() === allowedHost.toLowerCase();
|
|
14800
|
+
});
|
|
14801
|
+
// If not allowed, return error
|
|
14802
|
+
if (!allowed) {
|
|
14803
|
+
return handleError(res, {
|
|
14804
|
+
message: 'You are not allowed to access this endpoint.',
|
|
14805
|
+
code: ReactKitErrorCode$1.HostNotAllowed,
|
|
14806
|
+
status: 403,
|
|
14807
|
+
});
|
|
14887
14808
|
}
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
|
|
14809
|
+
}
|
|
14810
|
+
// Check banned
|
|
14811
|
+
if (opts.bannedHosts) {
|
|
14812
|
+
// Do not allow requests from banned hosts
|
|
14813
|
+
const bannedArray = (Array.isArray(opts.bannedHosts)
|
|
14814
|
+
? opts.bannedHosts
|
|
14815
|
+
: [opts.bannedHosts]);
|
|
14816
|
+
// Check if current host is banned
|
|
14817
|
+
const banned = bannedArray.some((bannedHost) => {
|
|
14818
|
+
if (bannedHost.startsWith('*')) {
|
|
14819
|
+
// Check end of hostname
|
|
14820
|
+
return originHostname.endsWith(bannedHost.substring(1));
|
|
14821
|
+
}
|
|
14822
|
+
// Check full hostname
|
|
14823
|
+
return originHostname.toLowerCase() === bannedHost.toLowerCase();
|
|
14894
14824
|
});
|
|
14895
|
-
//
|
|
14896
|
-
|
|
14825
|
+
// If banned, return error
|
|
14826
|
+
if (banned) {
|
|
14827
|
+
return handleError(res, {
|
|
14828
|
+
message: 'You are not allowed to access this endpoint.',
|
|
14829
|
+
code: ReactKitErrorCode$1.HostBanned,
|
|
14830
|
+
status: 403,
|
|
14831
|
+
});
|
|
14832
|
+
}
|
|
14897
14833
|
}
|
|
14898
14834
|
/*----------------------------------------*/
|
|
14899
14835
|
/* ------------ Parse Params ------------ */
|
|
14900
14836
|
/*----------------------------------------*/
|
|
14901
14837
|
// Process items one by one
|
|
14902
|
-
const paramList = Object.entries((
|
|
14838
|
+
const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
|
|
14903
14839
|
for (let i = 0; i < paramList.length; i++) {
|
|
14904
14840
|
const [name, type] = paramList[i];
|
|
14905
14841
|
// Find the value as a string
|
|
14906
|
-
const value =
|
|
14842
|
+
const value = (req.params[name]
|
|
14843
|
+
|| req.query[name]
|
|
14844
|
+
|| req.body[name]);
|
|
14907
14845
|
// Parse
|
|
14908
14846
|
if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
|
|
14909
14847
|
// Boolean
|
|
@@ -15061,7 +14999,7 @@ const genRouteHandler = (opts) => {
|
|
|
15061
14999
|
// Not launched
|
|
15062
15000
|
(!launched || !launchInfo)
|
|
15063
15001
|
// Not skipping the session check
|
|
15064
|
-
&& !skipSessionCheck) {
|
|
15002
|
+
&& !opts.skipSessionCheck) {
|
|
15065
15003
|
return handleError(res, {
|
|
15066
15004
|
message: 'Your session has expired. Please refresh the page and try again.',
|
|
15067
15005
|
code: ReactKitErrorCode$1.SessionExpired,
|
|
@@ -15081,7 +15019,7 @@ const genRouteHandler = (opts) => {
|
|
|
15081
15019
|
&& !launchInfo.isLearner
|
|
15082
15020
|
&& !launchInfo.isAdmin))
|
|
15083
15021
|
// Not skipping the session check
|
|
15084
|
-
&& !skipSessionCheck) {
|
|
15022
|
+
&& !opts.skipSessionCheck) {
|
|
15085
15023
|
return handleError(res, {
|
|
15086
15024
|
message: 'Your session was invalid. Please refresh the page and try again.',
|
|
15087
15025
|
code: ReactKitErrorCode$1.SessionExpired,
|
|
@@ -15091,34 +15029,34 @@ const genRouteHandler = (opts) => {
|
|
|
15091
15029
|
// Add launch info to output
|
|
15092
15030
|
output.userId = (launchInfo
|
|
15093
15031
|
? launchInfo.userId
|
|
15094
|
-
: ((
|
|
15032
|
+
: ((_b = output.userId) !== null && _b !== void 0 ? _b : undefined));
|
|
15095
15033
|
output.userFirstName = (launchInfo
|
|
15096
15034
|
? launchInfo.userFirstName
|
|
15097
|
-
: ((
|
|
15035
|
+
: ((_c = output.userFirstName) !== null && _c !== void 0 ? _c : undefined));
|
|
15098
15036
|
output.userLastName = (launchInfo
|
|
15099
15037
|
? launchInfo.userLastName
|
|
15100
|
-
: ((
|
|
15038
|
+
: ((_d = output.userLastName) !== null && _d !== void 0 ? _d : undefined));
|
|
15101
15039
|
output.userEmail = (launchInfo
|
|
15102
15040
|
? launchInfo.userEmail
|
|
15103
|
-
: ((
|
|
15041
|
+
: ((_e = output.userEmail) !== null && _e !== void 0 ? _e : undefined));
|
|
15104
15042
|
output.userAvatarURL = (launchInfo
|
|
15105
|
-
? ((
|
|
15106
|
-
: ((
|
|
15043
|
+
? ((_f = launchInfo.userImage) !== null && _f !== void 0 ? _f : 'http://www.gravatar.com/avatar/?d=identicon')
|
|
15044
|
+
: ((_g = output.userAvatarURL) !== null && _g !== void 0 ? _g : undefined));
|
|
15107
15045
|
output.isLearner = (launchInfo
|
|
15108
15046
|
? !!launchInfo.isLearner
|
|
15109
|
-
: ((
|
|
15047
|
+
: ((_h = output.isLearner) !== null && _h !== void 0 ? _h : undefined));
|
|
15110
15048
|
output.isTTM = (launchInfo
|
|
15111
15049
|
? !!launchInfo.isTTM
|
|
15112
|
-
: ((
|
|
15050
|
+
: ((_j = output.isTTM) !== null && _j !== void 0 ? _j : undefined));
|
|
15113
15051
|
output.isAdmin = (launchInfo
|
|
15114
15052
|
? !!launchInfo.isAdmin
|
|
15115
|
-
: ((
|
|
15053
|
+
: ((_k = output.isAdmin) !== null && _k !== void 0 ? _k : undefined));
|
|
15116
15054
|
output.courseId = (launchInfo
|
|
15117
|
-
? ((
|
|
15118
|
-
: ((
|
|
15055
|
+
? ((_l = output.courseId) !== null && _l !== void 0 ? _l : launchInfo.courseId)
|
|
15056
|
+
: ((_m = output.courseId) !== null && _m !== void 0 ? _m : undefined));
|
|
15119
15057
|
output.courseName = (launchInfo
|
|
15120
15058
|
? launchInfo.contextLabel
|
|
15121
|
-
: ((
|
|
15059
|
+
: ((_o = output.courseName) !== null && _o !== void 0 ? _o : undefined));
|
|
15122
15060
|
// Add other session variables
|
|
15123
15061
|
Object.keys(req.session).forEach((propName) => {
|
|
15124
15062
|
// Skip if prop already in output
|
|
@@ -15192,7 +15130,7 @@ const genRouteHandler = (opts) => {
|
|
|
15192
15130
|
* @author Gabe Abrams
|
|
15193
15131
|
*/
|
|
15194
15132
|
const logServerEvent = (logOpts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15195
|
-
var _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1
|
|
15133
|
+
var _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
|
|
15196
15134
|
// NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
|
|
15197
15135
|
// that indicates that this is actually a client event, but we don't
|
|
15198
15136
|
// include that in the LogFunction type because this is internal and
|
|
@@ -15224,24 +15162,24 @@ const genRouteHandler = (opts) => {
|
|
|
15224
15162
|
timestamp,
|
|
15225
15163
|
context: (typeof logOpts.context === 'string'
|
|
15226
15164
|
? logOpts.context
|
|
15227
|
-
: ((
|
|
15228
|
-
subcontext: ((
|
|
15229
|
-
tags: ((
|
|
15230
|
-
level: ((
|
|
15231
|
-
metadata: ((
|
|
15165
|
+
: ((_q = ((_p = logOpts.context) !== null && _p !== void 0 ? _p : {})._) !== null && _q !== void 0 ? _q : LogBuiltInMetadata.Context.Uncategorized)),
|
|
15166
|
+
subcontext: ((_r = logOpts.subcontext) !== null && _r !== void 0 ? _r : LogBuiltInMetadata.Context.Uncategorized),
|
|
15167
|
+
tags: ((_s = logOpts.tags) !== null && _s !== void 0 ? _s : []),
|
|
15168
|
+
level: ((_t = logOpts.level) !== null && _t !== void 0 ? _t : LogLevel$1.Info),
|
|
15169
|
+
metadata: ((_u = logOpts.metadata) !== null && _u !== void 0 ? _u : {}),
|
|
15232
15170
|
};
|
|
15233
15171
|
// Type-specific info
|
|
15234
15172
|
const typeSpecificInfo = (('error' in opts && opts.error)
|
|
15235
15173
|
? {
|
|
15236
15174
|
type: LogType$1.Error,
|
|
15237
|
-
errorMessage: (
|
|
15238
|
-
errorCode: (
|
|
15239
|
-
errorStack: (
|
|
15175
|
+
errorMessage: (_v = logOpts.error.message) !== null && _v !== void 0 ? _v : 'Unknown message',
|
|
15176
|
+
errorCode: (_w = logOpts.error.code) !== null && _w !== void 0 ? _w : ReactKitErrorCode$1.NoCode,
|
|
15177
|
+
errorStack: (_x = logOpts.error.stack) !== null && _x !== void 0 ? _x : 'No stack',
|
|
15240
15178
|
}
|
|
15241
15179
|
: {
|
|
15242
15180
|
type: LogType$1.Action,
|
|
15243
|
-
target: ((
|
|
15244
|
-
action: ((
|
|
15181
|
+
target: ((_y = logOpts.target) !== null && _y !== void 0 ? _y : LogBuiltInMetadata.Target.NoTarget),
|
|
15182
|
+
action: ((_z = logOpts.action) !== null && _z !== void 0 ? _z : LogAction$1.Unknown),
|
|
15245
15183
|
});
|
|
15246
15184
|
// Source-specific info
|
|
15247
15185
|
const sourceSpecificInfo = (logOpts.overrideAsClientEvent
|
|
@@ -15276,7 +15214,7 @@ const genRouteHandler = (opts) => {
|
|
|
15276
15214
|
catch (err) {
|
|
15277
15215
|
// Print because we cannot store the error
|
|
15278
15216
|
// eslint-disable-next-line no-console
|
|
15279
|
-
console.error('Could not log the following:', logOpts, 'due to this error:', ((
|
|
15217
|
+
console.error('Could not log the following:', logOpts, 'due to this error:', ((_0 = err) !== null && _0 !== void 0 ? _0 : {}).message, ((_1 = err) !== null && _1 !== void 0 ? _1 : {}).stack);
|
|
15280
15218
|
// Create a dummy log to return
|
|
15281
15219
|
const dummyMainInfo = {
|
|
15282
15220
|
id: '-1',
|
|
@@ -15629,18 +15567,6 @@ const initLogCollection = (Collection) => {
|
|
|
15629
15567
|
});
|
|
15630
15568
|
};
|
|
15631
15569
|
|
|
15632
|
-
/**
|
|
15633
|
-
* Initialize a cross-server credential collection given the dce-mango Collection class
|
|
15634
|
-
* @author Gabe Abrams
|
|
15635
|
-
* @param Collection the Collection class from dce-mango
|
|
15636
|
-
* @returns initialized logCollection
|
|
15637
|
-
*/
|
|
15638
|
-
const initCrossServerCredentialCollection = (Collection) => {
|
|
15639
|
-
return new Collection('CrossServerCredential', {
|
|
15640
|
-
uniqueIndexKey: 'key',
|
|
15641
|
-
});
|
|
15642
|
-
};
|
|
15643
|
-
|
|
15644
15570
|
/*------------------------------------------------------------------------*/
|
|
15645
15571
|
/* -------------------------------- Cache ------------------------------- */
|
|
15646
15572
|
/*------------------------------------------------------------------------*/
|
|
@@ -16390,6 +16316,9 @@ const shuffleArray = (arr) => {
|
|
|
16390
16316
|
* @param [opts.host] host to send request to
|
|
16391
16317
|
* @param [opts.method=GET] http method to use
|
|
16392
16318
|
* @param [opts.params] body/data to include in the request
|
|
16319
|
+
* @param [opts.headers] headers to include in the request
|
|
16320
|
+
* @param [opts.sendCrossDomainCredentials=true if in development mode] if true,
|
|
16321
|
+
* send cross-domain credentials even if not in dev mode
|
|
16393
16322
|
* @param [opts.responseType=JSON] expected response type
|
|
16394
16323
|
* @returns { body, status, headers } on success
|
|
16395
16324
|
*/
|
|
@@ -16426,7 +16355,7 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
|
|
|
16426
16355
|
url = `https://${opts.host}${opts.path}${query}`;
|
|
16427
16356
|
}
|
|
16428
16357
|
// Update headers
|
|
16429
|
-
const headers = {};
|
|
16358
|
+
const headers = opts.headers || {};
|
|
16430
16359
|
let data = null;
|
|
16431
16360
|
if (!headers['Content-Type']) {
|
|
16432
16361
|
// Form encoded
|
|
@@ -16499,49 +16428,49 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
|
|
|
16499
16428
|
});
|
|
16500
16429
|
|
|
16501
16430
|
/**
|
|
16502
|
-
*
|
|
16431
|
+
* Send a server-to-server request from this sever to another server that uses
|
|
16432
|
+
* dce-reactkit [for server only]
|
|
16503
16433
|
* @author Gabe Abrams
|
|
16504
16434
|
* @param opts object containing all arguments
|
|
16505
|
-
* @param opts.
|
|
16506
|
-
* @param opts.path the path of the other server's endpoint
|
|
16507
|
-
* @param opts.
|
|
16508
|
-
* @param [opts.
|
|
16509
|
-
*
|
|
16510
|
-
* @
|
|
16511
|
-
* credential secret
|
|
16512
|
-
* @param [opts.params={}] query/body parameters to include
|
|
16513
|
-
* @param [opts.responseType=JSON] the response type from the other server
|
|
16435
|
+
* @param opts.host - the host of the other server
|
|
16436
|
+
* @param opts.path - the path of the other server's endpoint
|
|
16437
|
+
* @param [opts.method=GET] - the method of the endpoint
|
|
16438
|
+
* @param [opts.params] - query/body parameters to include
|
|
16439
|
+
* @param [opts.headers] - headers to include
|
|
16440
|
+
* @returns response from server
|
|
16514
16441
|
*/
|
|
16515
16442
|
const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16516
|
-
var _a
|
|
16517
|
-
//
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
params
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16443
|
+
var _a;
|
|
16444
|
+
// Remove properties with undefined values
|
|
16445
|
+
let params;
|
|
16446
|
+
if (opts.params) {
|
|
16447
|
+
params = Object.fromEntries(Object
|
|
16448
|
+
.entries(opts.params)
|
|
16449
|
+
.filter(([, value]) => {
|
|
16450
|
+
return value !== undefined;
|
|
16451
|
+
}));
|
|
16452
|
+
}
|
|
16453
|
+
// Automatically JSONify arrays and objects
|
|
16454
|
+
if (params) {
|
|
16455
|
+
params = Object.fromEntries(Object
|
|
16456
|
+
.entries(params)
|
|
16457
|
+
.map(([key, value]) => {
|
|
16458
|
+
if (Array.isArray(value) || typeof value === 'object') {
|
|
16459
|
+
return [key, JSON.stringify(value)];
|
|
16460
|
+
}
|
|
16461
|
+
return [key, value];
|
|
16462
|
+
}));
|
|
16463
|
+
}
|
|
16532
16464
|
// Send the request
|
|
16533
16465
|
const response = yield sendServerToServerRequest({
|
|
16534
|
-
path: opts.path,
|
|
16535
16466
|
host: opts.host,
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
},
|
|
16540
|
-
responseType: opts.responseType,
|
|
16467
|
+
path: opts.path,
|
|
16468
|
+
method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
|
|
16469
|
+
params,
|
|
16541
16470
|
});
|
|
16542
16471
|
// Check for failure
|
|
16543
16472
|
if (!response || !response.body) {
|
|
16544
|
-
throw new ErrorWithCode('We didn\'t get a response from the
|
|
16473
|
+
throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
|
|
16545
16474
|
}
|
|
16546
16475
|
if (!response.body.success) {
|
|
16547
16476
|
// Other errors
|
|
@@ -16555,6 +16484,27 @@ const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0,
|
|
|
16555
16484
|
return body;
|
|
16556
16485
|
});
|
|
16557
16486
|
|
|
16487
|
+
const punctuationRegex = /[!@#$%^&*(),.?\/;:'"\-\[\]]/g;
|
|
16488
|
+
/**
|
|
16489
|
+
* Get number of words in string
|
|
16490
|
+
* @author Gardenia Liu
|
|
16491
|
+
* @author Allison Zhang
|
|
16492
|
+
* @author Gabe Abrams
|
|
16493
|
+
* @param text the string to check
|
|
16494
|
+
* @returns number of words in the string
|
|
16495
|
+
*/
|
|
16496
|
+
const getWordCount = (text) => {
|
|
16497
|
+
const trimmedTextWithoutPunctuation = (text
|
|
16498
|
+
// Remove leading and trailing whitespace
|
|
16499
|
+
.trim()
|
|
16500
|
+
// Remove punctuation
|
|
16501
|
+
.replace(punctuationRegex, ''));
|
|
16502
|
+
if (trimmedTextWithoutPunctuation.length === 0) {
|
|
16503
|
+
return 0;
|
|
16504
|
+
}
|
|
16505
|
+
return trimmedTextWithoutPunctuation.split(/\s+/g).length;
|
|
16506
|
+
};
|
|
16507
|
+
|
|
16558
16508
|
/**
|
|
16559
16509
|
* Days of the week
|
|
16560
16510
|
* @author Gabe Abrams
|
|
@@ -16639,11 +16589,11 @@ exports.getMonthName = getMonthName;
|
|
|
16639
16589
|
exports.getOrdinal = getOrdinal;
|
|
16640
16590
|
exports.getPartOfDay = getPartOfDay;
|
|
16641
16591
|
exports.getTimeInfoInET = getTimeInfoInET;
|
|
16592
|
+
exports.getWordCount = getWordCount;
|
|
16642
16593
|
exports.handleError = handleError;
|
|
16643
16594
|
exports.handleSuccess = handleSuccess;
|
|
16644
16595
|
exports.idify = idify;
|
|
16645
16596
|
exports.initClient = initClient;
|
|
16646
|
-
exports.initCrossServerCredentialCollection = initCrossServerCredentialCollection;
|
|
16647
16597
|
exports.initLogCollection = initLogCollection;
|
|
16648
16598
|
exports.initServer = initServer;
|
|
16649
16599
|
exports.isMobileOrTablet = isMobileOrTablet;
|