dce-reactkit 3.12.3 → 4.0.0-beta.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/.vscode/settings.json +1 -3
- package/README.md +20 -0
- package/dist/cjs/index.js +156 -1792
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ItemPicker/index.d.ts +0 -1
- package/dist/cjs/types/components/Modal/ModalProps.d.ts +0 -2
- package/dist/cjs/types/components/TabBox.d.ts +0 -1
- package/dist/cjs/types/index.d.ts +8 -12
- package/dist/cjs/types/types/ParamType.d.ts +10 -10
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +1 -15
- package/dist/esm/index.js +157 -1785
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ItemPicker/index.d.ts +0 -1
- package/dist/esm/types/components/Modal/ModalProps.d.ts +0 -2
- package/dist/esm/types/components/TabBox.d.ts +0 -1
- package/dist/esm/types/index.d.ts +8 -12
- package/dist/esm/types/types/ParamType.d.ts +10 -10
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +1 -15
- package/dist/index.d.ts +122 -351
- package/genEncodedSecret.ts +84 -0
- package/package.json +7 -5
- package/rollup.config.js +2 -0
- package/src/components/AppWrapper.tsx +3 -1
- package/src/components/CheckboxButton.tsx +1 -1
- package/src/components/Dropdown.tsx +1 -1
- package/src/components/IntelliTable.tsx +1 -1
- package/src/components/ItemPicker/NestableItemList.tsx +0 -1
- package/src/components/ItemPicker/index.tsx +0 -96
- package/src/components/Modal/ModalProps.ts +0 -4
- package/src/components/Modal/index.tsx +21 -60
- package/src/components/RadioButton.tsx +3 -1
- package/src/components/TabBox.tsx +9 -27
- package/src/{client → helpers}/initClient.tsx +5 -1
- package/src/helpers/logClientEvent.tsx +3 -1
- package/src/helpers/visitServerEndpoint.tsx +1 -1
- package/src/index.ts +12 -22
- package/src/types/ParamType.ts +10 -10
- package/src/types/ReactKitErrorCode.tsx +1 -17
- package/dist/cjs/types/helpers/addDBEditorEndpoints.d.ts +0 -41
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +0 -76
- package/dist/cjs/types/helpers/getWordCount.d.ts +0 -10
- package/dist/cjs/types/helpers/handleError.d.ts +0 -18
- package/dist/cjs/types/helpers/handleSuccess.d.ts +0 -8
- package/dist/cjs/types/helpers/parseUserAgent.d.ts +0 -17
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -24
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -33
- package/dist/cjs/types/server/initLogCollection.d.ts +0 -8
- package/dist/cjs/types/server/initServer.d.ts +0 -42
- package/dist/esm/types/helpers/addDBEditorEndpoints.d.ts +0 -41
- package/dist/esm/types/helpers/genRouteHandler.d.ts +0 -76
- package/dist/esm/types/helpers/getWordCount.d.ts +0 -10
- package/dist/esm/types/helpers/handleError.d.ts +0 -18
- package/dist/esm/types/helpers/handleSuccess.d.ts +0 -8
- package/dist/esm/types/helpers/parseUserAgent.d.ts +0 -17
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -24
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -33
- package/dist/esm/types/server/initLogCollection.d.ts +0 -8
- package/dist/esm/types/server/initServer.d.ts +0 -42
- package/src/components/ItemPicker/index.test.tsx +0 -783
- package/src/helpers/addDBEditorEndpoints.ts +0 -128
- package/src/helpers/genRouteHandler.ts +0 -948
- package/src/helpers/getWordCount.ts +0 -26
- package/src/helpers/handleError.ts +0 -65
- package/src/helpers/handleSuccess.ts +0 -18
- package/src/helpers/parseUserAgent.ts +0 -108
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +0 -93
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +0 -164
- package/src/server/initLogCollection.ts +0 -27
- package/src/server/initServer.ts +0 -287
- /package/dist/cjs/types/{client → helpers}/initClient.d.ts +0 -0
- /package/dist/esm/types/{client → helpers}/initClient.d.ts +0 -0
package/dist/cjs/index.js
CHANGED
|
@@ -7,7 +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 qs = require('qs');
|
|
11
10
|
|
|
12
11
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
13
12
|
|
|
@@ -32,7 +31,6 @@ function _interopNamespace(e) {
|
|
|
32
31
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
33
32
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
34
33
|
var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
|
|
35
|
-
var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
|
|
36
34
|
|
|
37
35
|
/******************************************************************************
|
|
38
36
|
Copyright (c) Microsoft Corporation.
|
|
@@ -59,7 +57,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
59
57
|
});
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
// Highest error code =
|
|
60
|
+
// Highest error code = DRK34
|
|
63
61
|
/**
|
|
64
62
|
* List of error codes built into the react kit
|
|
65
63
|
* @author Gabe Abrams
|
|
@@ -69,22 +67,7 @@ var ReactKitErrorCode;
|
|
|
69
67
|
ReactKitErrorCode["NoResponse"] = "DRK1";
|
|
70
68
|
ReactKitErrorCode["NoCode"] = "DRK2";
|
|
71
69
|
ReactKitErrorCode["SessionExpired"] = "DRK3";
|
|
72
|
-
ReactKitErrorCode["MissingParameter"] = "DRK4";
|
|
73
|
-
ReactKitErrorCode["InvalidParameter"] = "DRK5";
|
|
74
|
-
ReactKitErrorCode["HostNotAllowed"] = "DRK17";
|
|
75
|
-
ReactKitErrorCode["HostBanned"] = "DRK18";
|
|
76
|
-
ReactKitErrorCode["WrongCourse"] = "DRK6";
|
|
77
70
|
ReactKitErrorCode["NoCACCLSendRequestFunction"] = "DRK7";
|
|
78
|
-
ReactKitErrorCode["NoCACCLGetLaunchInfoFunction"] = "DRK8";
|
|
79
|
-
ReactKitErrorCode["NotTTM"] = "DRK9";
|
|
80
|
-
ReactKitErrorCode["NotAdmin"] = "DRK10";
|
|
81
|
-
ReactKitErrorCode["NotAllowedToReviewLogs"] = "DRK11";
|
|
82
|
-
ReactKitErrorCode["ThemeCheckedBeforeReactKitReady"] = "DRK12";
|
|
83
|
-
ReactKitErrorCode["SessionExpiredMessageGottenBeforeReactKitReady"] = "DRK13";
|
|
84
|
-
// Server-to-server requests
|
|
85
|
-
ReactKitErrorCode["NotConnected"] = "DRK14";
|
|
86
|
-
ReactKitErrorCode["SelfSigned"] = "DRK15";
|
|
87
|
-
ReactKitErrorCode["ResponseParseError"] = "DRK16";
|
|
88
71
|
})(ReactKitErrorCode || (ReactKitErrorCode = {}));
|
|
89
72
|
var ReactKitErrorCode$1 = ReactKitErrorCode;
|
|
90
73
|
|
|
@@ -216,98 +199,6 @@ const LogBuiltInMetadata = {
|
|
|
216
199
|
},
|
|
217
200
|
};
|
|
218
201
|
|
|
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
|
-
|
|
311
202
|
/**
|
|
312
203
|
* Wait for a certain number of ms
|
|
313
204
|
* @author Gabe Abrams
|
|
@@ -358,6 +249,15 @@ let onInitialized;
|
|
|
358
249
|
const initialized = new Promise((resolve) => {
|
|
359
250
|
onInitialized = resolve;
|
|
360
251
|
});
|
|
252
|
+
/* ------------ No Server ----------- */
|
|
253
|
+
let noServer = false;
|
|
254
|
+
/**
|
|
255
|
+
* Check if there is no server for this app
|
|
256
|
+
* @author Gabe Abrams
|
|
257
|
+
*/
|
|
258
|
+
const appHasNoServer = () => {
|
|
259
|
+
return noServer;
|
|
260
|
+
};
|
|
361
261
|
/* ---------- Send Request ---------- */
|
|
362
262
|
let storedSendRequest;
|
|
363
263
|
/**
|
|
@@ -419,11 +319,13 @@ const initClient = (opts) => {
|
|
|
419
319
|
storedSendRequest = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
420
320
|
throw new Error('Cannot send requests because there is no server');
|
|
421
321
|
});
|
|
322
|
+
noServer = true;
|
|
422
323
|
}
|
|
423
324
|
else {
|
|
424
325
|
// Store values
|
|
425
326
|
storedSendRequest = opts.sendRequest;
|
|
426
327
|
sessionExpiredMessage = opts.sessionExpiredMessage;
|
|
328
|
+
noServer = false;
|
|
427
329
|
}
|
|
428
330
|
// Handle universal parts
|
|
429
331
|
darkModeOn = !!opts.darkModeOn;
|
|
@@ -440,7 +342,6 @@ const BASE_Z_INDEX = 1000000000;
|
|
|
440
342
|
const BASE_Z_INDEX_ON_TOP = 2000000000;
|
|
441
343
|
// Constants
|
|
442
344
|
const MS_TO_ANIMATE = 200; // Animation duration
|
|
443
|
-
const MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR = 1000;
|
|
444
345
|
// Modal type to list of buttons
|
|
445
346
|
const modalTypeToModalButtonTypes = {
|
|
446
347
|
[ModalType$1.Okay]: [
|
|
@@ -554,7 +455,7 @@ const getNextUniqueId = () => {
|
|
|
554
455
|
/*------------------------------------------------------------------------*/
|
|
555
456
|
/* -------------------------------- Style ------------------------------- */
|
|
556
457
|
/*------------------------------------------------------------------------*/
|
|
557
|
-
const style$
|
|
458
|
+
const style$a = `
|
|
558
459
|
.Modal-backdrop {
|
|
559
460
|
position: fixed;
|
|
560
461
|
top: 0;
|
|
@@ -626,14 +527,13 @@ const Modal = (props) => {
|
|
|
626
527
|
/*------------------------------------------------------------------------*/
|
|
627
528
|
var _a;
|
|
628
529
|
/* -------------- Props ------------- */
|
|
629
|
-
const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, largeTitle, children, onClose, dontAllowBackdropExit, dontShowXButton, onTopOfOtherModals,
|
|
530
|
+
const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, largeTitle, children, onClose, dontAllowBackdropExit, dontShowXButton, onTopOfOtherModals, } = props;
|
|
630
531
|
// Determine if no header either
|
|
631
532
|
const noHeader = (!title && type === ModalType$1.NoButtons);
|
|
632
533
|
/* -------------- State ------------- */
|
|
633
534
|
// True if animation is in use
|
|
634
535
|
const [animatingIn, setAnimatingIn] = React.useState(true);
|
|
635
536
|
const [animatingPop, setAnimatingPop] = React.useState(false);
|
|
636
|
-
const [showModal, setShowModal] = React.useState(false);
|
|
637
537
|
/* -------------- Refs -------------- */
|
|
638
538
|
// Keep track of whether modal is still mounted
|
|
639
539
|
const mounted = React.useRef(false);
|
|
@@ -660,16 +560,6 @@ const Modal = (props) => {
|
|
|
660
560
|
if (mounted.current) {
|
|
661
561
|
setAnimatingIn(false);
|
|
662
562
|
}
|
|
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
|
-
}
|
|
673
563
|
}))();
|
|
674
564
|
return () => {
|
|
675
565
|
mounted.current = false;
|
|
@@ -736,15 +626,6 @@ const Modal = (props) => {
|
|
|
736
626
|
else if (animatingPop) {
|
|
737
627
|
animationClass = 'Modal-animating-pop';
|
|
738
628
|
}
|
|
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));
|
|
748
629
|
// Render the modal
|
|
749
630
|
const contentToRender = (React__default["default"].createElement("div", { className: "modal show", tabIndex: -1, style: {
|
|
750
631
|
zIndex: baseZIndex,
|
|
@@ -753,7 +634,7 @@ const Modal = (props) => {
|
|
|
753
634
|
left: 0,
|
|
754
635
|
right: 0,
|
|
755
636
|
} },
|
|
756
|
-
React__default["default"].createElement("style", null, style$
|
|
637
|
+
React__default["default"].createElement("style", null, style$a),
|
|
757
638
|
React__default["default"].createElement("div", { className: `Modal-backdrop ${backdropAnimationClass}`, style: {
|
|
758
639
|
zIndex: baseZIndex + 1,
|
|
759
640
|
}, onClick: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -771,7 +652,7 @@ const Modal = (props) => {
|
|
|
771
652
|
// Handle close
|
|
772
653
|
handleClose(ModalButtonType$1.Cancel);
|
|
773
654
|
}) }),
|
|
774
|
-
|
|
655
|
+
React__default["default"].createElement("div", { className: `modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`, style: {
|
|
775
656
|
zIndex: baseZIndex + 2,
|
|
776
657
|
// Override sizes for even larger for XL
|
|
777
658
|
width: (size === ModalSize$1.ExtraLarge
|
|
@@ -803,7 +684,7 @@ const Modal = (props) => {
|
|
|
803
684
|
? '1.6rem'
|
|
804
685
|
: undefined),
|
|
805
686
|
} }, title),
|
|
806
|
-
|
|
687
|
+
(onClose && !dontShowXButton) && (React__default["default"].createElement("button", { type: "button", className: "Modal-x-button btn-close", "aria-label": "Close", style: {
|
|
807
688
|
backgroundColor: (isDarkModeOn()
|
|
808
689
|
? 'white'
|
|
809
690
|
: undefined),
|
|
@@ -811,18 +692,14 @@ const Modal = (props) => {
|
|
|
811
692
|
// Handle close
|
|
812
693
|
handleClose(ModalButtonType$1.Cancel);
|
|
813
694
|
} })))),
|
|
814
|
-
React__default["default"].createElement("div", { className: "modal-body", style: {
|
|
695
|
+
children && (React__default["default"].createElement("div", { className: "modal-body", style: {
|
|
815
696
|
color: (isDarkModeOn()
|
|
816
697
|
? 'white'
|
|
817
698
|
: undefined),
|
|
818
699
|
backgroundColor: (isDarkModeOn()
|
|
819
700
|
? '#444'
|
|
820
701
|
: undefined),
|
|
821
|
-
} },
|
|
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),
|
|
702
|
+
} }, children)),
|
|
826
703
|
footer && (React__default["default"].createElement("div", { className: "modal-footer pt-1 pb-1", style: {
|
|
827
704
|
color: (isDarkModeOn()
|
|
828
705
|
? 'white'
|
|
@@ -833,7 +710,7 @@ const Modal = (props) => {
|
|
|
833
710
|
borderTop: (isDarkModeOn()
|
|
834
711
|
? '0.1rem solid gray'
|
|
835
712
|
: undefined),
|
|
836
|
-
} }, footer))))))
|
|
713
|
+
} }, footer))))));
|
|
837
714
|
// Determine which portal to use
|
|
838
715
|
const portalNumber = id.current % NUM_MODAL_PORTALS;
|
|
839
716
|
// Render in a portal
|
|
@@ -1004,6 +881,10 @@ const setClientEventMetadataPopulator = (newMetadataPopulator) => {
|
|
|
1004
881
|
*/
|
|
1005
882
|
const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1006
883
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
884
|
+
// Don't write to the server if there is none
|
|
885
|
+
if (appHasNoServer()) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
1007
888
|
// Populate metadata
|
|
1008
889
|
let metadata = ((_a = opts.metadata) !== null && _a !== void 0 ? _a : {});
|
|
1009
890
|
if (metadataPopulator) {
|
|
@@ -1400,7 +1281,7 @@ const showSessionExpiredMessage = () => __awaiter(void 0, void 0, void 0, functi
|
|
|
1400
1281
|
/*------------------------------------------------------------------------*/
|
|
1401
1282
|
/* -------------------------------- Style ------------------------------- */
|
|
1402
1283
|
/*------------------------------------------------------------------------*/
|
|
1403
|
-
const style$
|
|
1284
|
+
const style$9 = `
|
|
1404
1285
|
.AppWrapper-leave-to-url-notice {
|
|
1405
1286
|
opacity: 0;
|
|
1406
1287
|
|
|
@@ -1614,13 +1495,105 @@ const AppWrapper = (props) => {
|
|
|
1614
1495
|
/* --------------- Main UI -------------- */
|
|
1615
1496
|
/*----------------------------------------*/
|
|
1616
1497
|
return (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
1617
|
-
React__default["default"].createElement("style", null, style$
|
|
1498
|
+
React__default["default"].createElement("style", null, style$9),
|
|
1618
1499
|
React__default["default"].createElement("style", null, tooltipStyle),
|
|
1619
1500
|
modal,
|
|
1620
1501
|
customModalPortals,
|
|
1621
1502
|
body));
|
|
1622
1503
|
};
|
|
1623
1504
|
|
|
1505
|
+
/**
|
|
1506
|
+
* Loading spinner/indicator
|
|
1507
|
+
* @author Gabe Abrams
|
|
1508
|
+
*/
|
|
1509
|
+
/*------------------------------------------------------------------------*/
|
|
1510
|
+
/* -------------------------------- Style ------------------------------- */
|
|
1511
|
+
/*------------------------------------------------------------------------*/
|
|
1512
|
+
const style$8 = `
|
|
1513
|
+
/* Container fades in */
|
|
1514
|
+
.LoadingSpinner-container {
|
|
1515
|
+
animation-name: LoadingSpinner-container-fade-in;
|
|
1516
|
+
animation-duration: 0.3s;
|
|
1517
|
+
animation-delay: 0.4s;
|
|
1518
|
+
animation-fill-mode: both;
|
|
1519
|
+
animation-timing-function: ease-out;
|
|
1520
|
+
}
|
|
1521
|
+
@keyframes LoadingSpinner-container-fade-in {
|
|
1522
|
+
0% {
|
|
1523
|
+
opacity: 0;
|
|
1524
|
+
}
|
|
1525
|
+
100% {
|
|
1526
|
+
opacity: 1;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/* Blips */
|
|
1531
|
+
.LoadingSpinner-blip-1,
|
|
1532
|
+
.LoadingSpinner-blip-2,
|
|
1533
|
+
.LoadingSpinner-blip-3,
|
|
1534
|
+
.LoadingSpinner-blip-4 {
|
|
1535
|
+
font-size: 1.8rem;
|
|
1536
|
+
opacity: 0.6;
|
|
1537
|
+
margin-top: 1rem;
|
|
1538
|
+
margin-bottom: 1rem;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
/* First Blip */
|
|
1542
|
+
.LoadingSpinner-blip-1 {
|
|
1543
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/* Second Blip */
|
|
1547
|
+
.LoadingSpinner-blip-2 {
|
|
1548
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1549
|
+
-webkit-animation-delay: 0.1s;
|
|
1550
|
+
animation-delay: 0.1s;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
/* Third Blip */
|
|
1554
|
+
.LoadingSpinner-blip-3 {
|
|
1555
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1556
|
+
animation-delay: 0.2s;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
/* Fourth Blip */
|
|
1560
|
+
.LoadingSpinner-blip-4 {
|
|
1561
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1562
|
+
animation-delay: 0.3s;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/* Pop Animation for Each Blip */
|
|
1566
|
+
@keyframes LoadingSpinner-pop-animation {
|
|
1567
|
+
0% {
|
|
1568
|
+
transform: scale(1.0);
|
|
1569
|
+
}
|
|
1570
|
+
10% {
|
|
1571
|
+
transform: scale(1.5);
|
|
1572
|
+
}
|
|
1573
|
+
30% {
|
|
1574
|
+
transform: scale(1.0);
|
|
1575
|
+
}
|
|
1576
|
+
100% {
|
|
1577
|
+
transform: scale(1.0);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
`;
|
|
1581
|
+
/*------------------------------------------------------------------------*/
|
|
1582
|
+
/* ------------------------------ Component ----------------------------- */
|
|
1583
|
+
/*------------------------------------------------------------------------*/
|
|
1584
|
+
const LoadingSpinner = () => {
|
|
1585
|
+
/*------------------------------------------------------------------------*/
|
|
1586
|
+
/* ------------------------------- Render ------------------------------- */
|
|
1587
|
+
/*------------------------------------------------------------------------*/
|
|
1588
|
+
// Add all four blips to a container
|
|
1589
|
+
return (React__default["default"].createElement("div", { className: "text-center LoadingSpinner LoadingSpinner-container" },
|
|
1590
|
+
React__default["default"].createElement("style", null, style$8),
|
|
1591
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-1 me-1" }),
|
|
1592
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-2 me-1" }),
|
|
1593
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-3 me-1" }),
|
|
1594
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-4" })));
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1624
1597
|
/**
|
|
1625
1598
|
* A box with a tab on the top that holds buttons and other content
|
|
1626
1599
|
* @author Gabe Abrams
|
|
@@ -1652,9 +1625,7 @@ const style$7 = `
|
|
|
1652
1625
|
|
|
1653
1626
|
/* Container for Title */
|
|
1654
1627
|
.TabBox-title-container {
|
|
1655
|
-
|
|
1656
|
-
justify-content: space-between;
|
|
1657
|
-
align-items: center;
|
|
1628
|
+
/* Place on Left */
|
|
1658
1629
|
position: relative;
|
|
1659
1630
|
left: 0;
|
|
1660
1631
|
text-align: left;
|
|
@@ -1687,19 +1658,6 @@ const style$7 = `
|
|
|
1687
1658
|
background: #fdfdfd;
|
|
1688
1659
|
}
|
|
1689
1660
|
|
|
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
|
-
|
|
1703
1661
|
/* Make the TabBox's Children Appear Above Title if Overlap Occurs */
|
|
1704
1662
|
.TabBox-children {
|
|
1705
1663
|
position: relative;
|
|
@@ -1713,16 +1671,19 @@ const TabBox = (props) => {
|
|
|
1713
1671
|
/*------------------------------------------------------------------------*/
|
|
1714
1672
|
/* -------------------------------- Setup ------------------------------- */
|
|
1715
1673
|
/*------------------------------------------------------------------------*/
|
|
1716
|
-
|
|
1674
|
+
/* -------------- Props ------------- */
|
|
1675
|
+
const { title, children, noBottomPadding, noBottomMargin, } = props;
|
|
1717
1676
|
/*------------------------------------------------------------------------*/
|
|
1718
1677
|
/* ------------------------------- Render ------------------------------- */
|
|
1719
1678
|
/*------------------------------------------------------------------------*/
|
|
1679
|
+
/*----------------------------------------*/
|
|
1680
|
+
/* --------------- Main UI -------------- */
|
|
1681
|
+
/*----------------------------------------*/
|
|
1682
|
+
// Full UI
|
|
1720
1683
|
return (React__default["default"].createElement("div", { className: `TabBox-container ${noBottomMargin ? '' : 'mb-2'}` },
|
|
1721
1684
|
React__default["default"].createElement("style", null, style$7),
|
|
1722
1685
|
React__default["default"].createElement("div", { className: "TabBox-title-container" },
|
|
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)))),
|
|
1686
|
+
React__default["default"].createElement("div", { className: "TabBox-title" }, title)),
|
|
1726
1687
|
React__default["default"].createElement("div", { className: `TabBox-box ps-2 pt-2 pe-2 ${noBottomPadding ? '' : 'pb-2'}` },
|
|
1727
1688
|
React__default["default"].createElement("div", { className: "TabBox-children" }, children))));
|
|
1728
1689
|
};
|
|
@@ -2709,7 +2670,7 @@ const NestableItemList = (props) => {
|
|
|
2709
2670
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
|
|
2710
2671
|
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) => {
|
|
2711
2672
|
onChanged(changeChecked(item.id, checked, items));
|
|
2712
|
-
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light
|
|
2673
|
+
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2713
2674
|
(item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2714
2675
|
paddingLeft: '2.2rem',
|
|
2715
2676
|
} },
|
|
@@ -2732,62 +2693,17 @@ const ItemPicker = (props) => {
|
|
|
2732
2693
|
/*------------------------------------------------------------------------*/
|
|
2733
2694
|
/* -------------- Props ------------- */
|
|
2734
2695
|
// Destructure all props
|
|
2735
|
-
const { title, items, onChanged, noBottomMargin,
|
|
2696
|
+
const { title, items, onChanged, noBottomMargin, } = props;
|
|
2736
2697
|
/*------------------------------------------------------------------------*/
|
|
2737
2698
|
/* ------------------------- Component Functions ------------------------ */
|
|
2738
2699
|
/*------------------------------------------------------------------------*/
|
|
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
|
-
};
|
|
2774
2700
|
/*------------------------------------------------------------------------*/
|
|
2775
2701
|
/* ------------------------------- Render ------------------------------- */
|
|
2776
2702
|
/*------------------------------------------------------------------------*/
|
|
2777
2703
|
/*----------------------------------------*/
|
|
2778
2704
|
/* --------------- Main UI -------------- */
|
|
2779
2705
|
/*----------------------------------------*/
|
|
2780
|
-
|
|
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 },
|
|
2706
|
+
return (React__default["default"].createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
|
|
2791
2707
|
React__default["default"].createElement("div", { style: { overflowX: 'auto' } },
|
|
2792
2708
|
React__default["default"].createElement(NestableItemList, { items: items, onChanged: onChanged }))));
|
|
2793
2709
|
};
|
|
@@ -2902,16 +2818,16 @@ var LogAction$1 = LogAction;
|
|
|
2902
2818
|
*/
|
|
2903
2819
|
var ParamType;
|
|
2904
2820
|
(function (ParamType) {
|
|
2905
|
-
ParamType["Boolean"] = "
|
|
2906
|
-
ParamType["BooleanOptional"] = "
|
|
2907
|
-
ParamType["Float"] = "
|
|
2908
|
-
ParamType["FloatOptional"] = "
|
|
2909
|
-
ParamType["Int"] = "
|
|
2910
|
-
ParamType["IntOptional"] = "
|
|
2911
|
-
ParamType["JSON"] = "
|
|
2912
|
-
ParamType["JSONOptional"] = "
|
|
2913
|
-
ParamType["String"] = "
|
|
2914
|
-
ParamType["StringOptional"] = "
|
|
2821
|
+
ParamType["Boolean"] = "Boolean";
|
|
2822
|
+
ParamType["BooleanOptional"] = "BooleanOptional";
|
|
2823
|
+
ParamType["Float"] = "Float";
|
|
2824
|
+
ParamType["FloatOptional"] = "FloatOptional";
|
|
2825
|
+
ParamType["Int"] = "Int";
|
|
2826
|
+
ParamType["IntOptional"] = "IntOptional";
|
|
2827
|
+
ParamType["JSON"] = "JSON";
|
|
2828
|
+
ParamType["JSONOptional"] = "JSONOptional";
|
|
2829
|
+
ParamType["String"] = "String";
|
|
2830
|
+
ParamType["StringOptional"] = "StringOptional";
|
|
2915
2831
|
})(ParamType || (ParamType = {}));
|
|
2916
2832
|
var ParamType$1 = ParamType;
|
|
2917
2833
|
|
|
@@ -3159,7 +3075,7 @@ const IntelliTable = (props) => {
|
|
|
3159
3075
|
param: column.param,
|
|
3160
3076
|
visible: checked,
|
|
3161
3077
|
});
|
|
3162
|
-
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.
|
|
3078
|
+
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
|
|
3163
3079
|
}),
|
|
3164
3080
|
React__default["default"].createElement("div", { className: "mt-3" }, "Or you can:"),
|
|
3165
3081
|
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: () => {
|
|
@@ -14121,1295 +14037,23 @@ const padZerosLeft = (num, numDigits) => {
|
|
|
14121
14037
|
};
|
|
14122
14038
|
|
|
14123
14039
|
/**
|
|
14124
|
-
*
|
|
14125
|
-
* access to log review
|
|
14040
|
+
* Stub a server endpoint response
|
|
14126
14041
|
* @author Gabe Abrams
|
|
14042
|
+
* @param opts object containing all arguments
|
|
14043
|
+
* @param [opts.method=GET] http method to stub
|
|
14044
|
+
* @param opts.path full pathname to stub
|
|
14045
|
+
* @param opts.body body of response if stubbing a successful response
|
|
14046
|
+
* @param opts.errorMessage message of error if stubbing a failed response
|
|
14047
|
+
* @param [opts.errorCode] error code if stubbing a failed response
|
|
14127
14048
|
*/
|
|
14128
|
-
const
|
|
14049
|
+
const stubServerEndpoint = _setStubResponse;
|
|
14129
14050
|
|
|
14130
|
-
// Stored copy of caccl functions
|
|
14131
|
-
let _cacclGetLaunchInfo;
|
|
14132
|
-
// Stored copy of dce-mango log collection
|
|
14133
|
-
let _logCollection;
|
|
14134
|
-
/*------------------------------------------------------------------------*/
|
|
14135
|
-
/* Helpers */
|
|
14136
|
-
/*------------------------------------------------------------------------*/
|
|
14137
|
-
/**
|
|
14138
|
-
* Get launch info via CACCL
|
|
14139
|
-
* @author Gabe Abrams
|
|
14140
|
-
* @param req express request object
|
|
14141
|
-
* @returns object { launched, launchInfo }
|
|
14142
|
-
*/
|
|
14143
|
-
const cacclGetLaunchInfo = (req) => {
|
|
14144
|
-
if (!_cacclGetLaunchInfo) {
|
|
14145
|
-
throw new ErrorWithCode('Could not get launch info because server was not initialized with dce-reactkit\'s initServer function', ReactKitErrorCode$1.NoCACCLGetLaunchInfoFunction);
|
|
14146
|
-
}
|
|
14147
|
-
return _cacclGetLaunchInfo(req);
|
|
14148
|
-
};
|
|
14149
14051
|
/**
|
|
14150
|
-
*
|
|
14052
|
+
* Start a dynamic wait, call the function once the operation has completed and
|
|
14053
|
+
* the dynamic wait will continue waiting for the rest of the minimum time
|
|
14151
14054
|
* @author Gabe Abrams
|
|
14152
|
-
* @
|
|
14153
|
-
*
|
|
14154
|
-
*/
|
|
14155
|
-
const internalGetLogCollection = () => {
|
|
14156
|
-
return _logCollection !== null && _logCollection !== void 0 ? _logCollection : null;
|
|
14157
|
-
};
|
|
14158
|
-
/*------------------------------------------------------------------------*/
|
|
14159
|
-
/* Main */
|
|
14160
|
-
/*------------------------------------------------------------------------*/
|
|
14161
|
-
/**
|
|
14162
|
-
* Prepare dce-reactkit to run on the server
|
|
14163
|
-
* @author Gabe Abrams
|
|
14164
|
-
* @param opts object containing all arguments
|
|
14165
|
-
* @param opts.app express app from inside of the postprocessor function that
|
|
14166
|
-
* we will add routes to
|
|
14167
|
-
* @param opts.getLaunchInfo CACCL LTI's get launch info function
|
|
14168
|
-
* @param [opts.logCollection] mongo collection from dce-mango to use for
|
|
14169
|
-
* storing logs. If none is included, logs are written to the console
|
|
14170
|
-
* @param [opts.logReviewAdmins=all] info on which admins can review
|
|
14171
|
-
* logs from the client. If not included, all Canvas admins are allowed to
|
|
14172
|
-
* review logs. If null, no Canvas admins are allowed to review logs.
|
|
14173
|
-
* If an array of Canvas userIds (numbers), only Canvas admins with those
|
|
14174
|
-
* userIds are allowed to review logs. If a dce-mango collection, only
|
|
14175
|
-
* Canvas admins with entries in that collection ({ userId, ...}) are allowed
|
|
14176
|
-
* to review logs
|
|
14177
|
-
*/
|
|
14178
|
-
const initServer = (opts) => {
|
|
14179
|
-
_cacclGetLaunchInfo = opts.getLaunchInfo;
|
|
14180
|
-
_logCollection = opts.logCollection;
|
|
14181
|
-
/*----------------------------------------*/
|
|
14182
|
-
/* Logging */
|
|
14183
|
-
/*----------------------------------------*/
|
|
14184
|
-
/**
|
|
14185
|
-
* Log an event
|
|
14186
|
-
* @author Gabe Abrams
|
|
14187
|
-
* @param {string} context Context of the event (each app determines how to
|
|
14188
|
-
* organize its contexts)
|
|
14189
|
-
* @param {string} subcontext Subcontext of the event (each app determines
|
|
14190
|
-
* how to organize its subcontexts)
|
|
14191
|
-
* @param {string} tags stringified list of tags that apply to this action
|
|
14192
|
-
* (each app determines tag usage)
|
|
14193
|
-
* @param {string} metadata stringified object containing optional custom metadata
|
|
14194
|
-
* @param {string} level log level
|
|
14195
|
-
* @param {string} [errorMessage] error message if type is an error
|
|
14196
|
-
* @param {string} [errorCode] error code if type is an error
|
|
14197
|
-
* @param {string} [errorStack] error stack if type is an error
|
|
14198
|
-
* @param {string} [target] Target of the action (each app determines the list
|
|
14199
|
-
* of targets) These are usually buttons, panels, elements, etc.
|
|
14200
|
-
* @param {LogAction} [action] the type of action performed on the target
|
|
14201
|
-
* @returns {Log}
|
|
14202
|
-
*/
|
|
14203
|
-
opts.app.post(LOG_ROUTE_PATH, genRouteHandler({
|
|
14204
|
-
paramTypes: {
|
|
14205
|
-
context: ParamType$1.String,
|
|
14206
|
-
subcontext: ParamType$1.String,
|
|
14207
|
-
tags: ParamType$1.JSON,
|
|
14208
|
-
level: ParamType$1.String,
|
|
14209
|
-
metadata: ParamType$1.JSON,
|
|
14210
|
-
errorMessage: ParamType$1.StringOptional,
|
|
14211
|
-
errorCode: ParamType$1.StringOptional,
|
|
14212
|
-
errorStack: ParamType$1.StringOptional,
|
|
14213
|
-
target: ParamType$1.StringOptional,
|
|
14214
|
-
action: ParamType$1.StringOptional,
|
|
14215
|
-
},
|
|
14216
|
-
handler: ({ params, logServerEvent }) => {
|
|
14217
|
-
// Create log info
|
|
14218
|
-
const logInfo = ((params.errorMessage || params.errorCode || params.errorStack)
|
|
14219
|
-
// Error
|
|
14220
|
-
? {
|
|
14221
|
-
context: params.context,
|
|
14222
|
-
subcontext: params.subcontext,
|
|
14223
|
-
tags: params.tags,
|
|
14224
|
-
level: params.level,
|
|
14225
|
-
metadata: params.metadata,
|
|
14226
|
-
error: {
|
|
14227
|
-
message: params.errorMessage,
|
|
14228
|
-
code: params.errorCode,
|
|
14229
|
-
stack: params.errorStack,
|
|
14230
|
-
},
|
|
14231
|
-
}
|
|
14232
|
-
// Action
|
|
14233
|
-
: {
|
|
14234
|
-
context: params.context,
|
|
14235
|
-
subcontext: params.subcontext,
|
|
14236
|
-
tags: params.tags,
|
|
14237
|
-
level: params.level,
|
|
14238
|
-
metadata: params.metadata,
|
|
14239
|
-
target: params.target,
|
|
14240
|
-
action: params.action,
|
|
14241
|
-
});
|
|
14242
|
-
// Add hidden boolean to change source to "client"
|
|
14243
|
-
const logInfoForcedFromClient = Object.assign(Object.assign({}, logInfo), { overrideAsClientEvent: true });
|
|
14244
|
-
// Write the log
|
|
14245
|
-
const log = logServerEvent(logInfoForcedFromClient);
|
|
14246
|
-
// Return
|
|
14247
|
-
return log;
|
|
14248
|
-
},
|
|
14249
|
-
}));
|
|
14250
|
-
/*----------------------------------------*/
|
|
14251
|
-
/* Log Reviewer */
|
|
14252
|
-
/*----------------------------------------*/
|
|
14253
|
-
/**
|
|
14254
|
-
* Check if a given user is allowed to review logs
|
|
14255
|
-
* @author Gabe Abrams
|
|
14256
|
-
* @param userId the id of the user
|
|
14257
|
-
* @param isAdmin if true, the user is an admin
|
|
14258
|
-
* @returns true if the user can review logs
|
|
14259
|
-
*/
|
|
14260
|
-
const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14261
|
-
// Immediately deny access if user is not an admin
|
|
14262
|
-
if (!isAdmin) {
|
|
14263
|
-
return false;
|
|
14264
|
-
}
|
|
14265
|
-
// If all admins are allowed, we're done
|
|
14266
|
-
if (!opts.logReviewAdmins) {
|
|
14267
|
-
return true;
|
|
14268
|
-
}
|
|
14269
|
-
// Do a dynamic check
|
|
14270
|
-
try {
|
|
14271
|
-
// Array of userIds
|
|
14272
|
-
if (Array.isArray(opts.logReviewAdmins)) {
|
|
14273
|
-
return opts.logReviewAdmins.some((allowedId) => {
|
|
14274
|
-
return (userId === allowedId);
|
|
14275
|
-
});
|
|
14276
|
-
}
|
|
14277
|
-
// Must be a collection
|
|
14278
|
-
const matches = yield opts.logReviewAdmins.find({ userId });
|
|
14279
|
-
// Make sure at least one entry matches
|
|
14280
|
-
return matches.length > 0;
|
|
14281
|
-
}
|
|
14282
|
-
catch (err) {
|
|
14283
|
-
// If an error occurred, simply return false
|
|
14284
|
-
return false;
|
|
14285
|
-
}
|
|
14286
|
-
});
|
|
14287
|
-
/**
|
|
14288
|
-
* Check if the current user has access to logs
|
|
14289
|
-
* @author Gabe Abrams
|
|
14290
|
-
* @returns {boolean} true if user has access
|
|
14291
|
-
*/
|
|
14292
|
-
opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
|
|
14293
|
-
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14294
|
-
const { userId, isAdmin } = params;
|
|
14295
|
-
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
14296
|
-
return canReview;
|
|
14297
|
-
}),
|
|
14298
|
-
}));
|
|
14299
|
-
/**
|
|
14300
|
-
* Get all logs for a certain month
|
|
14301
|
-
* @author Gabe Abrams
|
|
14302
|
-
* @param {number} year the year to query (e.g. 2022)
|
|
14303
|
-
* @param {number} month the month to query (e.g. 1 = January)
|
|
14304
|
-
* @returns {Log[]} list of logs from the given month
|
|
14305
|
-
*/
|
|
14306
|
-
opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
|
|
14307
|
-
paramTypes: {
|
|
14308
|
-
year: ParamType$1.Int,
|
|
14309
|
-
month: ParamType$1.Int,
|
|
14310
|
-
pageNumber: ParamType$1.Int,
|
|
14311
|
-
},
|
|
14312
|
-
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14313
|
-
// Get user info
|
|
14314
|
-
const { year, month, pageNumber, userId, isAdmin, } = params;
|
|
14315
|
-
// Validate user
|
|
14316
|
-
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
14317
|
-
if (!canReview) {
|
|
14318
|
-
throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
|
|
14319
|
-
}
|
|
14320
|
-
// Query for logs
|
|
14321
|
-
const response = yield _logCollection.findPaged({
|
|
14322
|
-
query: {
|
|
14323
|
-
year,
|
|
14324
|
-
month,
|
|
14325
|
-
},
|
|
14326
|
-
perPage: 1000,
|
|
14327
|
-
pageNumber,
|
|
14328
|
-
});
|
|
14329
|
-
// Return response
|
|
14330
|
-
return response;
|
|
14331
|
-
}),
|
|
14332
|
-
}));
|
|
14333
|
-
};
|
|
14334
|
-
|
|
14335
|
-
// Import shared types
|
|
14336
|
-
/**
|
|
14337
|
-
* Handle an error and respond to the client
|
|
14338
|
-
* @author Gabe Abrams
|
|
14339
|
-
* @param res express response
|
|
14340
|
-
* @param error error info
|
|
14341
|
-
* @param opts.err the error to send to the client
|
|
14342
|
-
* or the error message
|
|
14343
|
-
* @param [opts.code] an error code (only used if err.code is not
|
|
14344
|
-
* included)
|
|
14345
|
-
* @param [opts.status=500] the https status code to use
|
|
14346
|
-
* defined)
|
|
14347
|
-
*/
|
|
14348
|
-
const handleError = (res, error) => {
|
|
14349
|
-
// Get the error message
|
|
14350
|
-
let message;
|
|
14351
|
-
if (error && error.message) {
|
|
14352
|
-
message = (error.message || 'An unknown error occurred.');
|
|
14353
|
-
}
|
|
14354
|
-
else if (typeof error === 'string') {
|
|
14355
|
-
message = (error.trim().length > 0
|
|
14356
|
-
? error
|
|
14357
|
-
: 'An unknown error occurred.');
|
|
14358
|
-
}
|
|
14359
|
-
else {
|
|
14360
|
-
message = 'An unknown error occurred.';
|
|
14361
|
-
}
|
|
14362
|
-
// Get the error code
|
|
14363
|
-
const code = (error.code || ReactKitErrorCode$1.NoCode);
|
|
14364
|
-
// Get the status code
|
|
14365
|
-
const status = (error.status || 500);
|
|
14366
|
-
// Respond to user
|
|
14367
|
-
res
|
|
14368
|
-
// Set the http status code
|
|
14369
|
-
.status(status)
|
|
14370
|
-
// Send a JSON response
|
|
14371
|
-
.json({
|
|
14372
|
-
// Error message
|
|
14373
|
-
message,
|
|
14374
|
-
// Error code
|
|
14375
|
-
code,
|
|
14376
|
-
// Success = false flag so client can detect server-side errors
|
|
14377
|
-
success: false,
|
|
14378
|
-
});
|
|
14379
|
-
return undefined;
|
|
14380
|
-
};
|
|
14381
|
-
|
|
14382
|
-
/**
|
|
14383
|
-
* Send successful API response
|
|
14384
|
-
* @author Gabe Abrams
|
|
14385
|
-
* @param res express response
|
|
14386
|
-
* @param body the body of the response to send to the client
|
|
14387
|
-
*/
|
|
14388
|
-
const handleSuccess = (res, body) => {
|
|
14389
|
-
// Send a http 200 json response
|
|
14390
|
-
res.json({
|
|
14391
|
-
// Include the body as a parameter
|
|
14392
|
-
body,
|
|
14393
|
-
// Success = true flag so client can detect successful responses
|
|
14394
|
-
success: true,
|
|
14395
|
-
});
|
|
14396
|
-
return undefined;
|
|
14397
|
-
};
|
|
14398
|
-
|
|
14399
|
-
// Import shared types
|
|
14400
|
-
/**
|
|
14401
|
-
* Generate a static error page
|
|
14402
|
-
* @author Gabe Abrams
|
|
14403
|
-
* @param opts object containing all arguments
|
|
14404
|
-
* @param [opts.title=An Error Occurred] title of the error box
|
|
14405
|
-
* @param [opts.description=An unknown server error occurred. Please contact support.]
|
|
14406
|
-
* a human-readable description of the error
|
|
14407
|
-
* @param [opts.code=ReactKitErrorCode.NoCode] error code to show
|
|
14408
|
-
* @param [opts.pageTitle=opts.title] title of the page/tab if it differs from
|
|
14409
|
-
* the title of the error
|
|
14410
|
-
* @returns html of the page
|
|
14411
|
-
*/
|
|
14412
|
-
const genErrorPage = (opts = {}) => {
|
|
14413
|
-
var _a, _b, _c, _d;
|
|
14414
|
-
const title = ((_a = opts.title) !== null && _a !== void 0 ? _a : 'An Error Occurred');
|
|
14415
|
-
const pageTitle = ((_b = opts.pageTitle) !== null && _b !== void 0 ? _b : title);
|
|
14416
|
-
const description = ((_c = opts.description) !== null && _c !== void 0 ? _c : 'An unknown server error occurred. Please contact support.');
|
|
14417
|
-
const code = ((_d = opts.code) !== null && _d !== void 0 ? _d : ReactKitErrorCode$1.NoCode);
|
|
14418
|
-
return `
|
|
14419
|
-
<head>
|
|
14420
|
-
<!-- Metadata -->
|
|
14421
|
-
<meta
|
|
14422
|
-
name="viewport"
|
|
14423
|
-
content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"
|
|
14424
|
-
>
|
|
14425
|
-
|
|
14426
|
-
<!-- Title -->
|
|
14427
|
-
<title>${pageTitle}</title>
|
|
14428
|
-
|
|
14429
|
-
<!-- Bootstrap -->
|
|
14430
|
-
<link
|
|
14431
|
-
rel="stylesheet"
|
|
14432
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/css/bootstrap.min.css"
|
|
14433
|
-
integrity="sha512-siwe/oXMhSjGCwLn+scraPOWrJxHlUgMBMZXdPe2Tnk3I0x3ESCoLz7WZ5NTH6SZrywMY+PB1cjyqJ5jAluCOg=="
|
|
14434
|
-
crossorigin="anonymous"
|
|
14435
|
-
referrerpolicy="no-referrer"
|
|
14436
|
-
/>
|
|
14437
|
-
<script
|
|
14438
|
-
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/js/bootstrap.min.js"
|
|
14439
|
-
integrity="sha512-vyRAVI0IEm6LI/fVSv/Wq/d0KUfrg3hJq2Qz5FlfER69sf3ZHlOrsLriNm49FxnpUGmhx+TaJKwJ+ByTLKT+Yg=="
|
|
14440
|
-
crossorigin="anonymous"
|
|
14441
|
-
referrerpolicy="no-referrer"
|
|
14442
|
-
></script>
|
|
14443
|
-
|
|
14444
|
-
<!-- FontAwesome -->
|
|
14445
|
-
<link
|
|
14446
|
-
rel="stylesheet"
|
|
14447
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
|
|
14448
|
-
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
|
|
14449
|
-
crossorigin="anonymous"
|
|
14450
|
-
referrerpolicy="no-referrer"
|
|
14451
|
-
/>
|
|
14452
|
-
|
|
14453
|
-
<!-- Style -->
|
|
14454
|
-
<style>
|
|
14455
|
-
.DCEReactKit-pop-in {
|
|
14456
|
-
animation-name: DCEReactKit-pop-in;
|
|
14457
|
-
animation-duration: 0.5s;
|
|
14458
|
-
animation-iteration-count: 1;
|
|
14459
|
-
animation-timing-function: ease-out;
|
|
14460
|
-
animation-fill-mode: both;
|
|
14461
|
-
|
|
14462
|
-
transform-origin: center;
|
|
14463
|
-
}
|
|
14464
|
-
|
|
14465
|
-
@keyframes DCEReactKit-pop-in {
|
|
14466
|
-
0% {
|
|
14467
|
-
opacity: 0;
|
|
14468
|
-
transform: scale(0.9);
|
|
14469
|
-
}
|
|
14470
|
-
100% {
|
|
14471
|
-
opacity: 1;
|
|
14472
|
-
transform: scale(1);
|
|
14473
|
-
}
|
|
14474
|
-
}
|
|
14475
|
-
|
|
14476
|
-
.DCEReactKit-slide-in {
|
|
14477
|
-
animation-name: DCEReactKit-slide-in;
|
|
14478
|
-
animation-duration: 1s;
|
|
14479
|
-
animation-iteration-count: 1;
|
|
14480
|
-
animation-timing-function: ease-out;
|
|
14481
|
-
animation-fill-mode: both;
|
|
14482
|
-
animation-delay: 0.2s;
|
|
14483
|
-
}
|
|
14484
|
-
|
|
14485
|
-
@keyframes DCEReactKit-slide-in {
|
|
14486
|
-
0% {
|
|
14487
|
-
opacity: 0;
|
|
14488
|
-
transform: translate(0, 0.3em);
|
|
14489
|
-
}
|
|
14490
|
-
100% {
|
|
14491
|
-
opacity: 1;
|
|
14492
|
-
transform: translate(0, 0);
|
|
14493
|
-
}
|
|
14494
|
-
}
|
|
14495
|
-
</style>
|
|
14496
|
-
</head>
|
|
14497
|
-
|
|
14498
|
-
<!-- Body -->
|
|
14499
|
-
<body class="bg-dark text-center pt-3 ps-3 pe-3">
|
|
14500
|
-
<!-- Alert -->
|
|
14501
|
-
<div
|
|
14502
|
-
class="DCEReactKit-pop-in alert alert-warning d-inline-block"
|
|
14503
|
-
style="width: 50em; max-width: 100%"
|
|
14504
|
-
>
|
|
14505
|
-
<!-- Title -->
|
|
14506
|
-
<h2>
|
|
14507
|
-
<i class="me-1 fa-solid fa-triangle-exclamation"></i>
|
|
14508
|
-
${title}
|
|
14509
|
-
</h2>
|
|
14510
|
-
<!-- Description -->
|
|
14511
|
-
<div>
|
|
14512
|
-
${description}
|
|
14513
|
-
</div>
|
|
14514
|
-
</div>
|
|
14515
|
-
|
|
14516
|
-
<!-- Error Code -->
|
|
14517
|
-
<div class="DCEReactKit-slide-in text-light">
|
|
14518
|
-
<strong>
|
|
14519
|
-
Error Code:
|
|
14520
|
-
</strong>
|
|
14521
|
-
${code}
|
|
14522
|
-
</div>
|
|
14523
|
-
</body>
|
|
14524
|
-
`;
|
|
14525
|
-
};
|
|
14526
|
-
|
|
14527
|
-
/**
|
|
14528
|
-
* Generate a static info page
|
|
14529
|
-
* @author Gabe Abrams
|
|
14530
|
-
* @param opts object containing all arguments
|
|
14531
|
-
* @param opts.title title of the info box
|
|
14532
|
-
* @param opts.body a human-readable text body for the info alert
|
|
14533
|
-
* @returns the HTML for the info page
|
|
14534
|
-
*/
|
|
14535
|
-
const genInfoPage = (opts) => {
|
|
14536
|
-
const { title, body, } = opts;
|
|
14537
|
-
return `
|
|
14538
|
-
<head>
|
|
14539
|
-
<!-- Metadata -->
|
|
14540
|
-
<meta
|
|
14541
|
-
name="viewport"
|
|
14542
|
-
content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"
|
|
14543
|
-
>
|
|
14544
|
-
|
|
14545
|
-
<!-- Title -->
|
|
14546
|
-
<title>${title}</title>
|
|
14547
|
-
|
|
14548
|
-
<!-- Bootstrap -->
|
|
14549
|
-
<link
|
|
14550
|
-
rel="stylesheet"
|
|
14551
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/css/bootstrap.min.css"
|
|
14552
|
-
integrity="sha512-siwe/oXMhSjGCwLn+scraPOWrJxHlUgMBMZXdPe2Tnk3I0x3ESCoLz7WZ5NTH6SZrywMY+PB1cjyqJ5jAluCOg=="
|
|
14553
|
-
crossorigin="anonymous"
|
|
14554
|
-
referrerpolicy="no-referrer"
|
|
14555
|
-
/>
|
|
14556
|
-
<script
|
|
14557
|
-
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/js/bootstrap.min.js"
|
|
14558
|
-
integrity="sha512-vyRAVI0IEm6LI/fVSv/Wq/d0KUfrg3hJq2Qz5FlfER69sf3ZHlOrsLriNm49FxnpUGmhx+TaJKwJ+ByTLKT+Yg=="
|
|
14559
|
-
crossorigin="anonymous"
|
|
14560
|
-
referrerpolicy="no-referrer"
|
|
14561
|
-
></script>
|
|
14562
|
-
|
|
14563
|
-
<!-- FontAwesome -->
|
|
14564
|
-
<link
|
|
14565
|
-
rel="stylesheet"
|
|
14566
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
|
|
14567
|
-
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
|
|
14568
|
-
crossorigin="anonymous"
|
|
14569
|
-
referrerpolicy="no-referrer"
|
|
14570
|
-
/>
|
|
14571
|
-
|
|
14572
|
-
<!-- Style -->
|
|
14573
|
-
<style>
|
|
14574
|
-
.DCEReactKit-pop-in {
|
|
14575
|
-
animation-name: DCEReactKit-pop-in;
|
|
14576
|
-
animation-duration: 0.5s;
|
|
14577
|
-
animation-iteration-count: 1;
|
|
14578
|
-
animation-timing-function: ease-out;
|
|
14579
|
-
animation-fill-mode: both;
|
|
14580
|
-
|
|
14581
|
-
transform-origin: center;
|
|
14582
|
-
}
|
|
14583
|
-
|
|
14584
|
-
@keyframes DCEReactKit-pop-in {
|
|
14585
|
-
0% {
|
|
14586
|
-
opacity: 0;
|
|
14587
|
-
transform: scale(0.9);
|
|
14588
|
-
}
|
|
14589
|
-
100% {
|
|
14590
|
-
opacity: 1;
|
|
14591
|
-
transform: scale(1);
|
|
14592
|
-
}
|
|
14593
|
-
}
|
|
14594
|
-
</style>
|
|
14595
|
-
</head>
|
|
14596
|
-
|
|
14597
|
-
<!-- Body -->
|
|
14598
|
-
<body class="bg-dark text-center pt-3 ps-3 pe-3">
|
|
14599
|
-
<!-- Alert -->
|
|
14600
|
-
<div
|
|
14601
|
-
class="DCEReactKit-pop-in alert alert-info d-inline-block"
|
|
14602
|
-
style="width: 50em; max-width: 100%"
|
|
14603
|
-
>
|
|
14604
|
-
<!-- Title -->
|
|
14605
|
-
<h2>
|
|
14606
|
-
<i class="me-1 fa-solid fa-circle-info"></i>
|
|
14607
|
-
${title}
|
|
14608
|
-
</h2>
|
|
14609
|
-
<!-- Body -->
|
|
14610
|
-
<div>
|
|
14611
|
-
${body}
|
|
14612
|
-
</div>
|
|
14613
|
-
</div>
|
|
14614
|
-
</body>
|
|
14615
|
-
`;
|
|
14616
|
-
};
|
|
14617
|
-
|
|
14618
|
-
/**
|
|
14619
|
-
* Perform a rudimentary parsing of the user's browser agent string
|
|
14620
|
-
* @author Gabe Abrams
|
|
14621
|
-
* @param userAgent the user's browser agent
|
|
14622
|
-
* @returns user info
|
|
14623
|
-
*/
|
|
14624
|
-
const parseUserAgent = (userAgent) => {
|
|
14625
|
-
/* ------------- Browser ------------ */
|
|
14626
|
-
let browser = {
|
|
14627
|
-
name: 'Unknown',
|
|
14628
|
-
version: 'Unknown',
|
|
14629
|
-
};
|
|
14630
|
-
// Parse user agent
|
|
14631
|
-
let verOffset;
|
|
14632
|
-
let nameOffset;
|
|
14633
|
-
if ((verOffset = userAgent.indexOf('Opera')) !== -1) {
|
|
14634
|
-
// In Opera, the true version is after 'Opera' or after 'Version'
|
|
14635
|
-
browser = {
|
|
14636
|
-
name: 'Opera',
|
|
14637
|
-
version: userAgent.substring(verOffset + 6),
|
|
14638
|
-
};
|
|
14639
|
-
if ((verOffset = userAgent.indexOf('Version')) !== -1) {
|
|
14640
|
-
browser.version = userAgent.substring(verOffset + 8);
|
|
14641
|
-
}
|
|
14642
|
-
}
|
|
14643
|
-
else if ((verOffset = userAgent.indexOf('MSIE')) !== -1) {
|
|
14644
|
-
// In MSIE, the true version is after 'MSIE' in userAgent
|
|
14645
|
-
browser = {
|
|
14646
|
-
name: 'Internet Explorer',
|
|
14647
|
-
version: userAgent.substring(verOffset + 5),
|
|
14648
|
-
};
|
|
14649
|
-
}
|
|
14650
|
-
else if ((verOffset = userAgent.indexOf('Chrome')) !== -1) {
|
|
14651
|
-
// In Chrome, the true version is after 'Chrome'
|
|
14652
|
-
browser = {
|
|
14653
|
-
name: 'Chrome',
|
|
14654
|
-
version: userAgent.substring(verOffset + 7),
|
|
14655
|
-
};
|
|
14656
|
-
}
|
|
14657
|
-
else if ((verOffset = userAgent.indexOf('Safari')) !== -1) {
|
|
14658
|
-
// In Safari, the true version is after 'Safari' or after 'Version'
|
|
14659
|
-
browser = {
|
|
14660
|
-
name: 'Safari',
|
|
14661
|
-
version: userAgent.substring(verOffset + 7),
|
|
14662
|
-
};
|
|
14663
|
-
if ((verOffset = userAgent.indexOf('Version')) !== -1) {
|
|
14664
|
-
browser.version = userAgent.substring(verOffset + 8);
|
|
14665
|
-
}
|
|
14666
|
-
}
|
|
14667
|
-
else if ((verOffset = userAgent.indexOf('Firefox')) != -1) {
|
|
14668
|
-
// In Firefox, the true version is after 'Firefox'
|
|
14669
|
-
browser = {
|
|
14670
|
-
name: 'Firefox',
|
|
14671
|
-
version: userAgent.substring(verOffset + 8),
|
|
14672
|
-
};
|
|
14673
|
-
}
|
|
14674
|
-
else if ((nameOffset = userAgent.lastIndexOf(' ') + 1)
|
|
14675
|
-
< (verOffset = userAgent.lastIndexOf('/'))) {
|
|
14676
|
-
browser = {
|
|
14677
|
-
name: userAgent.substring(nameOffset, verOffset),
|
|
14678
|
-
version: userAgent.substring(verOffset + 1),
|
|
14679
|
-
};
|
|
14680
|
-
}
|
|
14681
|
-
// Postprocess version
|
|
14682
|
-
// trim the fullVersion string at semicolon/space if present
|
|
14683
|
-
let ix;
|
|
14684
|
-
if ((ix = browser.version.indexOf(';')) !== -1) {
|
|
14685
|
-
browser.version = browser.version.substring(0, ix);
|
|
14686
|
-
}
|
|
14687
|
-
if ((ix = browser.version.indexOf(' ')) !== -1) {
|
|
14688
|
-
browser.version = browser.version.substring(0, ix);
|
|
14689
|
-
}
|
|
14690
|
-
/* ------------- Device ------------- */
|
|
14691
|
-
// Detect os
|
|
14692
|
-
let os = 'Unknown';
|
|
14693
|
-
if (userAgent.includes('Linux')) {
|
|
14694
|
-
os = 'Linux';
|
|
14695
|
-
}
|
|
14696
|
-
else if (userAgent.includes('like Mac')) {
|
|
14697
|
-
os = 'iOS';
|
|
14698
|
-
}
|
|
14699
|
-
else if (userAgent.includes('Mac')) {
|
|
14700
|
-
os = 'Mac';
|
|
14701
|
-
}
|
|
14702
|
-
else if (userAgent.includes('Android')) {
|
|
14703
|
-
os = 'Android';
|
|
14704
|
-
}
|
|
14705
|
-
else if (userAgent.includes('Win')) {
|
|
14706
|
-
os = 'Win';
|
|
14707
|
-
}
|
|
14708
|
-
// Check if mobile
|
|
14709
|
-
const isMobile = !!userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/);
|
|
14710
|
-
// Device
|
|
14711
|
-
const device = {
|
|
14712
|
-
isMobile,
|
|
14713
|
-
os,
|
|
14714
|
-
};
|
|
14715
|
-
/* ------------- Finish ------------- */
|
|
14716
|
-
// Return info
|
|
14717
|
-
return {
|
|
14718
|
-
browser,
|
|
14719
|
-
device,
|
|
14720
|
-
};
|
|
14721
|
-
};
|
|
14722
|
-
|
|
14723
|
-
/**
|
|
14724
|
-
* Generate an express API route handler
|
|
14725
|
-
* @author Gabe Abrams
|
|
14726
|
-
* @param opts object containing all arguments
|
|
14727
|
-
* @param opts.paramTypes map containing the types for each parameter that is
|
|
14728
|
-
* included in the request (map: param name => type)
|
|
14729
|
-
* @param opts.handler function that processes the request
|
|
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
|
|
14738
|
-
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
14739
|
-
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
14740
|
-
* where the error message is prefixed with this string. For example,
|
|
14741
|
-
* if unhandledErrorMessagePrefix is
|
|
14742
|
-
* 'While saving progress, we encountered an error:'
|
|
14743
|
-
* and the error is 'progressInfo is not an object',
|
|
14744
|
-
* the client will receive an error with the message
|
|
14745
|
-
* 'While saving progress, we encountered an error: progressInfo is not an object'
|
|
14746
|
-
* @returns express route handler that takes the following arguments:
|
|
14747
|
-
* params (map: param name => value),
|
|
14748
|
-
* req (express request object),
|
|
14749
|
-
* next (express next function),
|
|
14750
|
-
* send (a function that sends a string to the client),
|
|
14751
|
-
* redirect (takes a url and redirects the user to that url),
|
|
14752
|
-
* renderErrorPage (shows a static error page to the user),
|
|
14753
|
-
* renderInfoPage (shows a static info page to the user),
|
|
14754
|
-
* renderCustomHTML (renders custom html and sends it to the user),
|
|
14755
|
-
* and returns the value to send to the client as a JSON API response, or
|
|
14756
|
-
* calls next() or redirect(...) or send(...) or renderErrorPage(...).
|
|
14757
|
-
* Note: params also has userId, userFirstName,
|
|
14758
|
-
* userLastName, userEmail, userAvatarURL, isLearner, isTTM, isAdmin,
|
|
14759
|
-
* and any other variables that
|
|
14760
|
-
* are directly added to the session, if the user does have a session.
|
|
14761
|
-
*/
|
|
14762
|
-
const genRouteHandler = (opts) => {
|
|
14763
|
-
// Return a route handler
|
|
14764
|
-
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14765
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
14766
|
-
// Output params
|
|
14767
|
-
const output = {};
|
|
14768
|
-
/*----------------------------------------*/
|
|
14769
|
-
/* ----------- Hostname Check ----------- */
|
|
14770
|
-
/*----------------------------------------*/
|
|
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
|
-
});
|
|
14808
|
-
}
|
|
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();
|
|
14824
|
-
});
|
|
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
|
-
}
|
|
14833
|
-
}
|
|
14834
|
-
/*----------------------------------------*/
|
|
14835
|
-
/* ------------ Parse Params ------------ */
|
|
14836
|
-
/*----------------------------------------*/
|
|
14837
|
-
// Process items one by one
|
|
14838
|
-
const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
|
|
14839
|
-
for (let i = 0; i < paramList.length; i++) {
|
|
14840
|
-
const [name, type] = paramList[i];
|
|
14841
|
-
// Find the value as a string
|
|
14842
|
-
const value = (req.params[name]
|
|
14843
|
-
|| req.query[name]
|
|
14844
|
-
|| req.body[name]);
|
|
14845
|
-
// Parse
|
|
14846
|
-
if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
|
|
14847
|
-
// Boolean
|
|
14848
|
-
// Handle case where value doesn't exist
|
|
14849
|
-
if (value === undefined) {
|
|
14850
|
-
if (type === ParamType$1.BooleanOptional) {
|
|
14851
|
-
output[name] = undefined;
|
|
14852
|
-
}
|
|
14853
|
-
else {
|
|
14854
|
-
return handleError(res, {
|
|
14855
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
14856
|
-
code: ReactKitErrorCode$1.MissingParameter,
|
|
14857
|
-
status: 422,
|
|
14858
|
-
});
|
|
14859
|
-
}
|
|
14860
|
-
}
|
|
14861
|
-
else {
|
|
14862
|
-
// Value exists
|
|
14863
|
-
// Simplify value
|
|
14864
|
-
const simpleVal = (String(value)
|
|
14865
|
-
.trim()
|
|
14866
|
-
.toLowerCase());
|
|
14867
|
-
// Parse
|
|
14868
|
-
output[name] = ([
|
|
14869
|
-
'true',
|
|
14870
|
-
'yes',
|
|
14871
|
-
'y',
|
|
14872
|
-
'1',
|
|
14873
|
-
't',
|
|
14874
|
-
].indexOf(simpleVal) >= 0);
|
|
14875
|
-
}
|
|
14876
|
-
}
|
|
14877
|
-
else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
|
|
14878
|
-
// Float
|
|
14879
|
-
// Handle case where value doesn't exist
|
|
14880
|
-
if (value === undefined) {
|
|
14881
|
-
if (type === ParamType$1.FloatOptional) {
|
|
14882
|
-
output[name] = undefined;
|
|
14883
|
-
}
|
|
14884
|
-
else {
|
|
14885
|
-
return handleError(res, {
|
|
14886
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
14887
|
-
code: ReactKitErrorCode$1.MissingParameter,
|
|
14888
|
-
status: 422,
|
|
14889
|
-
});
|
|
14890
|
-
}
|
|
14891
|
-
}
|
|
14892
|
-
else if (!Number.isNaN(Number.parseFloat(String(value)))) {
|
|
14893
|
-
// Value is a number
|
|
14894
|
-
output[name] = Number.parseFloat(String(value));
|
|
14895
|
-
}
|
|
14896
|
-
else {
|
|
14897
|
-
// Issue!
|
|
14898
|
-
return handleError(res, {
|
|
14899
|
-
message: `Request data was malformed: ${name} was not a valid float.`,
|
|
14900
|
-
code: ReactKitErrorCode$1.InvalidParameter,
|
|
14901
|
-
status: 422,
|
|
14902
|
-
});
|
|
14903
|
-
}
|
|
14904
|
-
}
|
|
14905
|
-
else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
|
|
14906
|
-
// Int
|
|
14907
|
-
// Handle case where value doesn't exist
|
|
14908
|
-
if (value === undefined) {
|
|
14909
|
-
if (type === ParamType$1.IntOptional) {
|
|
14910
|
-
output[name] = undefined;
|
|
14911
|
-
}
|
|
14912
|
-
else {
|
|
14913
|
-
return handleError(res, {
|
|
14914
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
14915
|
-
code: ReactKitErrorCode$1.MissingParameter,
|
|
14916
|
-
status: 422,
|
|
14917
|
-
});
|
|
14918
|
-
}
|
|
14919
|
-
}
|
|
14920
|
-
else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
|
|
14921
|
-
// Value is a number
|
|
14922
|
-
output[name] = Number.parseInt(String(value), 10);
|
|
14923
|
-
}
|
|
14924
|
-
else {
|
|
14925
|
-
// Issue!
|
|
14926
|
-
return handleError(res, {
|
|
14927
|
-
message: `Request data was malformed: ${name} was not a valid int.`,
|
|
14928
|
-
code: ReactKitErrorCode$1.InvalidParameter,
|
|
14929
|
-
status: 422,
|
|
14930
|
-
});
|
|
14931
|
-
}
|
|
14932
|
-
}
|
|
14933
|
-
else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
|
|
14934
|
-
// Stringified JSON
|
|
14935
|
-
// Handle case where value doesn't exist
|
|
14936
|
-
if (value === undefined) {
|
|
14937
|
-
if (type === ParamType$1.JSONOptional) {
|
|
14938
|
-
output[name] = undefined;
|
|
14939
|
-
}
|
|
14940
|
-
else {
|
|
14941
|
-
return handleError(res, {
|
|
14942
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
14943
|
-
code: ReactKitErrorCode$1.MissingParameter,
|
|
14944
|
-
status: 422,
|
|
14945
|
-
});
|
|
14946
|
-
}
|
|
14947
|
-
}
|
|
14948
|
-
else {
|
|
14949
|
-
// Value exists
|
|
14950
|
-
// Parse
|
|
14951
|
-
try {
|
|
14952
|
-
output[name] = JSON.parse(String(value));
|
|
14953
|
-
}
|
|
14954
|
-
catch (err) {
|
|
14955
|
-
return handleError(res, {
|
|
14956
|
-
message: `Request data was malformed: ${name} was not a valid JSON payload.`,
|
|
14957
|
-
code: ReactKitErrorCode$1.InvalidParameter,
|
|
14958
|
-
status: 422,
|
|
14959
|
-
});
|
|
14960
|
-
}
|
|
14961
|
-
}
|
|
14962
|
-
}
|
|
14963
|
-
else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
|
|
14964
|
-
// String
|
|
14965
|
-
// Handle case where value doesn't exist
|
|
14966
|
-
if (value === undefined) {
|
|
14967
|
-
if (type === ParamType$1.StringOptional) {
|
|
14968
|
-
output[name] = undefined;
|
|
14969
|
-
}
|
|
14970
|
-
else {
|
|
14971
|
-
return handleError(res, {
|
|
14972
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
14973
|
-
code: ReactKitErrorCode$1.MissingParameter,
|
|
14974
|
-
status: 422,
|
|
14975
|
-
});
|
|
14976
|
-
}
|
|
14977
|
-
}
|
|
14978
|
-
else {
|
|
14979
|
-
// Value exists
|
|
14980
|
-
// Leave as is
|
|
14981
|
-
output[name] = value;
|
|
14982
|
-
}
|
|
14983
|
-
}
|
|
14984
|
-
else {
|
|
14985
|
-
// No valid data type
|
|
14986
|
-
return handleError(res, {
|
|
14987
|
-
message: `An internal error occurred: we could not determine the type of ${name}.`,
|
|
14988
|
-
code: ReactKitErrorCode$1.InvalidParameter,
|
|
14989
|
-
status: 422,
|
|
14990
|
-
});
|
|
14991
|
-
}
|
|
14992
|
-
}
|
|
14993
|
-
/*----------------------------------------*/
|
|
14994
|
-
/* ------------- Launch Info ------------ */
|
|
14995
|
-
/*----------------------------------------*/
|
|
14996
|
-
// Get launch info
|
|
14997
|
-
const { launched, launchInfo } = cacclGetLaunchInfo(req);
|
|
14998
|
-
if (
|
|
14999
|
-
// Not launched
|
|
15000
|
-
(!launched || !launchInfo)
|
|
15001
|
-
// Not skipping the session check
|
|
15002
|
-
&& !opts.skipSessionCheck) {
|
|
15003
|
-
return handleError(res, {
|
|
15004
|
-
message: 'Your session has expired. Please refresh the page and try again.',
|
|
15005
|
-
code: ReactKitErrorCode$1.SessionExpired,
|
|
15006
|
-
status: 401,
|
|
15007
|
-
});
|
|
15008
|
-
}
|
|
15009
|
-
// Error if user info cannot be found
|
|
15010
|
-
if (
|
|
15011
|
-
// User information is incomplete
|
|
15012
|
-
(!launchInfo
|
|
15013
|
-
|| !launchInfo.userId
|
|
15014
|
-
|| !launchInfo.userFirstName
|
|
15015
|
-
|| !launchInfo.userLastName
|
|
15016
|
-
|| (launchInfo.notInCourse
|
|
15017
|
-
&& !launchInfo.isAdmin)
|
|
15018
|
-
|| (!launchInfo.isTTM
|
|
15019
|
-
&& !launchInfo.isLearner
|
|
15020
|
-
&& !launchInfo.isAdmin))
|
|
15021
|
-
// Not skipping the session check
|
|
15022
|
-
&& !opts.skipSessionCheck) {
|
|
15023
|
-
return handleError(res, {
|
|
15024
|
-
message: 'Your session was invalid. Please refresh the page and try again.',
|
|
15025
|
-
code: ReactKitErrorCode$1.SessionExpired,
|
|
15026
|
-
status: 401,
|
|
15027
|
-
});
|
|
15028
|
-
}
|
|
15029
|
-
// Add launch info to output
|
|
15030
|
-
output.userId = (launchInfo
|
|
15031
|
-
? launchInfo.userId
|
|
15032
|
-
: ((_b = output.userId) !== null && _b !== void 0 ? _b : undefined));
|
|
15033
|
-
output.userFirstName = (launchInfo
|
|
15034
|
-
? launchInfo.userFirstName
|
|
15035
|
-
: ((_c = output.userFirstName) !== null && _c !== void 0 ? _c : undefined));
|
|
15036
|
-
output.userLastName = (launchInfo
|
|
15037
|
-
? launchInfo.userLastName
|
|
15038
|
-
: ((_d = output.userLastName) !== null && _d !== void 0 ? _d : undefined));
|
|
15039
|
-
output.userEmail = (launchInfo
|
|
15040
|
-
? launchInfo.userEmail
|
|
15041
|
-
: ((_e = output.userEmail) !== null && _e !== void 0 ? _e : undefined));
|
|
15042
|
-
output.userAvatarURL = (launchInfo
|
|
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));
|
|
15045
|
-
output.isLearner = (launchInfo
|
|
15046
|
-
? !!launchInfo.isLearner
|
|
15047
|
-
: ((_h = output.isLearner) !== null && _h !== void 0 ? _h : undefined));
|
|
15048
|
-
output.isTTM = (launchInfo
|
|
15049
|
-
? !!launchInfo.isTTM
|
|
15050
|
-
: ((_j = output.isTTM) !== null && _j !== void 0 ? _j : undefined));
|
|
15051
|
-
output.isAdmin = (launchInfo
|
|
15052
|
-
? !!launchInfo.isAdmin
|
|
15053
|
-
: ((_k = output.isAdmin) !== null && _k !== void 0 ? _k : undefined));
|
|
15054
|
-
output.courseId = (launchInfo
|
|
15055
|
-
? ((_l = output.courseId) !== null && _l !== void 0 ? _l : launchInfo.courseId)
|
|
15056
|
-
: ((_m = output.courseId) !== null && _m !== void 0 ? _m : undefined));
|
|
15057
|
-
output.courseName = (launchInfo
|
|
15058
|
-
? launchInfo.contextLabel
|
|
15059
|
-
: ((_o = output.courseName) !== null && _o !== void 0 ? _o : undefined));
|
|
15060
|
-
// Add other session variables
|
|
15061
|
-
Object.keys(req.session).forEach((propName) => {
|
|
15062
|
-
// Skip if prop already in output
|
|
15063
|
-
if (output[propName] !== undefined) {
|
|
15064
|
-
return;
|
|
15065
|
-
}
|
|
15066
|
-
// Add to output
|
|
15067
|
-
const value = req.session[propName];
|
|
15068
|
-
if (typeof value === 'string'
|
|
15069
|
-
|| typeof value === 'boolean'
|
|
15070
|
-
|| typeof value === 'number') {
|
|
15071
|
-
output[propName] = value;
|
|
15072
|
-
}
|
|
15073
|
-
});
|
|
15074
|
-
/*----------------------------------------*/
|
|
15075
|
-
/* ----- Require Course Consistency ----- */
|
|
15076
|
-
/*----------------------------------------*/
|
|
15077
|
-
// Make sure the user actually launched from the appropriate course
|
|
15078
|
-
if (output.courseId
|
|
15079
|
-
&& launchInfo
|
|
15080
|
-
&& launchInfo.courseId
|
|
15081
|
-
&& output.courseId !== launchInfo.courseId
|
|
15082
|
-
&& !output.isTTM
|
|
15083
|
-
&& !output.isAdmin) {
|
|
15084
|
-
// Course of interest is not the launch course
|
|
15085
|
-
return handleError(res, {
|
|
15086
|
-
message: 'You switched sessions by opening this app in another tab. Please refresh the page and try again.',
|
|
15087
|
-
code: ReactKitErrorCode$1.WrongCourse,
|
|
15088
|
-
status: 401,
|
|
15089
|
-
});
|
|
15090
|
-
}
|
|
15091
|
-
/*----------------------------------------*/
|
|
15092
|
-
/* Require Proper Permissions */
|
|
15093
|
-
/*----------------------------------------*/
|
|
15094
|
-
// Add TTM endpoint security
|
|
15095
|
-
if (
|
|
15096
|
-
// This is a TTM endpoint
|
|
15097
|
-
req.path.startsWith('/api/ttm')
|
|
15098
|
-
// User is not a TTM
|
|
15099
|
-
&& (
|
|
15100
|
-
// User is not a TTM
|
|
15101
|
-
!output.isTTM
|
|
15102
|
-
// User is not an admin
|
|
15103
|
-
&& !output.isAdmin)) {
|
|
15104
|
-
// User does not have access
|
|
15105
|
-
return handleError(res, {
|
|
15106
|
-
message: 'This action is only allowed if you are a teaching team member for the course. Please go back to Canvas, log in as a teaching team member, and try again.',
|
|
15107
|
-
code: ReactKitErrorCode$1.NotTTM,
|
|
15108
|
-
status: 401,
|
|
15109
|
-
});
|
|
15110
|
-
}
|
|
15111
|
-
// Add Admin endpoint security
|
|
15112
|
-
if (
|
|
15113
|
-
// This is an admin endpoint
|
|
15114
|
-
req.path.startsWith('/api/admin')
|
|
15115
|
-
// User is not an admin
|
|
15116
|
-
&& !output.isAdmin) {
|
|
15117
|
-
// User does not have access
|
|
15118
|
-
return handleError(res, {
|
|
15119
|
-
message: 'This action is only allowed if you are a Canvas admin. Please go back to Canvas, log in as an admin, and try again.',
|
|
15120
|
-
code: ReactKitErrorCode$1.NotAdmin,
|
|
15121
|
-
status: 401,
|
|
15122
|
-
});
|
|
15123
|
-
}
|
|
15124
|
-
/*----------------------------------------*/
|
|
15125
|
-
/* ------------- Log Handler ------------ */
|
|
15126
|
-
/*----------------------------------------*/
|
|
15127
|
-
// Create a log handler function
|
|
15128
|
-
/**
|
|
15129
|
-
* Log an event on the server
|
|
15130
|
-
* @author Gabe Abrams
|
|
15131
|
-
*/
|
|
15132
|
-
const logServerEvent = (logOpts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15133
|
-
var _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
|
|
15134
|
-
// NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
|
|
15135
|
-
// that indicates that this is actually a client event, but we don't
|
|
15136
|
-
// include that in the LogFunction type because this is internal and
|
|
15137
|
-
// hidden from users
|
|
15138
|
-
try {
|
|
15139
|
-
// Parse user agent
|
|
15140
|
-
const { browser, device, } = parseUserAgent(req.headers['user-agent']);
|
|
15141
|
-
// Get time info in ET
|
|
15142
|
-
const { timestamp, year, month, day, hour, minute, } = getTimeInfoInET();
|
|
15143
|
-
// Main log info
|
|
15144
|
-
const mainLogInfo = {
|
|
15145
|
-
id: `${launchInfo ? launchInfo.userId : 'unknown'}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
|
|
15146
|
-
userFirstName: (launchInfo ? launchInfo.userFirstName : 'unknown'),
|
|
15147
|
-
userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'),
|
|
15148
|
-
userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'),
|
|
15149
|
-
userId: (launchInfo ? launchInfo.userId : 'unknown'),
|
|
15150
|
-
isLearner: (launchInfo && !!launchInfo.isLearner),
|
|
15151
|
-
isAdmin: (launchInfo && !!launchInfo.isAdmin),
|
|
15152
|
-
isTTM: (launchInfo && !!launchInfo.isTTM),
|
|
15153
|
-
courseId: (launchInfo ? launchInfo.courseId : 'unknown'),
|
|
15154
|
-
courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'),
|
|
15155
|
-
browser,
|
|
15156
|
-
device,
|
|
15157
|
-
year,
|
|
15158
|
-
month,
|
|
15159
|
-
day,
|
|
15160
|
-
hour,
|
|
15161
|
-
minute,
|
|
15162
|
-
timestamp,
|
|
15163
|
-
context: (typeof logOpts.context === 'string'
|
|
15164
|
-
? logOpts.context
|
|
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 : {}),
|
|
15170
|
-
};
|
|
15171
|
-
// Type-specific info
|
|
15172
|
-
const typeSpecificInfo = (('error' in opts && opts.error)
|
|
15173
|
-
? {
|
|
15174
|
-
type: LogType$1.Error,
|
|
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',
|
|
15178
|
-
}
|
|
15179
|
-
: {
|
|
15180
|
-
type: LogType$1.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),
|
|
15183
|
-
});
|
|
15184
|
-
// Source-specific info
|
|
15185
|
-
const sourceSpecificInfo = (logOpts.overrideAsClientEvent
|
|
15186
|
-
? {
|
|
15187
|
-
source: LogSource$1.Client,
|
|
15188
|
-
}
|
|
15189
|
-
: {
|
|
15190
|
-
source: LogSource$1.Server,
|
|
15191
|
-
routePath: req.path,
|
|
15192
|
-
routeTemplate: req.route.path,
|
|
15193
|
-
});
|
|
15194
|
-
// Build log event
|
|
15195
|
-
const log = Object.assign(Object.assign(Object.assign({}, mainLogInfo), typeSpecificInfo), sourceSpecificInfo);
|
|
15196
|
-
// Either print to console or save to db
|
|
15197
|
-
const logCollection = internalGetLogCollection();
|
|
15198
|
-
if (logCollection) {
|
|
15199
|
-
// Store to the log collection
|
|
15200
|
-
yield logCollection.insert(log);
|
|
15201
|
-
}
|
|
15202
|
-
else if (log.type === LogType$1.Error) {
|
|
15203
|
-
// Print to console
|
|
15204
|
-
// eslint-disable-next-line no-console
|
|
15205
|
-
console.error('dce-reactkit error log:', log);
|
|
15206
|
-
}
|
|
15207
|
-
else {
|
|
15208
|
-
// eslint-disable-next-line no-console
|
|
15209
|
-
console.log('dce-reactkit action log:', log);
|
|
15210
|
-
}
|
|
15211
|
-
// Return log entry
|
|
15212
|
-
return log;
|
|
15213
|
-
}
|
|
15214
|
-
catch (err) {
|
|
15215
|
-
// Print because we cannot store the error
|
|
15216
|
-
// eslint-disable-next-line no-console
|
|
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);
|
|
15218
|
-
// Create a dummy log to return
|
|
15219
|
-
const dummyMainInfo = {
|
|
15220
|
-
id: '-1',
|
|
15221
|
-
userFirstName: 'Unknown',
|
|
15222
|
-
userLastName: 'Unknown',
|
|
15223
|
-
userEmail: 'unknown@harvard.edu',
|
|
15224
|
-
userId: 1,
|
|
15225
|
-
isLearner: false,
|
|
15226
|
-
isAdmin: false,
|
|
15227
|
-
isTTM: false,
|
|
15228
|
-
courseId: 1,
|
|
15229
|
-
courseName: 'Unknown',
|
|
15230
|
-
browser: {
|
|
15231
|
-
name: 'Unknown',
|
|
15232
|
-
version: 'Unknown',
|
|
15233
|
-
},
|
|
15234
|
-
device: {
|
|
15235
|
-
isMobile: false,
|
|
15236
|
-
os: 'Unknown',
|
|
15237
|
-
},
|
|
15238
|
-
year: 1,
|
|
15239
|
-
month: 1,
|
|
15240
|
-
day: 1,
|
|
15241
|
-
hour: 1,
|
|
15242
|
-
minute: 1,
|
|
15243
|
-
timestamp: Date.now(),
|
|
15244
|
-
tags: [],
|
|
15245
|
-
level: LogLevel$1.Warn,
|
|
15246
|
-
metadata: {},
|
|
15247
|
-
context: LogBuiltInMetadata.Context.Uncategorized,
|
|
15248
|
-
subcontext: LogBuiltInMetadata.Context.Uncategorized,
|
|
15249
|
-
};
|
|
15250
|
-
const dummyTypeSpecificInfo = {
|
|
15251
|
-
type: LogType$1.Error,
|
|
15252
|
-
errorMessage: 'Unknown',
|
|
15253
|
-
errorCode: 'Unknown',
|
|
15254
|
-
errorStack: 'No Stack',
|
|
15255
|
-
};
|
|
15256
|
-
const dummySourceSpecificInfo = {
|
|
15257
|
-
source: LogSource$1.Server,
|
|
15258
|
-
routePath: req.path,
|
|
15259
|
-
routeTemplate: req.route.path,
|
|
15260
|
-
};
|
|
15261
|
-
const log = Object.assign(Object.assign(Object.assign({}, dummyMainInfo), dummyTypeSpecificInfo), dummySourceSpecificInfo);
|
|
15262
|
-
return log;
|
|
15263
|
-
}
|
|
15264
|
-
});
|
|
15265
|
-
/*------------------------------------------------------------------------*/
|
|
15266
|
-
/* Call handler */
|
|
15267
|
-
/*------------------------------------------------------------------------*/
|
|
15268
|
-
// Keep track of whether a response was already sent
|
|
15269
|
-
let responseSent = false;
|
|
15270
|
-
/**
|
|
15271
|
-
* Redirect the user to another path or url
|
|
15272
|
-
* @author Gabe Abrams
|
|
15273
|
-
* @param pathOrURL the path or url to redirect to
|
|
15274
|
-
*/
|
|
15275
|
-
const redirect = (pathOrURL) => {
|
|
15276
|
-
responseSent = true;
|
|
15277
|
-
res.redirect(pathOrURL);
|
|
15278
|
-
};
|
|
15279
|
-
/**
|
|
15280
|
-
* Send text to the client (with an optional status code)
|
|
15281
|
-
* @author Gabe Abrams
|
|
15282
|
-
* @param text the text to send to the client
|
|
15283
|
-
* @parm [status=200] the http status code to send
|
|
15284
|
-
*/
|
|
15285
|
-
const send = (text, status = 200) => {
|
|
15286
|
-
responseSent = true;
|
|
15287
|
-
res.status(status).send(text);
|
|
15288
|
-
};
|
|
15289
|
-
/**
|
|
15290
|
-
* Render an error page
|
|
15291
|
-
* @author Gabe Abrams
|
|
15292
|
-
* @param renderOpts object containing all arguments
|
|
15293
|
-
* @param [renderOpts.title=An Error Occurred] title of the error box
|
|
15294
|
-
* @param [renderOpts.description=An unknown server error occurred. Please contact support.]
|
|
15295
|
-
* a human-readable description of the error
|
|
15296
|
-
* @param [renderOpts.code=ReactKitErrorCode.NoCode] error code to show
|
|
15297
|
-
* @param [renderOpts.pageTitle=renderOpts.title] title of the page/tab if it differs from
|
|
15298
|
-
* the title of the error
|
|
15299
|
-
* @param [renderOpts.status=500] http status code
|
|
15300
|
-
*/
|
|
15301
|
-
const renderErrorPage = (renderOpts = {}) => {
|
|
15302
|
-
var _a, _b, _c;
|
|
15303
|
-
const html = genErrorPage(renderOpts);
|
|
15304
|
-
send(html, (_a = renderOpts.status) !== null && _a !== void 0 ? _a : 500);
|
|
15305
|
-
// Log server-side error if not a session expired error or 404
|
|
15306
|
-
if (renderOpts.status && renderOpts.status === 404) {
|
|
15307
|
-
return;
|
|
15308
|
-
}
|
|
15309
|
-
if ((_b = renderOpts.title) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('session expired')) {
|
|
15310
|
-
return;
|
|
15311
|
-
}
|
|
15312
|
-
logServerEvent({
|
|
15313
|
-
context: LogBuiltInMetadata.Context.ServerRenderedErrorPage,
|
|
15314
|
-
error: {
|
|
15315
|
-
message: `${renderOpts.title}: ${renderOpts.description}`,
|
|
15316
|
-
code: renderOpts.code,
|
|
15317
|
-
},
|
|
15318
|
-
metadata: {
|
|
15319
|
-
title: renderOpts.title,
|
|
15320
|
-
description: renderOpts.description,
|
|
15321
|
-
code: renderOpts.code,
|
|
15322
|
-
pageTitle: renderOpts.pageTitle,
|
|
15323
|
-
status: (_c = renderOpts.status) !== null && _c !== void 0 ? _c : 500,
|
|
15324
|
-
},
|
|
15325
|
-
});
|
|
15326
|
-
};
|
|
15327
|
-
/**
|
|
15328
|
-
* Render an info page
|
|
15329
|
-
* @author Gabe Abrams
|
|
15330
|
-
* @param renderOpts object containing all arguments
|
|
15331
|
-
* @param renderOpts.title title of the info box
|
|
15332
|
-
* @param renderOpts.body a human-readable text body for the info alert
|
|
15333
|
-
*/
|
|
15334
|
-
const renderInfoPage = (renderOpts) => {
|
|
15335
|
-
const html = genInfoPage(renderOpts);
|
|
15336
|
-
send(html, 200);
|
|
15337
|
-
};
|
|
15338
|
-
/**
|
|
15339
|
-
* Render custom HTML
|
|
15340
|
-
* @author Gabe Abrams
|
|
15341
|
-
* @param htmlOpts object containing all arguments
|
|
15342
|
-
* @param htmlOpts.html the HTML to send to the client
|
|
15343
|
-
* @param [ejsOpts.status=200] the http status code to send
|
|
15344
|
-
*/
|
|
15345
|
-
const renderCustomHTML = (htmlOpts) => {
|
|
15346
|
-
var _a;
|
|
15347
|
-
send(htmlOpts.html, (_a = htmlOpts.status) !== null && _a !== void 0 ? _a : 200);
|
|
15348
|
-
};
|
|
15349
|
-
// Call the handler
|
|
15350
|
-
try {
|
|
15351
|
-
const results = yield opts.handler({
|
|
15352
|
-
params: output,
|
|
15353
|
-
req,
|
|
15354
|
-
send,
|
|
15355
|
-
next: () => {
|
|
15356
|
-
responseSent = true;
|
|
15357
|
-
next();
|
|
15358
|
-
},
|
|
15359
|
-
redirect,
|
|
15360
|
-
renderErrorPage,
|
|
15361
|
-
renderInfoPage,
|
|
15362
|
-
renderCustomHTML,
|
|
15363
|
-
logServerEvent,
|
|
15364
|
-
});
|
|
15365
|
-
// Send results to client (only if next wasn't called)
|
|
15366
|
-
if (!responseSent) {
|
|
15367
|
-
return handleSuccess(res, results !== null && results !== void 0 ? results : undefined);
|
|
15368
|
-
}
|
|
15369
|
-
}
|
|
15370
|
-
catch (err) {
|
|
15371
|
-
// Prefix error message if needed
|
|
15372
|
-
if (opts.unhandledErrorMessagePrefix
|
|
15373
|
-
&& err instanceof Error
|
|
15374
|
-
&& err.message
|
|
15375
|
-
&& err.name !== 'ErrorWithCode') {
|
|
15376
|
-
err.message = `${opts.unhandledErrorMessagePrefix.trim()} ${err.message.trim()}`;
|
|
15377
|
-
}
|
|
15378
|
-
// Send error to client (only if next wasn't called)
|
|
15379
|
-
if (!responseSent) {
|
|
15380
|
-
handleError(res, err);
|
|
15381
|
-
// Log server-side error
|
|
15382
|
-
logServerEvent({
|
|
15383
|
-
context: LogBuiltInMetadata.Context.ServerEndpointError,
|
|
15384
|
-
error: err,
|
|
15385
|
-
});
|
|
15386
|
-
return;
|
|
15387
|
-
}
|
|
15388
|
-
// Log error that was not responded with
|
|
15389
|
-
// eslint-disable-next-line no-console
|
|
15390
|
-
console.log('Error occurred but could not be sent to client because a response was already sent:', err);
|
|
15391
|
-
}
|
|
15392
|
-
});
|
|
15393
|
-
};
|
|
15394
|
-
|
|
15395
|
-
/**
|
|
15396
|
-
* Stub a server endpoint response
|
|
15397
|
-
* @author Gabe Abrams
|
|
15398
|
-
* @param opts object containing all arguments
|
|
15399
|
-
* @param [opts.method=GET] http method to stub
|
|
15400
|
-
* @param opts.path full pathname to stub
|
|
15401
|
-
* @param opts.body body of response if stubbing a successful response
|
|
15402
|
-
* @param opts.errorMessage message of error if stubbing a failed response
|
|
15403
|
-
* @param [opts.errorCode] error code if stubbing a failed response
|
|
15404
|
-
*/
|
|
15405
|
-
const stubServerEndpoint = _setStubResponse;
|
|
15406
|
-
|
|
15407
|
-
/**
|
|
15408
|
-
* Start a dynamic wait, call the function once the operation has completed and
|
|
15409
|
-
* the dynamic wait will continue waiting for the rest of the minimum time
|
|
15410
|
-
* @author Gabe Abrams
|
|
15411
|
-
* @param minWaitMs the minimum number of ms to wait
|
|
15412
|
-
* @returns async function to call to finish the wait
|
|
14055
|
+
* @param minWaitMs the minimum number of ms to wait
|
|
14056
|
+
* @returns async function to call to finish the wait
|
|
15413
14057
|
*/
|
|
15414
14058
|
const startMinWait = (minWaitMs) => {
|
|
15415
14059
|
const startTimestamp = Date.now();
|
|
@@ -15545,27 +14189,11 @@ const parallelLimit = (taskFunctions, limit) => __awaiter(void 0, void 0, void 0
|
|
|
15545
14189
|
});
|
|
15546
14190
|
|
|
15547
14191
|
/**
|
|
15548
|
-
*
|
|
14192
|
+
* Route for checking the status of the current user's
|
|
14193
|
+
* access to log review
|
|
15549
14194
|
* @author Gabe Abrams
|
|
15550
|
-
* @param Collection the Collection class from dce-mango
|
|
15551
|
-
* @returns initialized logCollection
|
|
15552
14195
|
*/
|
|
15553
|
-
const
|
|
15554
|
-
return new Collection('Log', {
|
|
15555
|
-
uniqueIndexKey: 'id',
|
|
15556
|
-
indexKeys: [
|
|
15557
|
-
'courseId',
|
|
15558
|
-
'context',
|
|
15559
|
-
'subcontext',
|
|
15560
|
-
'tags',
|
|
15561
|
-
'year',
|
|
15562
|
-
'month',
|
|
15563
|
-
'day',
|
|
15564
|
-
'hour',
|
|
15565
|
-
'type',
|
|
15566
|
-
],
|
|
15567
|
-
});
|
|
15568
|
-
};
|
|
14196
|
+
const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
|
|
15569
14197
|
|
|
15570
14198
|
/*------------------------------------------------------------------------*/
|
|
15571
14199
|
/* -------------------------------- Cache ------------------------------- */
|
|
@@ -15707,66 +14335,6 @@ const getLocalTimeInfo = (dateOrTimestamp) => {
|
|
|
15707
14335
|
};
|
|
15708
14336
|
};
|
|
15709
14337
|
|
|
15710
|
-
/**
|
|
15711
|
-
* Add all routes for the DBEditor
|
|
15712
|
-
* @author Yuen Ler Chow
|
|
15713
|
-
* @param opts object containing all arguments
|
|
15714
|
-
* @param opts.app express app to add routes too
|
|
15715
|
-
* @param opts.collectionName the name of the collection
|
|
15716
|
-
* @param opts.adminsOnly true if the endpoint is for admins only
|
|
15717
|
-
* @param opts.collection dce-mango db collection
|
|
15718
|
-
*/
|
|
15719
|
-
const addDBEditorEndpoints = (opts) => {
|
|
15720
|
-
const { app, collectionName, adminsOnly, collection, } = opts;
|
|
15721
|
-
// Generate the endpoint path
|
|
15722
|
-
const endpointPath = generateEndpointPath(collectionName, adminsOnly);
|
|
15723
|
-
/**
|
|
15724
|
-
* List all items in the collection
|
|
15725
|
-
* @author Yuen Ler Chow
|
|
15726
|
-
* @returns {any[]} the list of items in the collection
|
|
15727
|
-
*/
|
|
15728
|
-
app.get(endpointPath, genRouteHandler({
|
|
15729
|
-
paramTypes: {
|
|
15730
|
-
filterQuery: ParamType$1.JSONOptional,
|
|
15731
|
-
},
|
|
15732
|
-
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15733
|
-
var _a;
|
|
15734
|
-
const filterQuery = (_a = params.filterQuery) !== null && _a !== void 0 ? _a : {};
|
|
15735
|
-
const categories = yield collection.find(filterQuery);
|
|
15736
|
-
return categories;
|
|
15737
|
-
}),
|
|
15738
|
-
}));
|
|
15739
|
-
/**
|
|
15740
|
-
* Create a new item in the collection
|
|
15741
|
-
* @author Yuen Ler Chow
|
|
15742
|
-
* @param {any} item the item to create
|
|
15743
|
-
*/
|
|
15744
|
-
app.post(endpointPath, genRouteHandler({
|
|
15745
|
-
paramTypes: {
|
|
15746
|
-
item: ParamType$1.JSON,
|
|
15747
|
-
},
|
|
15748
|
-
handler: ({ params, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15749
|
-
// Destructure params
|
|
15750
|
-
const { item, } = params;
|
|
15751
|
-
yield collection.insert(item);
|
|
15752
|
-
}),
|
|
15753
|
-
}));
|
|
15754
|
-
/**
|
|
15755
|
-
* Remove an item from the collection by id
|
|
15756
|
-
* @author Yuen Ler Chow
|
|
15757
|
-
*/
|
|
15758
|
-
app.delete(`${endpointPath}/:id`, genRouteHandler({
|
|
15759
|
-
paramTypes: {
|
|
15760
|
-
id: ParamType$1.String,
|
|
15761
|
-
},
|
|
15762
|
-
handler: ({ params, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15763
|
-
// Destructure params
|
|
15764
|
-
const { id, } = params;
|
|
15765
|
-
yield collection.delete({ id });
|
|
15766
|
-
}),
|
|
15767
|
-
}));
|
|
15768
|
-
};
|
|
15769
|
-
|
|
15770
14338
|
/**
|
|
15771
14339
|
* Given an array of strings, create a single comma-separated string that includes
|
|
15772
14340
|
* 'and' as well as an oxford comma.
|
|
@@ -16308,203 +14876,6 @@ const shuffleArray = (arr) => {
|
|
|
16308
14876
|
return newArr;
|
|
16309
14877
|
};
|
|
16310
14878
|
|
|
16311
|
-
/**
|
|
16312
|
-
* Sends and retries an http request
|
|
16313
|
-
* @author Gabriel Abrams
|
|
16314
|
-
* @param opts object containing all arguments
|
|
16315
|
-
* @param opts.path path to send request to
|
|
16316
|
-
* @param [opts.host] host to send request to
|
|
16317
|
-
* @param [opts.method=GET] http method to use
|
|
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
|
|
16322
|
-
* @param [opts.responseType=JSON] expected response type
|
|
16323
|
-
* @returns { body, status, headers } on success
|
|
16324
|
-
*/
|
|
16325
|
-
const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16326
|
-
var _a;
|
|
16327
|
-
// Process method
|
|
16328
|
-
const method = (opts.method || 'GET');
|
|
16329
|
-
// Encode objects within params
|
|
16330
|
-
let params;
|
|
16331
|
-
if (opts.params) {
|
|
16332
|
-
params = {};
|
|
16333
|
-
Object.entries(opts.params).forEach(([key, val]) => {
|
|
16334
|
-
if (typeof val === 'object' && !Array.isArray(val)) {
|
|
16335
|
-
params[key] = JSON.stringify(val);
|
|
16336
|
-
}
|
|
16337
|
-
else {
|
|
16338
|
-
params[key] = val;
|
|
16339
|
-
}
|
|
16340
|
-
});
|
|
16341
|
-
}
|
|
16342
|
-
// Stringify parameters
|
|
16343
|
-
const stringifiedParams = qs__default["default"].stringify(params || {}, {
|
|
16344
|
-
encodeValuesOnly: true,
|
|
16345
|
-
arrayFormat: 'brackets',
|
|
16346
|
-
});
|
|
16347
|
-
// Create url (include query if GET)
|
|
16348
|
-
const query = (method === 'GET' ? `?${stringifiedParams}` : '');
|
|
16349
|
-
let url;
|
|
16350
|
-
if (!opts.host) {
|
|
16351
|
-
// No host included at all. Just send to a path
|
|
16352
|
-
url = `${opts.path}${query}`;
|
|
16353
|
-
}
|
|
16354
|
-
else {
|
|
16355
|
-
url = `https://${opts.host}${opts.path}${query}`;
|
|
16356
|
-
}
|
|
16357
|
-
// Update headers
|
|
16358
|
-
const headers = opts.headers || {};
|
|
16359
|
-
let data = null;
|
|
16360
|
-
if (!headers['Content-Type']) {
|
|
16361
|
-
// Form encoded
|
|
16362
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
16363
|
-
// Add data if applicable
|
|
16364
|
-
data = (method !== 'GET' ? stringifiedParams : null);
|
|
16365
|
-
}
|
|
16366
|
-
else {
|
|
16367
|
-
// JSON encode
|
|
16368
|
-
data = params;
|
|
16369
|
-
}
|
|
16370
|
-
// Encode data
|
|
16371
|
-
let encodedData;
|
|
16372
|
-
if (data) {
|
|
16373
|
-
if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
|
|
16374
|
-
encodedData = new URLSearchParams(params);
|
|
16375
|
-
}
|
|
16376
|
-
else {
|
|
16377
|
-
encodedData = JSON.stringify(data);
|
|
16378
|
-
}
|
|
16379
|
-
}
|
|
16380
|
-
// Send request
|
|
16381
|
-
try {
|
|
16382
|
-
const response = yield fetch(url, {
|
|
16383
|
-
method,
|
|
16384
|
-
mode: 'cors',
|
|
16385
|
-
headers: headers !== null && headers !== void 0 ? headers : {},
|
|
16386
|
-
body: ((method !== 'GET' && encodedData)
|
|
16387
|
-
? encodedData
|
|
16388
|
-
: undefined),
|
|
16389
|
-
redirect: 'follow',
|
|
16390
|
-
});
|
|
16391
|
-
// Get headers map
|
|
16392
|
-
const responseHeaders = {};
|
|
16393
|
-
response.headers.forEach((value, key) => {
|
|
16394
|
-
responseHeaders[key] = value;
|
|
16395
|
-
});
|
|
16396
|
-
// Process response based on responseType
|
|
16397
|
-
try {
|
|
16398
|
-
// Parse response
|
|
16399
|
-
let responseBody;
|
|
16400
|
-
if (opts.responseType
|
|
16401
|
-
&& opts.responseType === 'Text') {
|
|
16402
|
-
// Response type is text
|
|
16403
|
-
responseBody = yield response.text();
|
|
16404
|
-
}
|
|
16405
|
-
else {
|
|
16406
|
-
// Response type is JSON
|
|
16407
|
-
responseBody = yield response.json();
|
|
16408
|
-
}
|
|
16409
|
-
// Return response
|
|
16410
|
-
return {
|
|
16411
|
-
body: responseBody,
|
|
16412
|
-
status: response.status,
|
|
16413
|
-
headers: responseHeaders,
|
|
16414
|
-
};
|
|
16415
|
-
}
|
|
16416
|
-
catch (err) {
|
|
16417
|
-
throw new ErrorWithCode(`Failed to parse response as ${opts.responseType}: ${err === null || err === void 0 ? void 0 : err.message}`, ReactKitErrorCode$1.ResponseParseError);
|
|
16418
|
-
}
|
|
16419
|
-
}
|
|
16420
|
-
catch (err) {
|
|
16421
|
-
// Self-signed certificate error:
|
|
16422
|
-
if ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes('self signed certificate')) {
|
|
16423
|
-
throw new ErrorWithCode('We refused to send a request because the receiver has self-signed certificates.', ReactKitErrorCode$1.SelfSigned);
|
|
16424
|
-
}
|
|
16425
|
-
// No tries left
|
|
16426
|
-
throw new ErrorWithCode(`We encountered an error when trying to send a network request. If this issue persists, contact an admin. Error: ${err === null || err === void 0 ? void 0 : err.message}`, ReactKitErrorCode$1.NotConnected);
|
|
16427
|
-
}
|
|
16428
|
-
});
|
|
16429
|
-
|
|
16430
|
-
/**
|
|
16431
|
-
* Send a server-to-server request from this sever to another server that uses
|
|
16432
|
-
* dce-reactkit [for server only]
|
|
16433
|
-
* @author Gabe Abrams
|
|
16434
|
-
* @param opts object containing all arguments
|
|
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
|
|
16441
|
-
*/
|
|
16442
|
-
const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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
|
-
}
|
|
16464
|
-
// Send the request
|
|
16465
|
-
const response = yield sendServerToServerRequest({
|
|
16466
|
-
host: opts.host,
|
|
16467
|
-
path: opts.path,
|
|
16468
|
-
method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
|
|
16469
|
-
params,
|
|
16470
|
-
});
|
|
16471
|
-
// Check for failure
|
|
16472
|
-
if (!response || !response.body) {
|
|
16473
|
-
throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
|
|
16474
|
-
}
|
|
16475
|
-
if (!response.body.success) {
|
|
16476
|
-
// Other errors
|
|
16477
|
-
throw new ErrorWithCode((response.body.message
|
|
16478
|
-
|| 'An unknown error occurred. Please contact an admin.'), (response.body.code
|
|
16479
|
-
|| ReactKitErrorCode$1.NoCode));
|
|
16480
|
-
}
|
|
16481
|
-
// Success! Extract the body
|
|
16482
|
-
const { body } = response.body;
|
|
16483
|
-
// Return
|
|
16484
|
-
return body;
|
|
16485
|
-
});
|
|
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
|
-
|
|
16508
14879
|
/**
|
|
16509
14880
|
* Days of the week
|
|
16510
14881
|
* @author Gabe Abrams
|
|
@@ -16543,6 +14914,7 @@ exports.ItemPicker = ItemPicker;
|
|
|
16543
14914
|
exports.LoadingSpinner = LoadingSpinner;
|
|
16544
14915
|
exports.LogAction = LogAction$1;
|
|
16545
14916
|
exports.LogBuiltInMetadata = LogBuiltInMetadata;
|
|
14917
|
+
exports.LogLevel = LogLevel$1;
|
|
16546
14918
|
exports.LogReviewer = LogReviewer;
|
|
16547
14919
|
exports.LogSource = LogSource$1;
|
|
16548
14920
|
exports.LogType = LogType$1;
|
|
@@ -16564,7 +14936,6 @@ exports.ToggleSwitch = ToggleSwitch;
|
|
|
16564
14936
|
exports.Tooltip = Tooltip;
|
|
16565
14937
|
exports.Variant = Variant$1;
|
|
16566
14938
|
exports.abbreviate = abbreviate;
|
|
16567
|
-
exports.addDBEditorEndpoints = addDBEditorEndpoints;
|
|
16568
14939
|
exports.addFatalErrorHandler = addFatalErrorHandler;
|
|
16569
14940
|
exports.alert = alert;
|
|
16570
14941
|
exports.avg = avg;
|
|
@@ -16582,20 +14953,14 @@ exports.forEachAsync = forEachAsync;
|
|
|
16582
14953
|
exports.forceNumIntoBounds = forceNumIntoBounds;
|
|
16583
14954
|
exports.genCSV = genCSV;
|
|
16584
14955
|
exports.genCommaList = genCommaList;
|
|
16585
|
-
exports.genRouteHandler = genRouteHandler;
|
|
16586
14956
|
exports.getHumanReadableDate = getHumanReadableDate;
|
|
16587
14957
|
exports.getLocalTimeInfo = getLocalTimeInfo;
|
|
16588
14958
|
exports.getMonthName = getMonthName;
|
|
16589
14959
|
exports.getOrdinal = getOrdinal;
|
|
16590
14960
|
exports.getPartOfDay = getPartOfDay;
|
|
16591
14961
|
exports.getTimeInfoInET = getTimeInfoInET;
|
|
16592
|
-
exports.getWordCount = getWordCount;
|
|
16593
|
-
exports.handleError = handleError;
|
|
16594
|
-
exports.handleSuccess = handleSuccess;
|
|
16595
14962
|
exports.idify = idify;
|
|
16596
14963
|
exports.initClient = initClient;
|
|
16597
|
-
exports.initLogCollection = initLogCollection;
|
|
16598
|
-
exports.initServer = initServer;
|
|
16599
14964
|
exports.isMobileOrTablet = isMobileOrTablet;
|
|
16600
14965
|
exports.leaveToURL = leaveToURL;
|
|
16601
14966
|
exports.logClientEvent = logClientEvent;
|
|
@@ -16620,7 +14985,6 @@ exports.useForceRender = useForceRender;
|
|
|
16620
14985
|
exports.validateEmail = validateEmail;
|
|
16621
14986
|
exports.validatePhoneNumber = validatePhoneNumber;
|
|
16622
14987
|
exports.validateString = validateString;
|
|
16623
|
-
exports.visitEndpointOnAnotherServer = visitEndpointOnAnotherServer;
|
|
16624
14988
|
exports.visitServerEndpoint = visitServerEndpoint;
|
|
16625
14989
|
exports.waitMs = waitMs;
|
|
16626
14990
|
//# sourceMappingURL=index.js.map
|