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/esm/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import React__default, { useState, useRef, useEffect, useReducer, forwardRef, useContext, useLayoutEffect, createContext, useMemo, useCallback, Component, Fragment } from 'react';
|
|
3
|
-
import { faExclamationTriangle,
|
|
3
|
+
import { faExclamationTriangle, faHourglassEnd, faCircle, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faMinus, faCheckCircle, faXmarkCircle, faSort, faSortDown, faSortUp, faCalendar, faTag, faHammer, faList, faTimes, faSave, faTrash, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
|
|
4
4
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
5
5
|
import ReactDOM, { createPortal } from 'react-dom';
|
|
6
6
|
import { faCircle as faCircle$1, faSquareMinus, faSquare } from '@fortawesome/free-regular-svg-icons';
|
|
7
|
-
import qs from 'qs';
|
|
8
7
|
|
|
9
8
|
/******************************************************************************
|
|
10
9
|
Copyright (c) Microsoft Corporation.
|
|
@@ -31,7 +30,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
31
30
|
});
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
// Highest error code =
|
|
33
|
+
// Highest error code = DRK34
|
|
35
34
|
/**
|
|
36
35
|
* List of error codes built into the react kit
|
|
37
36
|
* @author Gabe Abrams
|
|
@@ -41,22 +40,7 @@ var ReactKitErrorCode;
|
|
|
41
40
|
ReactKitErrorCode["NoResponse"] = "DRK1";
|
|
42
41
|
ReactKitErrorCode["NoCode"] = "DRK2";
|
|
43
42
|
ReactKitErrorCode["SessionExpired"] = "DRK3";
|
|
44
|
-
ReactKitErrorCode["MissingParameter"] = "DRK4";
|
|
45
|
-
ReactKitErrorCode["InvalidParameter"] = "DRK5";
|
|
46
|
-
ReactKitErrorCode["HostNotAllowed"] = "DRK17";
|
|
47
|
-
ReactKitErrorCode["HostBanned"] = "DRK18";
|
|
48
|
-
ReactKitErrorCode["WrongCourse"] = "DRK6";
|
|
49
43
|
ReactKitErrorCode["NoCACCLSendRequestFunction"] = "DRK7";
|
|
50
|
-
ReactKitErrorCode["NoCACCLGetLaunchInfoFunction"] = "DRK8";
|
|
51
|
-
ReactKitErrorCode["NotTTM"] = "DRK9";
|
|
52
|
-
ReactKitErrorCode["NotAdmin"] = "DRK10";
|
|
53
|
-
ReactKitErrorCode["NotAllowedToReviewLogs"] = "DRK11";
|
|
54
|
-
ReactKitErrorCode["ThemeCheckedBeforeReactKitReady"] = "DRK12";
|
|
55
|
-
ReactKitErrorCode["SessionExpiredMessageGottenBeforeReactKitReady"] = "DRK13";
|
|
56
|
-
// Server-to-server requests
|
|
57
|
-
ReactKitErrorCode["NotConnected"] = "DRK14";
|
|
58
|
-
ReactKitErrorCode["SelfSigned"] = "DRK15";
|
|
59
|
-
ReactKitErrorCode["ResponseParseError"] = "DRK16";
|
|
60
44
|
})(ReactKitErrorCode || (ReactKitErrorCode = {}));
|
|
61
45
|
var ReactKitErrorCode$1 = ReactKitErrorCode;
|
|
62
46
|
|
|
@@ -188,98 +172,6 @@ const LogBuiltInMetadata = {
|
|
|
188
172
|
},
|
|
189
173
|
};
|
|
190
174
|
|
|
191
|
-
/**
|
|
192
|
-
* Loading spinner/indicator
|
|
193
|
-
* @author Gabe Abrams
|
|
194
|
-
*/
|
|
195
|
-
/*------------------------------------------------------------------------*/
|
|
196
|
-
/* -------------------------------- Style ------------------------------- */
|
|
197
|
-
/*------------------------------------------------------------------------*/
|
|
198
|
-
const style$a = `
|
|
199
|
-
/* Container fades in */
|
|
200
|
-
.LoadingSpinner-container {
|
|
201
|
-
animation-name: LoadingSpinner-container-fade-in;
|
|
202
|
-
animation-duration: 0.3s;
|
|
203
|
-
animation-delay: 0.4s;
|
|
204
|
-
animation-fill-mode: both;
|
|
205
|
-
animation-timing-function: ease-out;
|
|
206
|
-
}
|
|
207
|
-
@keyframes LoadingSpinner-container-fade-in {
|
|
208
|
-
0% {
|
|
209
|
-
opacity: 0;
|
|
210
|
-
}
|
|
211
|
-
100% {
|
|
212
|
-
opacity: 1;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/* Blips */
|
|
217
|
-
.LoadingSpinner-blip-1,
|
|
218
|
-
.LoadingSpinner-blip-2,
|
|
219
|
-
.LoadingSpinner-blip-3,
|
|
220
|
-
.LoadingSpinner-blip-4 {
|
|
221
|
-
font-size: 1.8rem;
|
|
222
|
-
opacity: 0.6;
|
|
223
|
-
margin-top: 1rem;
|
|
224
|
-
margin-bottom: 1rem;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/* First Blip */
|
|
228
|
-
.LoadingSpinner-blip-1 {
|
|
229
|
-
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/* Second Blip */
|
|
233
|
-
.LoadingSpinner-blip-2 {
|
|
234
|
-
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
235
|
-
-webkit-animation-delay: 0.1s;
|
|
236
|
-
animation-delay: 0.1s;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/* Third Blip */
|
|
240
|
-
.LoadingSpinner-blip-3 {
|
|
241
|
-
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
242
|
-
animation-delay: 0.2s;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/* Fourth Blip */
|
|
246
|
-
.LoadingSpinner-blip-4 {
|
|
247
|
-
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
248
|
-
animation-delay: 0.3s;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/* Pop Animation for Each Blip */
|
|
252
|
-
@keyframes LoadingSpinner-pop-animation {
|
|
253
|
-
0% {
|
|
254
|
-
transform: scale(1.0);
|
|
255
|
-
}
|
|
256
|
-
10% {
|
|
257
|
-
transform: scale(1.5);
|
|
258
|
-
}
|
|
259
|
-
30% {
|
|
260
|
-
transform: scale(1.0);
|
|
261
|
-
}
|
|
262
|
-
100% {
|
|
263
|
-
transform: scale(1.0);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
`;
|
|
267
|
-
/*------------------------------------------------------------------------*/
|
|
268
|
-
/* ------------------------------ Component ----------------------------- */
|
|
269
|
-
/*------------------------------------------------------------------------*/
|
|
270
|
-
const LoadingSpinner = () => {
|
|
271
|
-
/*------------------------------------------------------------------------*/
|
|
272
|
-
/* ------------------------------- Render ------------------------------- */
|
|
273
|
-
/*------------------------------------------------------------------------*/
|
|
274
|
-
// Add all four blips to a container
|
|
275
|
-
return (React__default.createElement("div", { className: "text-center LoadingSpinner LoadingSpinner-container" },
|
|
276
|
-
React__default.createElement("style", null, style$a),
|
|
277
|
-
React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-1 me-1" }),
|
|
278
|
-
React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-2 me-1" }),
|
|
279
|
-
React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-3 me-1" }),
|
|
280
|
-
React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-4" })));
|
|
281
|
-
};
|
|
282
|
-
|
|
283
175
|
/**
|
|
284
176
|
* Wait for a certain number of ms
|
|
285
177
|
* @author Gabe Abrams
|
|
@@ -330,6 +222,15 @@ let onInitialized;
|
|
|
330
222
|
const initialized = new Promise((resolve) => {
|
|
331
223
|
onInitialized = resolve;
|
|
332
224
|
});
|
|
225
|
+
/* ------------ No Server ----------- */
|
|
226
|
+
let noServer = false;
|
|
227
|
+
/**
|
|
228
|
+
* Check if there is no server for this app
|
|
229
|
+
* @author Gabe Abrams
|
|
230
|
+
*/
|
|
231
|
+
const appHasNoServer = () => {
|
|
232
|
+
return noServer;
|
|
233
|
+
};
|
|
333
234
|
/* ---------- Send Request ---------- */
|
|
334
235
|
let storedSendRequest;
|
|
335
236
|
/**
|
|
@@ -391,11 +292,13 @@ const initClient = (opts) => {
|
|
|
391
292
|
storedSendRequest = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
392
293
|
throw new Error('Cannot send requests because there is no server');
|
|
393
294
|
});
|
|
295
|
+
noServer = true;
|
|
394
296
|
}
|
|
395
297
|
else {
|
|
396
298
|
// Store values
|
|
397
299
|
storedSendRequest = opts.sendRequest;
|
|
398
300
|
sessionExpiredMessage = opts.sessionExpiredMessage;
|
|
301
|
+
noServer = false;
|
|
399
302
|
}
|
|
400
303
|
// Handle universal parts
|
|
401
304
|
darkModeOn = !!opts.darkModeOn;
|
|
@@ -412,7 +315,6 @@ const BASE_Z_INDEX = 1000000000;
|
|
|
412
315
|
const BASE_Z_INDEX_ON_TOP = 2000000000;
|
|
413
316
|
// Constants
|
|
414
317
|
const MS_TO_ANIMATE = 200; // Animation duration
|
|
415
|
-
const MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR = 1000;
|
|
416
318
|
// Modal type to list of buttons
|
|
417
319
|
const modalTypeToModalButtonTypes = {
|
|
418
320
|
[ModalType$1.Okay]: [
|
|
@@ -526,7 +428,7 @@ const getNextUniqueId = () => {
|
|
|
526
428
|
/*------------------------------------------------------------------------*/
|
|
527
429
|
/* -------------------------------- Style ------------------------------- */
|
|
528
430
|
/*------------------------------------------------------------------------*/
|
|
529
|
-
const style$
|
|
431
|
+
const style$a = `
|
|
530
432
|
.Modal-backdrop {
|
|
531
433
|
position: fixed;
|
|
532
434
|
top: 0;
|
|
@@ -598,14 +500,13 @@ const Modal = (props) => {
|
|
|
598
500
|
/*------------------------------------------------------------------------*/
|
|
599
501
|
var _a;
|
|
600
502
|
/* -------------- Props ------------- */
|
|
601
|
-
const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, largeTitle, children, onClose, dontAllowBackdropExit, dontShowXButton, onTopOfOtherModals,
|
|
503
|
+
const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, largeTitle, children, onClose, dontAllowBackdropExit, dontShowXButton, onTopOfOtherModals, } = props;
|
|
602
504
|
// Determine if no header either
|
|
603
505
|
const noHeader = (!title && type === ModalType$1.NoButtons);
|
|
604
506
|
/* -------------- State ------------- */
|
|
605
507
|
// True if animation is in use
|
|
606
508
|
const [animatingIn, setAnimatingIn] = useState(true);
|
|
607
509
|
const [animatingPop, setAnimatingPop] = useState(false);
|
|
608
|
-
const [showModal, setShowModal] = useState(false);
|
|
609
510
|
/* -------------- Refs -------------- */
|
|
610
511
|
// Keep track of whether modal is still mounted
|
|
611
512
|
const mounted = useRef(false);
|
|
@@ -632,16 +533,6 @@ const Modal = (props) => {
|
|
|
632
533
|
if (mounted.current) {
|
|
633
534
|
setAnimatingIn(false);
|
|
634
535
|
}
|
|
635
|
-
if (isLoading) {
|
|
636
|
-
// wait before showing modal
|
|
637
|
-
yield waitMs(MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR);
|
|
638
|
-
if (mounted.current) {
|
|
639
|
-
setShowModal(true);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
else {
|
|
643
|
-
setShowModal(true);
|
|
644
|
-
}
|
|
645
536
|
}))();
|
|
646
537
|
return () => {
|
|
647
538
|
mounted.current = false;
|
|
@@ -708,15 +599,6 @@ const Modal = (props) => {
|
|
|
708
599
|
else if (animatingPop) {
|
|
709
600
|
animationClass = 'Modal-animating-pop';
|
|
710
601
|
}
|
|
711
|
-
// default to show close button when not loading and not show close button when loading
|
|
712
|
-
// unless downShowXButton or isLoadingCancelable
|
|
713
|
-
const showCloseButton = (
|
|
714
|
-
// The modal must have an onClose function
|
|
715
|
-
onClose
|
|
716
|
-
// ...and the close button is allowed to be visible
|
|
717
|
-
&& !dontShowXButton
|
|
718
|
-
// ...and should not be shown if the modal is loading and not cancelable
|
|
719
|
-
&& !(isLoading && !isLoadingCancelable));
|
|
720
602
|
// Render the modal
|
|
721
603
|
const contentToRender = (React__default.createElement("div", { className: "modal show", tabIndex: -1, style: {
|
|
722
604
|
zIndex: baseZIndex,
|
|
@@ -725,7 +607,7 @@ const Modal = (props) => {
|
|
|
725
607
|
left: 0,
|
|
726
608
|
right: 0,
|
|
727
609
|
} },
|
|
728
|
-
React__default.createElement("style", null, style$
|
|
610
|
+
React__default.createElement("style", null, style$a),
|
|
729
611
|
React__default.createElement("div", { className: `Modal-backdrop ${backdropAnimationClass}`, style: {
|
|
730
612
|
zIndex: baseZIndex + 1,
|
|
731
613
|
}, onClick: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -743,7 +625,7 @@ const Modal = (props) => {
|
|
|
743
625
|
// Handle close
|
|
744
626
|
handleClose(ModalButtonType$1.Cancel);
|
|
745
627
|
}) }),
|
|
746
|
-
|
|
628
|
+
React__default.createElement("div", { className: `modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`, style: {
|
|
747
629
|
zIndex: baseZIndex + 2,
|
|
748
630
|
// Override sizes for even larger for XL
|
|
749
631
|
width: (size === ModalSize$1.ExtraLarge
|
|
@@ -775,7 +657,7 @@ const Modal = (props) => {
|
|
|
775
657
|
? '1.6rem'
|
|
776
658
|
: undefined),
|
|
777
659
|
} }, title),
|
|
778
|
-
|
|
660
|
+
(onClose && !dontShowXButton) && (React__default.createElement("button", { type: "button", className: "Modal-x-button btn-close", "aria-label": "Close", style: {
|
|
779
661
|
backgroundColor: (isDarkModeOn()
|
|
780
662
|
? 'white'
|
|
781
663
|
: undefined),
|
|
@@ -783,18 +665,14 @@ const Modal = (props) => {
|
|
|
783
665
|
// Handle close
|
|
784
666
|
handleClose(ModalButtonType$1.Cancel);
|
|
785
667
|
} })))),
|
|
786
|
-
React__default.createElement("div", { className: "modal-body", style: {
|
|
668
|
+
children && (React__default.createElement("div", { className: "modal-body", style: {
|
|
787
669
|
color: (isDarkModeOn()
|
|
788
670
|
? 'white'
|
|
789
671
|
: undefined),
|
|
790
672
|
backgroundColor: (isDarkModeOn()
|
|
791
673
|
? '#444'
|
|
792
674
|
: undefined),
|
|
793
|
-
} },
|
|
794
|
-
? (React__default.createElement(React__default.Fragment, null,
|
|
795
|
-
React__default.createElement(LoadingSpinner, null),
|
|
796
|
-
React__default.createElement("span", { className: "sr-only" }, "Content loading")))
|
|
797
|
-
: children),
|
|
675
|
+
} }, children)),
|
|
798
676
|
footer && (React__default.createElement("div", { className: "modal-footer pt-1 pb-1", style: {
|
|
799
677
|
color: (isDarkModeOn()
|
|
800
678
|
? 'white'
|
|
@@ -805,7 +683,7 @@ const Modal = (props) => {
|
|
|
805
683
|
borderTop: (isDarkModeOn()
|
|
806
684
|
? '0.1rem solid gray'
|
|
807
685
|
: undefined),
|
|
808
|
-
} }, footer))))))
|
|
686
|
+
} }, footer))))));
|
|
809
687
|
// Determine which portal to use
|
|
810
688
|
const portalNumber = id.current % NUM_MODAL_PORTALS;
|
|
811
689
|
// Render in a portal
|
|
@@ -976,6 +854,10 @@ const setClientEventMetadataPopulator = (newMetadataPopulator) => {
|
|
|
976
854
|
*/
|
|
977
855
|
const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
978
856
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
857
|
+
// Don't write to the server if there is none
|
|
858
|
+
if (appHasNoServer()) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
979
861
|
// Populate metadata
|
|
980
862
|
let metadata = ((_a = opts.metadata) !== null && _a !== void 0 ? _a : {});
|
|
981
863
|
if (metadataPopulator) {
|
|
@@ -1372,7 +1254,7 @@ const showSessionExpiredMessage = () => __awaiter(void 0, void 0, void 0, functi
|
|
|
1372
1254
|
/*------------------------------------------------------------------------*/
|
|
1373
1255
|
/* -------------------------------- Style ------------------------------- */
|
|
1374
1256
|
/*------------------------------------------------------------------------*/
|
|
1375
|
-
const style$
|
|
1257
|
+
const style$9 = `
|
|
1376
1258
|
.AppWrapper-leave-to-url-notice {
|
|
1377
1259
|
opacity: 0;
|
|
1378
1260
|
|
|
@@ -1586,13 +1468,105 @@ const AppWrapper = (props) => {
|
|
|
1586
1468
|
/* --------------- Main UI -------------- */
|
|
1587
1469
|
/*----------------------------------------*/
|
|
1588
1470
|
return (React__default.createElement(React__default.Fragment, null,
|
|
1589
|
-
React__default.createElement("style", null, style$
|
|
1471
|
+
React__default.createElement("style", null, style$9),
|
|
1590
1472
|
React__default.createElement("style", null, tooltipStyle),
|
|
1591
1473
|
modal,
|
|
1592
1474
|
customModalPortals,
|
|
1593
1475
|
body));
|
|
1594
1476
|
};
|
|
1595
1477
|
|
|
1478
|
+
/**
|
|
1479
|
+
* Loading spinner/indicator
|
|
1480
|
+
* @author Gabe Abrams
|
|
1481
|
+
*/
|
|
1482
|
+
/*------------------------------------------------------------------------*/
|
|
1483
|
+
/* -------------------------------- Style ------------------------------- */
|
|
1484
|
+
/*------------------------------------------------------------------------*/
|
|
1485
|
+
const style$8 = `
|
|
1486
|
+
/* Container fades in */
|
|
1487
|
+
.LoadingSpinner-container {
|
|
1488
|
+
animation-name: LoadingSpinner-container-fade-in;
|
|
1489
|
+
animation-duration: 0.3s;
|
|
1490
|
+
animation-delay: 0.4s;
|
|
1491
|
+
animation-fill-mode: both;
|
|
1492
|
+
animation-timing-function: ease-out;
|
|
1493
|
+
}
|
|
1494
|
+
@keyframes LoadingSpinner-container-fade-in {
|
|
1495
|
+
0% {
|
|
1496
|
+
opacity: 0;
|
|
1497
|
+
}
|
|
1498
|
+
100% {
|
|
1499
|
+
opacity: 1;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/* Blips */
|
|
1504
|
+
.LoadingSpinner-blip-1,
|
|
1505
|
+
.LoadingSpinner-blip-2,
|
|
1506
|
+
.LoadingSpinner-blip-3,
|
|
1507
|
+
.LoadingSpinner-blip-4 {
|
|
1508
|
+
font-size: 1.8rem;
|
|
1509
|
+
opacity: 0.6;
|
|
1510
|
+
margin-top: 1rem;
|
|
1511
|
+
margin-bottom: 1rem;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/* First Blip */
|
|
1515
|
+
.LoadingSpinner-blip-1 {
|
|
1516
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
/* Second Blip */
|
|
1520
|
+
.LoadingSpinner-blip-2 {
|
|
1521
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1522
|
+
-webkit-animation-delay: 0.1s;
|
|
1523
|
+
animation-delay: 0.1s;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/* Third Blip */
|
|
1527
|
+
.LoadingSpinner-blip-3 {
|
|
1528
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1529
|
+
animation-delay: 0.2s;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/* Fourth Blip */
|
|
1533
|
+
.LoadingSpinner-blip-4 {
|
|
1534
|
+
animation: LoadingSpinner-pop-animation 2s infinite;
|
|
1535
|
+
animation-delay: 0.3s;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/* Pop Animation for Each Blip */
|
|
1539
|
+
@keyframes LoadingSpinner-pop-animation {
|
|
1540
|
+
0% {
|
|
1541
|
+
transform: scale(1.0);
|
|
1542
|
+
}
|
|
1543
|
+
10% {
|
|
1544
|
+
transform: scale(1.5);
|
|
1545
|
+
}
|
|
1546
|
+
30% {
|
|
1547
|
+
transform: scale(1.0);
|
|
1548
|
+
}
|
|
1549
|
+
100% {
|
|
1550
|
+
transform: scale(1.0);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
`;
|
|
1554
|
+
/*------------------------------------------------------------------------*/
|
|
1555
|
+
/* ------------------------------ Component ----------------------------- */
|
|
1556
|
+
/*------------------------------------------------------------------------*/
|
|
1557
|
+
const LoadingSpinner = () => {
|
|
1558
|
+
/*------------------------------------------------------------------------*/
|
|
1559
|
+
/* ------------------------------- Render ------------------------------- */
|
|
1560
|
+
/*------------------------------------------------------------------------*/
|
|
1561
|
+
// Add all four blips to a container
|
|
1562
|
+
return (React__default.createElement("div", { className: "text-center LoadingSpinner LoadingSpinner-container" },
|
|
1563
|
+
React__default.createElement("style", null, style$8),
|
|
1564
|
+
React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-1 me-1" }),
|
|
1565
|
+
React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-2 me-1" }),
|
|
1566
|
+
React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-3 me-1" }),
|
|
1567
|
+
React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-4" })));
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1596
1570
|
/**
|
|
1597
1571
|
* A box with a tab on the top that holds buttons and other content
|
|
1598
1572
|
* @author Gabe Abrams
|
|
@@ -1624,9 +1598,7 @@ const style$7 = `
|
|
|
1624
1598
|
|
|
1625
1599
|
/* Container for Title */
|
|
1626
1600
|
.TabBox-title-container {
|
|
1627
|
-
|
|
1628
|
-
justify-content: space-between;
|
|
1629
|
-
align-items: center;
|
|
1601
|
+
/* Place on Left */
|
|
1630
1602
|
position: relative;
|
|
1631
1603
|
left: 0;
|
|
1632
1604
|
text-align: left;
|
|
@@ -1659,19 +1631,6 @@ const style$7 = `
|
|
|
1659
1631
|
background: #fdfdfd;
|
|
1660
1632
|
}
|
|
1661
1633
|
|
|
1662
|
-
.TabBox-title-right-container {
|
|
1663
|
-
display: flex;
|
|
1664
|
-
flex-direction: row;
|
|
1665
|
-
align-items: bottom;
|
|
1666
|
-
height: 2.4rem;
|
|
1667
|
-
overflow: visible;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
.TabBox-title-right-contents {
|
|
1671
|
-
margin-right: 0.5rem;
|
|
1672
|
-
margin-bottom: 0.2rem;
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
1634
|
/* Make the TabBox's Children Appear Above Title if Overlap Occurs */
|
|
1676
1635
|
.TabBox-children {
|
|
1677
1636
|
position: relative;
|
|
@@ -1685,16 +1644,19 @@ const TabBox = (props) => {
|
|
|
1685
1644
|
/*------------------------------------------------------------------------*/
|
|
1686
1645
|
/* -------------------------------- Setup ------------------------------- */
|
|
1687
1646
|
/*------------------------------------------------------------------------*/
|
|
1688
|
-
|
|
1647
|
+
/* -------------- Props ------------- */
|
|
1648
|
+
const { title, children, noBottomPadding, noBottomMargin, } = props;
|
|
1689
1649
|
/*------------------------------------------------------------------------*/
|
|
1690
1650
|
/* ------------------------------- Render ------------------------------- */
|
|
1691
1651
|
/*------------------------------------------------------------------------*/
|
|
1652
|
+
/*----------------------------------------*/
|
|
1653
|
+
/* --------------- Main UI -------------- */
|
|
1654
|
+
/*----------------------------------------*/
|
|
1655
|
+
// Full UI
|
|
1692
1656
|
return (React__default.createElement("div", { className: `TabBox-container ${noBottomMargin ? '' : 'mb-2'}` },
|
|
1693
1657
|
React__default.createElement("style", null, style$7),
|
|
1694
1658
|
React__default.createElement("div", { className: "TabBox-title-container" },
|
|
1695
|
-
React__default.createElement("div", { className: "TabBox-title" }, title),
|
|
1696
|
-
topRightChildren && (React__default.createElement("div", { className: "TabBox-title-right-container" },
|
|
1697
|
-
React__default.createElement("div", { className: "TabBox-title-right-contents" }, topRightChildren)))),
|
|
1659
|
+
React__default.createElement("div", { className: "TabBox-title" }, title)),
|
|
1698
1660
|
React__default.createElement("div", { className: `TabBox-box ps-2 pt-2 pe-2 ${noBottomPadding ? '' : 'pb-2'}` },
|
|
1699
1661
|
React__default.createElement("div", { className: "TabBox-children" }, children))));
|
|
1700
1662
|
};
|
|
@@ -2681,7 +2643,7 @@ const NestableItemList = (props) => {
|
|
|
2681
2643
|
React__default.createElement(FontAwesomeIcon, { icon: childExpanded[item.id] ? faChevronDown : faChevronRight })))),
|
|
2682
2644
|
React__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) => {
|
|
2683
2645
|
onChanged(changeChecked(item.id, checked, items));
|
|
2684
|
-
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light
|
|
2646
|
+
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2685
2647
|
(item.isGroup && childExpanded[item.id]) && (React__default.createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2686
2648
|
paddingLeft: '2.2rem',
|
|
2687
2649
|
} },
|
|
@@ -2704,62 +2666,17 @@ const ItemPicker = (props) => {
|
|
|
2704
2666
|
/*------------------------------------------------------------------------*/
|
|
2705
2667
|
/* -------------- Props ------------- */
|
|
2706
2668
|
// Destructure all props
|
|
2707
|
-
const { title, items, onChanged, noBottomMargin,
|
|
2669
|
+
const { title, items, onChanged, noBottomMargin, } = props;
|
|
2708
2670
|
/*------------------------------------------------------------------------*/
|
|
2709
2671
|
/* ------------------------- Component Functions ------------------------ */
|
|
2710
2672
|
/*------------------------------------------------------------------------*/
|
|
2711
|
-
/**
|
|
2712
|
-
* Updates the checked state of an item (including all children)
|
|
2713
|
-
* @author Yuen Ler Chow
|
|
2714
|
-
* @param item the item to update
|
|
2715
|
-
* @param checked the new checked state
|
|
2716
|
-
* @returns the updated item
|
|
2717
|
-
*/
|
|
2718
|
-
const updateItemChecked = (item, checked) => {
|
|
2719
|
-
if (item.isGroup) {
|
|
2720
|
-
return Object.assign(Object.assign({}, item), { children: item.children.map((child) => {
|
|
2721
|
-
return updateItemChecked(child, checked);
|
|
2722
|
-
}) });
|
|
2723
|
-
}
|
|
2724
|
-
return Object.assign(Object.assign({}, item), { checked });
|
|
2725
|
-
};
|
|
2726
|
-
/**
|
|
2727
|
-
* Selects all items in the list
|
|
2728
|
-
* @author Yuen Ler Chow
|
|
2729
|
-
*/
|
|
2730
|
-
const handleSelectAll = () => {
|
|
2731
|
-
const updatedItems = items.map((item) => {
|
|
2732
|
-
return updateItemChecked(item, true);
|
|
2733
|
-
});
|
|
2734
|
-
onChanged(updatedItems);
|
|
2735
|
-
};
|
|
2736
|
-
/**
|
|
2737
|
-
* Deselects all items in the list
|
|
2738
|
-
* @author Yuen Ler Chow
|
|
2739
|
-
*/
|
|
2740
|
-
const handleDeselectAll = () => {
|
|
2741
|
-
const updatedItems = items.map((item) => {
|
|
2742
|
-
return updateItemChecked(item, false);
|
|
2743
|
-
});
|
|
2744
|
-
onChanged(updatedItems);
|
|
2745
|
-
};
|
|
2746
2673
|
/*------------------------------------------------------------------------*/
|
|
2747
2674
|
/* ------------------------------- Render ------------------------------- */
|
|
2748
2675
|
/*------------------------------------------------------------------------*/
|
|
2749
2676
|
/*----------------------------------------*/
|
|
2750
2677
|
/* --------------- Main UI -------------- */
|
|
2751
2678
|
/*----------------------------------------*/
|
|
2752
|
-
|
|
2753
|
-
const selectAllOrNone = (!hideSelectAllOrNoneButtons
|
|
2754
|
-
? (React__default.createElement("div", { className: "d-flex h-100 align-items-end flex-row" },
|
|
2755
|
-
React__default.createElement("div", { className: "d-flex justify-content-end" },
|
|
2756
|
-
React__default.createElement("div", { className: "me-2", style: { fontSize: '1.2rem' } }, "Select"),
|
|
2757
|
-
React__default.createElement("div", { className: "btn-group", role: "group" },
|
|
2758
|
-
React__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"),
|
|
2759
|
-
React__default.createElement("button", { type: "button", "aria-label": "Deselect all contexts", className: "btn btn-secondary py-0", onClick: handleDeselectAll }, "None")))))
|
|
2760
|
-
: undefined);
|
|
2761
|
-
// Main UI
|
|
2762
|
-
return (React__default.createElement(TabBox, { title: title, noBottomMargin: noBottomMargin, topRightChildren: selectAllOrNone },
|
|
2679
|
+
return (React__default.createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
|
|
2763
2680
|
React__default.createElement("div", { style: { overflowX: 'auto' } },
|
|
2764
2681
|
React__default.createElement(NestableItemList, { items: items, onChanged: onChanged }))));
|
|
2765
2682
|
};
|
|
@@ -2874,16 +2791,16 @@ var LogAction$1 = LogAction;
|
|
|
2874
2791
|
*/
|
|
2875
2792
|
var ParamType;
|
|
2876
2793
|
(function (ParamType) {
|
|
2877
|
-
ParamType["Boolean"] = "
|
|
2878
|
-
ParamType["BooleanOptional"] = "
|
|
2879
|
-
ParamType["Float"] = "
|
|
2880
|
-
ParamType["FloatOptional"] = "
|
|
2881
|
-
ParamType["Int"] = "
|
|
2882
|
-
ParamType["IntOptional"] = "
|
|
2883
|
-
ParamType["JSON"] = "
|
|
2884
|
-
ParamType["JSONOptional"] = "
|
|
2885
|
-
ParamType["String"] = "
|
|
2886
|
-
ParamType["StringOptional"] = "
|
|
2794
|
+
ParamType["Boolean"] = "Boolean";
|
|
2795
|
+
ParamType["BooleanOptional"] = "BooleanOptional";
|
|
2796
|
+
ParamType["Float"] = "Float";
|
|
2797
|
+
ParamType["FloatOptional"] = "FloatOptional";
|
|
2798
|
+
ParamType["Int"] = "Int";
|
|
2799
|
+
ParamType["IntOptional"] = "IntOptional";
|
|
2800
|
+
ParamType["JSON"] = "JSON";
|
|
2801
|
+
ParamType["JSONOptional"] = "JSONOptional";
|
|
2802
|
+
ParamType["String"] = "String";
|
|
2803
|
+
ParamType["StringOptional"] = "StringOptional";
|
|
2887
2804
|
})(ParamType || (ParamType = {}));
|
|
2888
2805
|
var ParamType$1 = ParamType;
|
|
2889
2806
|
|
|
@@ -3131,7 +3048,7 @@ const IntelliTable = (props) => {
|
|
|
3131
3048
|
param: column.param,
|
|
3132
3049
|
visible: checked,
|
|
3133
3050
|
});
|
|
3134
|
-
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.
|
|
3051
|
+
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
|
|
3135
3052
|
}),
|
|
3136
3053
|
React__default.createElement("div", { className: "mt-3" }, "Or you can:"),
|
|
3137
3054
|
React__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: () => {
|
|
@@ -14093,1295 +14010,23 @@ const padZerosLeft = (num, numDigits) => {
|
|
|
14093
14010
|
};
|
|
14094
14011
|
|
|
14095
14012
|
/**
|
|
14096
|
-
*
|
|
14097
|
-
* access to log review
|
|
14013
|
+
* Stub a server endpoint response
|
|
14098
14014
|
* @author Gabe Abrams
|
|
14015
|
+
* @param opts object containing all arguments
|
|
14016
|
+
* @param [opts.method=GET] http method to stub
|
|
14017
|
+
* @param opts.path full pathname to stub
|
|
14018
|
+
* @param opts.body body of response if stubbing a successful response
|
|
14019
|
+
* @param opts.errorMessage message of error if stubbing a failed response
|
|
14020
|
+
* @param [opts.errorCode] error code if stubbing a failed response
|
|
14099
14021
|
*/
|
|
14100
|
-
const
|
|
14022
|
+
const stubServerEndpoint = _setStubResponse;
|
|
14101
14023
|
|
|
14102
|
-
// Stored copy of caccl functions
|
|
14103
|
-
let _cacclGetLaunchInfo;
|
|
14104
|
-
// Stored copy of dce-mango log collection
|
|
14105
|
-
let _logCollection;
|
|
14106
|
-
/*------------------------------------------------------------------------*/
|
|
14107
|
-
/* Helpers */
|
|
14108
|
-
/*------------------------------------------------------------------------*/
|
|
14109
|
-
/**
|
|
14110
|
-
* Get launch info via CACCL
|
|
14111
|
-
* @author Gabe Abrams
|
|
14112
|
-
* @param req express request object
|
|
14113
|
-
* @returns object { launched, launchInfo }
|
|
14114
|
-
*/
|
|
14115
|
-
const cacclGetLaunchInfo = (req) => {
|
|
14116
|
-
if (!_cacclGetLaunchInfo) {
|
|
14117
|
-
throw new ErrorWithCode('Could not get launch info because server was not initialized with dce-reactkit\'s initServer function', ReactKitErrorCode$1.NoCACCLGetLaunchInfoFunction);
|
|
14118
|
-
}
|
|
14119
|
-
return _cacclGetLaunchInfo(req);
|
|
14120
|
-
};
|
|
14121
14024
|
/**
|
|
14122
|
-
*
|
|
14025
|
+
* Start a dynamic wait, call the function once the operation has completed and
|
|
14026
|
+
* the dynamic wait will continue waiting for the rest of the minimum time
|
|
14123
14027
|
* @author Gabe Abrams
|
|
14124
|
-
* @
|
|
14125
|
-
*
|
|
14126
|
-
*/
|
|
14127
|
-
const internalGetLogCollection = () => {
|
|
14128
|
-
return _logCollection !== null && _logCollection !== void 0 ? _logCollection : null;
|
|
14129
|
-
};
|
|
14130
|
-
/*------------------------------------------------------------------------*/
|
|
14131
|
-
/* Main */
|
|
14132
|
-
/*------------------------------------------------------------------------*/
|
|
14133
|
-
/**
|
|
14134
|
-
* Prepare dce-reactkit to run on the server
|
|
14135
|
-
* @author Gabe Abrams
|
|
14136
|
-
* @param opts object containing all arguments
|
|
14137
|
-
* @param opts.app express app from inside of the postprocessor function that
|
|
14138
|
-
* we will add routes to
|
|
14139
|
-
* @param opts.getLaunchInfo CACCL LTI's get launch info function
|
|
14140
|
-
* @param [opts.logCollection] mongo collection from dce-mango to use for
|
|
14141
|
-
* storing logs. If none is included, logs are written to the console
|
|
14142
|
-
* @param [opts.logReviewAdmins=all] info on which admins can review
|
|
14143
|
-
* logs from the client. If not included, all Canvas admins are allowed to
|
|
14144
|
-
* review logs. If null, no Canvas admins are allowed to review logs.
|
|
14145
|
-
* If an array of Canvas userIds (numbers), only Canvas admins with those
|
|
14146
|
-
* userIds are allowed to review logs. If a dce-mango collection, only
|
|
14147
|
-
* Canvas admins with entries in that collection ({ userId, ...}) are allowed
|
|
14148
|
-
* to review logs
|
|
14149
|
-
*/
|
|
14150
|
-
const initServer = (opts) => {
|
|
14151
|
-
_cacclGetLaunchInfo = opts.getLaunchInfo;
|
|
14152
|
-
_logCollection = opts.logCollection;
|
|
14153
|
-
/*----------------------------------------*/
|
|
14154
|
-
/* Logging */
|
|
14155
|
-
/*----------------------------------------*/
|
|
14156
|
-
/**
|
|
14157
|
-
* Log an event
|
|
14158
|
-
* @author Gabe Abrams
|
|
14159
|
-
* @param {string} context Context of the event (each app determines how to
|
|
14160
|
-
* organize its contexts)
|
|
14161
|
-
* @param {string} subcontext Subcontext of the event (each app determines
|
|
14162
|
-
* how to organize its subcontexts)
|
|
14163
|
-
* @param {string} tags stringified list of tags that apply to this action
|
|
14164
|
-
* (each app determines tag usage)
|
|
14165
|
-
* @param {string} metadata stringified object containing optional custom metadata
|
|
14166
|
-
* @param {string} level log level
|
|
14167
|
-
* @param {string} [errorMessage] error message if type is an error
|
|
14168
|
-
* @param {string} [errorCode] error code if type is an error
|
|
14169
|
-
* @param {string} [errorStack] error stack if type is an error
|
|
14170
|
-
* @param {string} [target] Target of the action (each app determines the list
|
|
14171
|
-
* of targets) These are usually buttons, panels, elements, etc.
|
|
14172
|
-
* @param {LogAction} [action] the type of action performed on the target
|
|
14173
|
-
* @returns {Log}
|
|
14174
|
-
*/
|
|
14175
|
-
opts.app.post(LOG_ROUTE_PATH, genRouteHandler({
|
|
14176
|
-
paramTypes: {
|
|
14177
|
-
context: ParamType$1.String,
|
|
14178
|
-
subcontext: ParamType$1.String,
|
|
14179
|
-
tags: ParamType$1.JSON,
|
|
14180
|
-
level: ParamType$1.String,
|
|
14181
|
-
metadata: ParamType$1.JSON,
|
|
14182
|
-
errorMessage: ParamType$1.StringOptional,
|
|
14183
|
-
errorCode: ParamType$1.StringOptional,
|
|
14184
|
-
errorStack: ParamType$1.StringOptional,
|
|
14185
|
-
target: ParamType$1.StringOptional,
|
|
14186
|
-
action: ParamType$1.StringOptional,
|
|
14187
|
-
},
|
|
14188
|
-
handler: ({ params, logServerEvent }) => {
|
|
14189
|
-
// Create log info
|
|
14190
|
-
const logInfo = ((params.errorMessage || params.errorCode || params.errorStack)
|
|
14191
|
-
// Error
|
|
14192
|
-
? {
|
|
14193
|
-
context: params.context,
|
|
14194
|
-
subcontext: params.subcontext,
|
|
14195
|
-
tags: params.tags,
|
|
14196
|
-
level: params.level,
|
|
14197
|
-
metadata: params.metadata,
|
|
14198
|
-
error: {
|
|
14199
|
-
message: params.errorMessage,
|
|
14200
|
-
code: params.errorCode,
|
|
14201
|
-
stack: params.errorStack,
|
|
14202
|
-
},
|
|
14203
|
-
}
|
|
14204
|
-
// Action
|
|
14205
|
-
: {
|
|
14206
|
-
context: params.context,
|
|
14207
|
-
subcontext: params.subcontext,
|
|
14208
|
-
tags: params.tags,
|
|
14209
|
-
level: params.level,
|
|
14210
|
-
metadata: params.metadata,
|
|
14211
|
-
target: params.target,
|
|
14212
|
-
action: params.action,
|
|
14213
|
-
});
|
|
14214
|
-
// Add hidden boolean to change source to "client"
|
|
14215
|
-
const logInfoForcedFromClient = Object.assign(Object.assign({}, logInfo), { overrideAsClientEvent: true });
|
|
14216
|
-
// Write the log
|
|
14217
|
-
const log = logServerEvent(logInfoForcedFromClient);
|
|
14218
|
-
// Return
|
|
14219
|
-
return log;
|
|
14220
|
-
},
|
|
14221
|
-
}));
|
|
14222
|
-
/*----------------------------------------*/
|
|
14223
|
-
/* Log Reviewer */
|
|
14224
|
-
/*----------------------------------------*/
|
|
14225
|
-
/**
|
|
14226
|
-
* Check if a given user is allowed to review logs
|
|
14227
|
-
* @author Gabe Abrams
|
|
14228
|
-
* @param userId the id of the user
|
|
14229
|
-
* @param isAdmin if true, the user is an admin
|
|
14230
|
-
* @returns true if the user can review logs
|
|
14231
|
-
*/
|
|
14232
|
-
const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14233
|
-
// Immediately deny access if user is not an admin
|
|
14234
|
-
if (!isAdmin) {
|
|
14235
|
-
return false;
|
|
14236
|
-
}
|
|
14237
|
-
// If all admins are allowed, we're done
|
|
14238
|
-
if (!opts.logReviewAdmins) {
|
|
14239
|
-
return true;
|
|
14240
|
-
}
|
|
14241
|
-
// Do a dynamic check
|
|
14242
|
-
try {
|
|
14243
|
-
// Array of userIds
|
|
14244
|
-
if (Array.isArray(opts.logReviewAdmins)) {
|
|
14245
|
-
return opts.logReviewAdmins.some((allowedId) => {
|
|
14246
|
-
return (userId === allowedId);
|
|
14247
|
-
});
|
|
14248
|
-
}
|
|
14249
|
-
// Must be a collection
|
|
14250
|
-
const matches = yield opts.logReviewAdmins.find({ userId });
|
|
14251
|
-
// Make sure at least one entry matches
|
|
14252
|
-
return matches.length > 0;
|
|
14253
|
-
}
|
|
14254
|
-
catch (err) {
|
|
14255
|
-
// If an error occurred, simply return false
|
|
14256
|
-
return false;
|
|
14257
|
-
}
|
|
14258
|
-
});
|
|
14259
|
-
/**
|
|
14260
|
-
* Check if the current user has access to logs
|
|
14261
|
-
* @author Gabe Abrams
|
|
14262
|
-
* @returns {boolean} true if user has access
|
|
14263
|
-
*/
|
|
14264
|
-
opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
|
|
14265
|
-
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14266
|
-
const { userId, isAdmin } = params;
|
|
14267
|
-
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
14268
|
-
return canReview;
|
|
14269
|
-
}),
|
|
14270
|
-
}));
|
|
14271
|
-
/**
|
|
14272
|
-
* Get all logs for a certain month
|
|
14273
|
-
* @author Gabe Abrams
|
|
14274
|
-
* @param {number} year the year to query (e.g. 2022)
|
|
14275
|
-
* @param {number} month the month to query (e.g. 1 = January)
|
|
14276
|
-
* @returns {Log[]} list of logs from the given month
|
|
14277
|
-
*/
|
|
14278
|
-
opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
|
|
14279
|
-
paramTypes: {
|
|
14280
|
-
year: ParamType$1.Int,
|
|
14281
|
-
month: ParamType$1.Int,
|
|
14282
|
-
pageNumber: ParamType$1.Int,
|
|
14283
|
-
},
|
|
14284
|
-
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14285
|
-
// Get user info
|
|
14286
|
-
const { year, month, pageNumber, userId, isAdmin, } = params;
|
|
14287
|
-
// Validate user
|
|
14288
|
-
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
14289
|
-
if (!canReview) {
|
|
14290
|
-
throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
|
|
14291
|
-
}
|
|
14292
|
-
// Query for logs
|
|
14293
|
-
const response = yield _logCollection.findPaged({
|
|
14294
|
-
query: {
|
|
14295
|
-
year,
|
|
14296
|
-
month,
|
|
14297
|
-
},
|
|
14298
|
-
perPage: 1000,
|
|
14299
|
-
pageNumber,
|
|
14300
|
-
});
|
|
14301
|
-
// Return response
|
|
14302
|
-
return response;
|
|
14303
|
-
}),
|
|
14304
|
-
}));
|
|
14305
|
-
};
|
|
14306
|
-
|
|
14307
|
-
// Import shared types
|
|
14308
|
-
/**
|
|
14309
|
-
* Handle an error and respond to the client
|
|
14310
|
-
* @author Gabe Abrams
|
|
14311
|
-
* @param res express response
|
|
14312
|
-
* @param error error info
|
|
14313
|
-
* @param opts.err the error to send to the client
|
|
14314
|
-
* or the error message
|
|
14315
|
-
* @param [opts.code] an error code (only used if err.code is not
|
|
14316
|
-
* included)
|
|
14317
|
-
* @param [opts.status=500] the https status code to use
|
|
14318
|
-
* defined)
|
|
14319
|
-
*/
|
|
14320
|
-
const handleError = (res, error) => {
|
|
14321
|
-
// Get the error message
|
|
14322
|
-
let message;
|
|
14323
|
-
if (error && error.message) {
|
|
14324
|
-
message = (error.message || 'An unknown error occurred.');
|
|
14325
|
-
}
|
|
14326
|
-
else if (typeof error === 'string') {
|
|
14327
|
-
message = (error.trim().length > 0
|
|
14328
|
-
? error
|
|
14329
|
-
: 'An unknown error occurred.');
|
|
14330
|
-
}
|
|
14331
|
-
else {
|
|
14332
|
-
message = 'An unknown error occurred.';
|
|
14333
|
-
}
|
|
14334
|
-
// Get the error code
|
|
14335
|
-
const code = (error.code || ReactKitErrorCode$1.NoCode);
|
|
14336
|
-
// Get the status code
|
|
14337
|
-
const status = (error.status || 500);
|
|
14338
|
-
// Respond to user
|
|
14339
|
-
res
|
|
14340
|
-
// Set the http status code
|
|
14341
|
-
.status(status)
|
|
14342
|
-
// Send a JSON response
|
|
14343
|
-
.json({
|
|
14344
|
-
// Error message
|
|
14345
|
-
message,
|
|
14346
|
-
// Error code
|
|
14347
|
-
code,
|
|
14348
|
-
// Success = false flag so client can detect server-side errors
|
|
14349
|
-
success: false,
|
|
14350
|
-
});
|
|
14351
|
-
return undefined;
|
|
14352
|
-
};
|
|
14353
|
-
|
|
14354
|
-
/**
|
|
14355
|
-
* Send successful API response
|
|
14356
|
-
* @author Gabe Abrams
|
|
14357
|
-
* @param res express response
|
|
14358
|
-
* @param body the body of the response to send to the client
|
|
14359
|
-
*/
|
|
14360
|
-
const handleSuccess = (res, body) => {
|
|
14361
|
-
// Send a http 200 json response
|
|
14362
|
-
res.json({
|
|
14363
|
-
// Include the body as a parameter
|
|
14364
|
-
body,
|
|
14365
|
-
// Success = true flag so client can detect successful responses
|
|
14366
|
-
success: true,
|
|
14367
|
-
});
|
|
14368
|
-
return undefined;
|
|
14369
|
-
};
|
|
14370
|
-
|
|
14371
|
-
// Import shared types
|
|
14372
|
-
/**
|
|
14373
|
-
* Generate a static error page
|
|
14374
|
-
* @author Gabe Abrams
|
|
14375
|
-
* @param opts object containing all arguments
|
|
14376
|
-
* @param [opts.title=An Error Occurred] title of the error box
|
|
14377
|
-
* @param [opts.description=An unknown server error occurred. Please contact support.]
|
|
14378
|
-
* a human-readable description of the error
|
|
14379
|
-
* @param [opts.code=ReactKitErrorCode.NoCode] error code to show
|
|
14380
|
-
* @param [opts.pageTitle=opts.title] title of the page/tab if it differs from
|
|
14381
|
-
* the title of the error
|
|
14382
|
-
* @returns html of the page
|
|
14383
|
-
*/
|
|
14384
|
-
const genErrorPage = (opts = {}) => {
|
|
14385
|
-
var _a, _b, _c, _d;
|
|
14386
|
-
const title = ((_a = opts.title) !== null && _a !== void 0 ? _a : 'An Error Occurred');
|
|
14387
|
-
const pageTitle = ((_b = opts.pageTitle) !== null && _b !== void 0 ? _b : title);
|
|
14388
|
-
const description = ((_c = opts.description) !== null && _c !== void 0 ? _c : 'An unknown server error occurred. Please contact support.');
|
|
14389
|
-
const code = ((_d = opts.code) !== null && _d !== void 0 ? _d : ReactKitErrorCode$1.NoCode);
|
|
14390
|
-
return `
|
|
14391
|
-
<head>
|
|
14392
|
-
<!-- Metadata -->
|
|
14393
|
-
<meta
|
|
14394
|
-
name="viewport"
|
|
14395
|
-
content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"
|
|
14396
|
-
>
|
|
14397
|
-
|
|
14398
|
-
<!-- Title -->
|
|
14399
|
-
<title>${pageTitle}</title>
|
|
14400
|
-
|
|
14401
|
-
<!-- Bootstrap -->
|
|
14402
|
-
<link
|
|
14403
|
-
rel="stylesheet"
|
|
14404
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/css/bootstrap.min.css"
|
|
14405
|
-
integrity="sha512-siwe/oXMhSjGCwLn+scraPOWrJxHlUgMBMZXdPe2Tnk3I0x3ESCoLz7WZ5NTH6SZrywMY+PB1cjyqJ5jAluCOg=="
|
|
14406
|
-
crossorigin="anonymous"
|
|
14407
|
-
referrerpolicy="no-referrer"
|
|
14408
|
-
/>
|
|
14409
|
-
<script
|
|
14410
|
-
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/js/bootstrap.min.js"
|
|
14411
|
-
integrity="sha512-vyRAVI0IEm6LI/fVSv/Wq/d0KUfrg3hJq2Qz5FlfER69sf3ZHlOrsLriNm49FxnpUGmhx+TaJKwJ+ByTLKT+Yg=="
|
|
14412
|
-
crossorigin="anonymous"
|
|
14413
|
-
referrerpolicy="no-referrer"
|
|
14414
|
-
></script>
|
|
14415
|
-
|
|
14416
|
-
<!-- FontAwesome -->
|
|
14417
|
-
<link
|
|
14418
|
-
rel="stylesheet"
|
|
14419
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
|
|
14420
|
-
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
|
|
14421
|
-
crossorigin="anonymous"
|
|
14422
|
-
referrerpolicy="no-referrer"
|
|
14423
|
-
/>
|
|
14424
|
-
|
|
14425
|
-
<!-- Style -->
|
|
14426
|
-
<style>
|
|
14427
|
-
.DCEReactKit-pop-in {
|
|
14428
|
-
animation-name: DCEReactKit-pop-in;
|
|
14429
|
-
animation-duration: 0.5s;
|
|
14430
|
-
animation-iteration-count: 1;
|
|
14431
|
-
animation-timing-function: ease-out;
|
|
14432
|
-
animation-fill-mode: both;
|
|
14433
|
-
|
|
14434
|
-
transform-origin: center;
|
|
14435
|
-
}
|
|
14436
|
-
|
|
14437
|
-
@keyframes DCEReactKit-pop-in {
|
|
14438
|
-
0% {
|
|
14439
|
-
opacity: 0;
|
|
14440
|
-
transform: scale(0.9);
|
|
14441
|
-
}
|
|
14442
|
-
100% {
|
|
14443
|
-
opacity: 1;
|
|
14444
|
-
transform: scale(1);
|
|
14445
|
-
}
|
|
14446
|
-
}
|
|
14447
|
-
|
|
14448
|
-
.DCEReactKit-slide-in {
|
|
14449
|
-
animation-name: DCEReactKit-slide-in;
|
|
14450
|
-
animation-duration: 1s;
|
|
14451
|
-
animation-iteration-count: 1;
|
|
14452
|
-
animation-timing-function: ease-out;
|
|
14453
|
-
animation-fill-mode: both;
|
|
14454
|
-
animation-delay: 0.2s;
|
|
14455
|
-
}
|
|
14456
|
-
|
|
14457
|
-
@keyframes DCEReactKit-slide-in {
|
|
14458
|
-
0% {
|
|
14459
|
-
opacity: 0;
|
|
14460
|
-
transform: translate(0, 0.3em);
|
|
14461
|
-
}
|
|
14462
|
-
100% {
|
|
14463
|
-
opacity: 1;
|
|
14464
|
-
transform: translate(0, 0);
|
|
14465
|
-
}
|
|
14466
|
-
}
|
|
14467
|
-
</style>
|
|
14468
|
-
</head>
|
|
14469
|
-
|
|
14470
|
-
<!-- Body -->
|
|
14471
|
-
<body class="bg-dark text-center pt-3 ps-3 pe-3">
|
|
14472
|
-
<!-- Alert -->
|
|
14473
|
-
<div
|
|
14474
|
-
class="DCEReactKit-pop-in alert alert-warning d-inline-block"
|
|
14475
|
-
style="width: 50em; max-width: 100%"
|
|
14476
|
-
>
|
|
14477
|
-
<!-- Title -->
|
|
14478
|
-
<h2>
|
|
14479
|
-
<i class="me-1 fa-solid fa-triangle-exclamation"></i>
|
|
14480
|
-
${title}
|
|
14481
|
-
</h2>
|
|
14482
|
-
<!-- Description -->
|
|
14483
|
-
<div>
|
|
14484
|
-
${description}
|
|
14485
|
-
</div>
|
|
14486
|
-
</div>
|
|
14487
|
-
|
|
14488
|
-
<!-- Error Code -->
|
|
14489
|
-
<div class="DCEReactKit-slide-in text-light">
|
|
14490
|
-
<strong>
|
|
14491
|
-
Error Code:
|
|
14492
|
-
</strong>
|
|
14493
|
-
${code}
|
|
14494
|
-
</div>
|
|
14495
|
-
</body>
|
|
14496
|
-
`;
|
|
14497
|
-
};
|
|
14498
|
-
|
|
14499
|
-
/**
|
|
14500
|
-
* Generate a static info page
|
|
14501
|
-
* @author Gabe Abrams
|
|
14502
|
-
* @param opts object containing all arguments
|
|
14503
|
-
* @param opts.title title of the info box
|
|
14504
|
-
* @param opts.body a human-readable text body for the info alert
|
|
14505
|
-
* @returns the HTML for the info page
|
|
14506
|
-
*/
|
|
14507
|
-
const genInfoPage = (opts) => {
|
|
14508
|
-
const { title, body, } = opts;
|
|
14509
|
-
return `
|
|
14510
|
-
<head>
|
|
14511
|
-
<!-- Metadata -->
|
|
14512
|
-
<meta
|
|
14513
|
-
name="viewport"
|
|
14514
|
-
content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"
|
|
14515
|
-
>
|
|
14516
|
-
|
|
14517
|
-
<!-- Title -->
|
|
14518
|
-
<title>${title}</title>
|
|
14519
|
-
|
|
14520
|
-
<!-- Bootstrap -->
|
|
14521
|
-
<link
|
|
14522
|
-
rel="stylesheet"
|
|
14523
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/css/bootstrap.min.css"
|
|
14524
|
-
integrity="sha512-siwe/oXMhSjGCwLn+scraPOWrJxHlUgMBMZXdPe2Tnk3I0x3ESCoLz7WZ5NTH6SZrywMY+PB1cjyqJ5jAluCOg=="
|
|
14525
|
-
crossorigin="anonymous"
|
|
14526
|
-
referrerpolicy="no-referrer"
|
|
14527
|
-
/>
|
|
14528
|
-
<script
|
|
14529
|
-
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/js/bootstrap.min.js"
|
|
14530
|
-
integrity="sha512-vyRAVI0IEm6LI/fVSv/Wq/d0KUfrg3hJq2Qz5FlfER69sf3ZHlOrsLriNm49FxnpUGmhx+TaJKwJ+ByTLKT+Yg=="
|
|
14531
|
-
crossorigin="anonymous"
|
|
14532
|
-
referrerpolicy="no-referrer"
|
|
14533
|
-
></script>
|
|
14534
|
-
|
|
14535
|
-
<!-- FontAwesome -->
|
|
14536
|
-
<link
|
|
14537
|
-
rel="stylesheet"
|
|
14538
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
|
|
14539
|
-
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
|
|
14540
|
-
crossorigin="anonymous"
|
|
14541
|
-
referrerpolicy="no-referrer"
|
|
14542
|
-
/>
|
|
14543
|
-
|
|
14544
|
-
<!-- Style -->
|
|
14545
|
-
<style>
|
|
14546
|
-
.DCEReactKit-pop-in {
|
|
14547
|
-
animation-name: DCEReactKit-pop-in;
|
|
14548
|
-
animation-duration: 0.5s;
|
|
14549
|
-
animation-iteration-count: 1;
|
|
14550
|
-
animation-timing-function: ease-out;
|
|
14551
|
-
animation-fill-mode: both;
|
|
14552
|
-
|
|
14553
|
-
transform-origin: center;
|
|
14554
|
-
}
|
|
14555
|
-
|
|
14556
|
-
@keyframes DCEReactKit-pop-in {
|
|
14557
|
-
0% {
|
|
14558
|
-
opacity: 0;
|
|
14559
|
-
transform: scale(0.9);
|
|
14560
|
-
}
|
|
14561
|
-
100% {
|
|
14562
|
-
opacity: 1;
|
|
14563
|
-
transform: scale(1);
|
|
14564
|
-
}
|
|
14565
|
-
}
|
|
14566
|
-
</style>
|
|
14567
|
-
</head>
|
|
14568
|
-
|
|
14569
|
-
<!-- Body -->
|
|
14570
|
-
<body class="bg-dark text-center pt-3 ps-3 pe-3">
|
|
14571
|
-
<!-- Alert -->
|
|
14572
|
-
<div
|
|
14573
|
-
class="DCEReactKit-pop-in alert alert-info d-inline-block"
|
|
14574
|
-
style="width: 50em; max-width: 100%"
|
|
14575
|
-
>
|
|
14576
|
-
<!-- Title -->
|
|
14577
|
-
<h2>
|
|
14578
|
-
<i class="me-1 fa-solid fa-circle-info"></i>
|
|
14579
|
-
${title}
|
|
14580
|
-
</h2>
|
|
14581
|
-
<!-- Body -->
|
|
14582
|
-
<div>
|
|
14583
|
-
${body}
|
|
14584
|
-
</div>
|
|
14585
|
-
</div>
|
|
14586
|
-
</body>
|
|
14587
|
-
`;
|
|
14588
|
-
};
|
|
14589
|
-
|
|
14590
|
-
/**
|
|
14591
|
-
* Perform a rudimentary parsing of the user's browser agent string
|
|
14592
|
-
* @author Gabe Abrams
|
|
14593
|
-
* @param userAgent the user's browser agent
|
|
14594
|
-
* @returns user info
|
|
14595
|
-
*/
|
|
14596
|
-
const parseUserAgent = (userAgent) => {
|
|
14597
|
-
/* ------------- Browser ------------ */
|
|
14598
|
-
let browser = {
|
|
14599
|
-
name: 'Unknown',
|
|
14600
|
-
version: 'Unknown',
|
|
14601
|
-
};
|
|
14602
|
-
// Parse user agent
|
|
14603
|
-
let verOffset;
|
|
14604
|
-
let nameOffset;
|
|
14605
|
-
if ((verOffset = userAgent.indexOf('Opera')) !== -1) {
|
|
14606
|
-
// In Opera, the true version is after 'Opera' or after 'Version'
|
|
14607
|
-
browser = {
|
|
14608
|
-
name: 'Opera',
|
|
14609
|
-
version: userAgent.substring(verOffset + 6),
|
|
14610
|
-
};
|
|
14611
|
-
if ((verOffset = userAgent.indexOf('Version')) !== -1) {
|
|
14612
|
-
browser.version = userAgent.substring(verOffset + 8);
|
|
14613
|
-
}
|
|
14614
|
-
}
|
|
14615
|
-
else if ((verOffset = userAgent.indexOf('MSIE')) !== -1) {
|
|
14616
|
-
// In MSIE, the true version is after 'MSIE' in userAgent
|
|
14617
|
-
browser = {
|
|
14618
|
-
name: 'Internet Explorer',
|
|
14619
|
-
version: userAgent.substring(verOffset + 5),
|
|
14620
|
-
};
|
|
14621
|
-
}
|
|
14622
|
-
else if ((verOffset = userAgent.indexOf('Chrome')) !== -1) {
|
|
14623
|
-
// In Chrome, the true version is after 'Chrome'
|
|
14624
|
-
browser = {
|
|
14625
|
-
name: 'Chrome',
|
|
14626
|
-
version: userAgent.substring(verOffset + 7),
|
|
14627
|
-
};
|
|
14628
|
-
}
|
|
14629
|
-
else if ((verOffset = userAgent.indexOf('Safari')) !== -1) {
|
|
14630
|
-
// In Safari, the true version is after 'Safari' or after 'Version'
|
|
14631
|
-
browser = {
|
|
14632
|
-
name: 'Safari',
|
|
14633
|
-
version: userAgent.substring(verOffset + 7),
|
|
14634
|
-
};
|
|
14635
|
-
if ((verOffset = userAgent.indexOf('Version')) !== -1) {
|
|
14636
|
-
browser.version = userAgent.substring(verOffset + 8);
|
|
14637
|
-
}
|
|
14638
|
-
}
|
|
14639
|
-
else if ((verOffset = userAgent.indexOf('Firefox')) != -1) {
|
|
14640
|
-
// In Firefox, the true version is after 'Firefox'
|
|
14641
|
-
browser = {
|
|
14642
|
-
name: 'Firefox',
|
|
14643
|
-
version: userAgent.substring(verOffset + 8),
|
|
14644
|
-
};
|
|
14645
|
-
}
|
|
14646
|
-
else if ((nameOffset = userAgent.lastIndexOf(' ') + 1)
|
|
14647
|
-
< (verOffset = userAgent.lastIndexOf('/'))) {
|
|
14648
|
-
browser = {
|
|
14649
|
-
name: userAgent.substring(nameOffset, verOffset),
|
|
14650
|
-
version: userAgent.substring(verOffset + 1),
|
|
14651
|
-
};
|
|
14652
|
-
}
|
|
14653
|
-
// Postprocess version
|
|
14654
|
-
// trim the fullVersion string at semicolon/space if present
|
|
14655
|
-
let ix;
|
|
14656
|
-
if ((ix = browser.version.indexOf(';')) !== -1) {
|
|
14657
|
-
browser.version = browser.version.substring(0, ix);
|
|
14658
|
-
}
|
|
14659
|
-
if ((ix = browser.version.indexOf(' ')) !== -1) {
|
|
14660
|
-
browser.version = browser.version.substring(0, ix);
|
|
14661
|
-
}
|
|
14662
|
-
/* ------------- Device ------------- */
|
|
14663
|
-
// Detect os
|
|
14664
|
-
let os = 'Unknown';
|
|
14665
|
-
if (userAgent.includes('Linux')) {
|
|
14666
|
-
os = 'Linux';
|
|
14667
|
-
}
|
|
14668
|
-
else if (userAgent.includes('like Mac')) {
|
|
14669
|
-
os = 'iOS';
|
|
14670
|
-
}
|
|
14671
|
-
else if (userAgent.includes('Mac')) {
|
|
14672
|
-
os = 'Mac';
|
|
14673
|
-
}
|
|
14674
|
-
else if (userAgent.includes('Android')) {
|
|
14675
|
-
os = 'Android';
|
|
14676
|
-
}
|
|
14677
|
-
else if (userAgent.includes('Win')) {
|
|
14678
|
-
os = 'Win';
|
|
14679
|
-
}
|
|
14680
|
-
// Check if mobile
|
|
14681
|
-
const isMobile = !!userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/);
|
|
14682
|
-
// Device
|
|
14683
|
-
const device = {
|
|
14684
|
-
isMobile,
|
|
14685
|
-
os,
|
|
14686
|
-
};
|
|
14687
|
-
/* ------------- Finish ------------- */
|
|
14688
|
-
// Return info
|
|
14689
|
-
return {
|
|
14690
|
-
browser,
|
|
14691
|
-
device,
|
|
14692
|
-
};
|
|
14693
|
-
};
|
|
14694
|
-
|
|
14695
|
-
/**
|
|
14696
|
-
* Generate an express API route handler
|
|
14697
|
-
* @author Gabe Abrams
|
|
14698
|
-
* @param opts object containing all arguments
|
|
14699
|
-
* @param opts.paramTypes map containing the types for each parameter that is
|
|
14700
|
-
* included in the request (map: param name => type)
|
|
14701
|
-
* @param opts.handler function that processes the request
|
|
14702
|
-
* @param [opts.skipSessionCheck] if true, skip the session check (allow users
|
|
14703
|
-
* to not be logged in and launched via LTI)
|
|
14704
|
-
* @param [opts.allowedHosts] if included, only allow requests from these hosts
|
|
14705
|
-
* (start a hostname with a "*" to only check the end of the hostname)
|
|
14706
|
-
* you can include just one string instead of an array
|
|
14707
|
-
* @param [opts.bannedHosts] if included, do not allow requests from these hosts
|
|
14708
|
-
* (start a hostname with a "*" to only check the end of the hostname)
|
|
14709
|
-
* you can include just one string instead of an array
|
|
14710
|
-
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
14711
|
-
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
14712
|
-
* where the error message is prefixed with this string. For example,
|
|
14713
|
-
* if unhandledErrorMessagePrefix is
|
|
14714
|
-
* 'While saving progress, we encountered an error:'
|
|
14715
|
-
* and the error is 'progressInfo is not an object',
|
|
14716
|
-
* the client will receive an error with the message
|
|
14717
|
-
* 'While saving progress, we encountered an error: progressInfo is not an object'
|
|
14718
|
-
* @returns express route handler that takes the following arguments:
|
|
14719
|
-
* params (map: param name => value),
|
|
14720
|
-
* req (express request object),
|
|
14721
|
-
* next (express next function),
|
|
14722
|
-
* send (a function that sends a string to the client),
|
|
14723
|
-
* redirect (takes a url and redirects the user to that url),
|
|
14724
|
-
* renderErrorPage (shows a static error page to the user),
|
|
14725
|
-
* renderInfoPage (shows a static info page to the user),
|
|
14726
|
-
* renderCustomHTML (renders custom html and sends it to the user),
|
|
14727
|
-
* and returns the value to send to the client as a JSON API response, or
|
|
14728
|
-
* calls next() or redirect(...) or send(...) or renderErrorPage(...).
|
|
14729
|
-
* Note: params also has userId, userFirstName,
|
|
14730
|
-
* userLastName, userEmail, userAvatarURL, isLearner, isTTM, isAdmin,
|
|
14731
|
-
* and any other variables that
|
|
14732
|
-
* are directly added to the session, if the user does have a session.
|
|
14733
|
-
*/
|
|
14734
|
-
const genRouteHandler = (opts) => {
|
|
14735
|
-
// Return a route handler
|
|
14736
|
-
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14737
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
14738
|
-
// Output params
|
|
14739
|
-
const output = {};
|
|
14740
|
-
/*----------------------------------------*/
|
|
14741
|
-
/* ----------- Hostname Check ----------- */
|
|
14742
|
-
/*----------------------------------------*/
|
|
14743
|
-
// Get hostnames
|
|
14744
|
-
const originURL = String(req.get('origin')
|
|
14745
|
-
|| req.headers.origin
|
|
14746
|
-
|| req.headers.referer);
|
|
14747
|
-
const originHostname = (originURL
|
|
14748
|
-
// Remove protocol
|
|
14749
|
-
.replace(/(^\w+:|^)\/\//, '')
|
|
14750
|
-
// Remove port
|
|
14751
|
-
.replace(/:\d+$/, ''));
|
|
14752
|
-
const serverHostname = String(req.hostname);
|
|
14753
|
-
// Check allowed
|
|
14754
|
-
if (opts.allowedHosts) {
|
|
14755
|
-
// Only accept requests from allowed hosts
|
|
14756
|
-
const allowedArray = (Array.isArray(opts.allowedHosts)
|
|
14757
|
-
? opts.allowedHosts
|
|
14758
|
-
: [opts.allowedHosts]);
|
|
14759
|
-
// Check if server is localhost
|
|
14760
|
-
if (serverHostname === 'localhost') {
|
|
14761
|
-
// Allow localhost
|
|
14762
|
-
allowedArray.push('localhost');
|
|
14763
|
-
}
|
|
14764
|
-
// Check if current host is allowed
|
|
14765
|
-
const allowed = allowedArray.some((allowedHost) => {
|
|
14766
|
-
if (allowedHost.startsWith('*')) {
|
|
14767
|
-
// Check end of hostname
|
|
14768
|
-
return originHostname.endsWith(allowedHost.substring(1));
|
|
14769
|
-
}
|
|
14770
|
-
// Check full hostname
|
|
14771
|
-
return originHostname.toLowerCase() === allowedHost.toLowerCase();
|
|
14772
|
-
});
|
|
14773
|
-
// If not allowed, return error
|
|
14774
|
-
if (!allowed) {
|
|
14775
|
-
return handleError(res, {
|
|
14776
|
-
message: 'You are not allowed to access this endpoint.',
|
|
14777
|
-
code: ReactKitErrorCode$1.HostNotAllowed,
|
|
14778
|
-
status: 403,
|
|
14779
|
-
});
|
|
14780
|
-
}
|
|
14781
|
-
}
|
|
14782
|
-
// Check banned
|
|
14783
|
-
if (opts.bannedHosts) {
|
|
14784
|
-
// Do not allow requests from banned hosts
|
|
14785
|
-
const bannedArray = (Array.isArray(opts.bannedHosts)
|
|
14786
|
-
? opts.bannedHosts
|
|
14787
|
-
: [opts.bannedHosts]);
|
|
14788
|
-
// Check if current host is banned
|
|
14789
|
-
const banned = bannedArray.some((bannedHost) => {
|
|
14790
|
-
if (bannedHost.startsWith('*')) {
|
|
14791
|
-
// Check end of hostname
|
|
14792
|
-
return originHostname.endsWith(bannedHost.substring(1));
|
|
14793
|
-
}
|
|
14794
|
-
// Check full hostname
|
|
14795
|
-
return originHostname.toLowerCase() === bannedHost.toLowerCase();
|
|
14796
|
-
});
|
|
14797
|
-
// If banned, return error
|
|
14798
|
-
if (banned) {
|
|
14799
|
-
return handleError(res, {
|
|
14800
|
-
message: 'You are not allowed to access this endpoint.',
|
|
14801
|
-
code: ReactKitErrorCode$1.HostBanned,
|
|
14802
|
-
status: 403,
|
|
14803
|
-
});
|
|
14804
|
-
}
|
|
14805
|
-
}
|
|
14806
|
-
/*----------------------------------------*/
|
|
14807
|
-
/* ------------ Parse Params ------------ */
|
|
14808
|
-
/*----------------------------------------*/
|
|
14809
|
-
// Process items one by one
|
|
14810
|
-
const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
|
|
14811
|
-
for (let i = 0; i < paramList.length; i++) {
|
|
14812
|
-
const [name, type] = paramList[i];
|
|
14813
|
-
// Find the value as a string
|
|
14814
|
-
const value = (req.params[name]
|
|
14815
|
-
|| req.query[name]
|
|
14816
|
-
|| req.body[name]);
|
|
14817
|
-
// Parse
|
|
14818
|
-
if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
|
|
14819
|
-
// Boolean
|
|
14820
|
-
// Handle case where value doesn't exist
|
|
14821
|
-
if (value === undefined) {
|
|
14822
|
-
if (type === ParamType$1.BooleanOptional) {
|
|
14823
|
-
output[name] = undefined;
|
|
14824
|
-
}
|
|
14825
|
-
else {
|
|
14826
|
-
return handleError(res, {
|
|
14827
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
14828
|
-
code: ReactKitErrorCode$1.MissingParameter,
|
|
14829
|
-
status: 422,
|
|
14830
|
-
});
|
|
14831
|
-
}
|
|
14832
|
-
}
|
|
14833
|
-
else {
|
|
14834
|
-
// Value exists
|
|
14835
|
-
// Simplify value
|
|
14836
|
-
const simpleVal = (String(value)
|
|
14837
|
-
.trim()
|
|
14838
|
-
.toLowerCase());
|
|
14839
|
-
// Parse
|
|
14840
|
-
output[name] = ([
|
|
14841
|
-
'true',
|
|
14842
|
-
'yes',
|
|
14843
|
-
'y',
|
|
14844
|
-
'1',
|
|
14845
|
-
't',
|
|
14846
|
-
].indexOf(simpleVal) >= 0);
|
|
14847
|
-
}
|
|
14848
|
-
}
|
|
14849
|
-
else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
|
|
14850
|
-
// Float
|
|
14851
|
-
// Handle case where value doesn't exist
|
|
14852
|
-
if (value === undefined) {
|
|
14853
|
-
if (type === ParamType$1.FloatOptional) {
|
|
14854
|
-
output[name] = undefined;
|
|
14855
|
-
}
|
|
14856
|
-
else {
|
|
14857
|
-
return handleError(res, {
|
|
14858
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
14859
|
-
code: ReactKitErrorCode$1.MissingParameter,
|
|
14860
|
-
status: 422,
|
|
14861
|
-
});
|
|
14862
|
-
}
|
|
14863
|
-
}
|
|
14864
|
-
else if (!Number.isNaN(Number.parseFloat(String(value)))) {
|
|
14865
|
-
// Value is a number
|
|
14866
|
-
output[name] = Number.parseFloat(String(value));
|
|
14867
|
-
}
|
|
14868
|
-
else {
|
|
14869
|
-
// Issue!
|
|
14870
|
-
return handleError(res, {
|
|
14871
|
-
message: `Request data was malformed: ${name} was not a valid float.`,
|
|
14872
|
-
code: ReactKitErrorCode$1.InvalidParameter,
|
|
14873
|
-
status: 422,
|
|
14874
|
-
});
|
|
14875
|
-
}
|
|
14876
|
-
}
|
|
14877
|
-
else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
|
|
14878
|
-
// Int
|
|
14879
|
-
// Handle case where value doesn't exist
|
|
14880
|
-
if (value === undefined) {
|
|
14881
|
-
if (type === ParamType$1.IntOptional) {
|
|
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.parseInt(String(value), 10))) {
|
|
14893
|
-
// Value is a number
|
|
14894
|
-
output[name] = Number.parseInt(String(value), 10);
|
|
14895
|
-
}
|
|
14896
|
-
else {
|
|
14897
|
-
// Issue!
|
|
14898
|
-
return handleError(res, {
|
|
14899
|
-
message: `Request data was malformed: ${name} was not a valid int.`,
|
|
14900
|
-
code: ReactKitErrorCode$1.InvalidParameter,
|
|
14901
|
-
status: 422,
|
|
14902
|
-
});
|
|
14903
|
-
}
|
|
14904
|
-
}
|
|
14905
|
-
else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
|
|
14906
|
-
// Stringified JSON
|
|
14907
|
-
// Handle case where value doesn't exist
|
|
14908
|
-
if (value === undefined) {
|
|
14909
|
-
if (type === ParamType$1.JSONOptional) {
|
|
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 {
|
|
14921
|
-
// Value exists
|
|
14922
|
-
// Parse
|
|
14923
|
-
try {
|
|
14924
|
-
output[name] = JSON.parse(String(value));
|
|
14925
|
-
}
|
|
14926
|
-
catch (err) {
|
|
14927
|
-
return handleError(res, {
|
|
14928
|
-
message: `Request data was malformed: ${name} was not a valid JSON payload.`,
|
|
14929
|
-
code: ReactKitErrorCode$1.InvalidParameter,
|
|
14930
|
-
status: 422,
|
|
14931
|
-
});
|
|
14932
|
-
}
|
|
14933
|
-
}
|
|
14934
|
-
}
|
|
14935
|
-
else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
|
|
14936
|
-
// String
|
|
14937
|
-
// Handle case where value doesn't exist
|
|
14938
|
-
if (value === undefined) {
|
|
14939
|
-
if (type === ParamType$1.StringOptional) {
|
|
14940
|
-
output[name] = undefined;
|
|
14941
|
-
}
|
|
14942
|
-
else {
|
|
14943
|
-
return handleError(res, {
|
|
14944
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
14945
|
-
code: ReactKitErrorCode$1.MissingParameter,
|
|
14946
|
-
status: 422,
|
|
14947
|
-
});
|
|
14948
|
-
}
|
|
14949
|
-
}
|
|
14950
|
-
else {
|
|
14951
|
-
// Value exists
|
|
14952
|
-
// Leave as is
|
|
14953
|
-
output[name] = value;
|
|
14954
|
-
}
|
|
14955
|
-
}
|
|
14956
|
-
else {
|
|
14957
|
-
// No valid data type
|
|
14958
|
-
return handleError(res, {
|
|
14959
|
-
message: `An internal error occurred: we could not determine the type of ${name}.`,
|
|
14960
|
-
code: ReactKitErrorCode$1.InvalidParameter,
|
|
14961
|
-
status: 422,
|
|
14962
|
-
});
|
|
14963
|
-
}
|
|
14964
|
-
}
|
|
14965
|
-
/*----------------------------------------*/
|
|
14966
|
-
/* ------------- Launch Info ------------ */
|
|
14967
|
-
/*----------------------------------------*/
|
|
14968
|
-
// Get launch info
|
|
14969
|
-
const { launched, launchInfo } = cacclGetLaunchInfo(req);
|
|
14970
|
-
if (
|
|
14971
|
-
// Not launched
|
|
14972
|
-
(!launched || !launchInfo)
|
|
14973
|
-
// Not skipping the session check
|
|
14974
|
-
&& !opts.skipSessionCheck) {
|
|
14975
|
-
return handleError(res, {
|
|
14976
|
-
message: 'Your session has expired. Please refresh the page and try again.',
|
|
14977
|
-
code: ReactKitErrorCode$1.SessionExpired,
|
|
14978
|
-
status: 401,
|
|
14979
|
-
});
|
|
14980
|
-
}
|
|
14981
|
-
// Error if user info cannot be found
|
|
14982
|
-
if (
|
|
14983
|
-
// User information is incomplete
|
|
14984
|
-
(!launchInfo
|
|
14985
|
-
|| !launchInfo.userId
|
|
14986
|
-
|| !launchInfo.userFirstName
|
|
14987
|
-
|| !launchInfo.userLastName
|
|
14988
|
-
|| (launchInfo.notInCourse
|
|
14989
|
-
&& !launchInfo.isAdmin)
|
|
14990
|
-
|| (!launchInfo.isTTM
|
|
14991
|
-
&& !launchInfo.isLearner
|
|
14992
|
-
&& !launchInfo.isAdmin))
|
|
14993
|
-
// Not skipping the session check
|
|
14994
|
-
&& !opts.skipSessionCheck) {
|
|
14995
|
-
return handleError(res, {
|
|
14996
|
-
message: 'Your session was invalid. Please refresh the page and try again.',
|
|
14997
|
-
code: ReactKitErrorCode$1.SessionExpired,
|
|
14998
|
-
status: 401,
|
|
14999
|
-
});
|
|
15000
|
-
}
|
|
15001
|
-
// Add launch info to output
|
|
15002
|
-
output.userId = (launchInfo
|
|
15003
|
-
? launchInfo.userId
|
|
15004
|
-
: ((_b = output.userId) !== null && _b !== void 0 ? _b : undefined));
|
|
15005
|
-
output.userFirstName = (launchInfo
|
|
15006
|
-
? launchInfo.userFirstName
|
|
15007
|
-
: ((_c = output.userFirstName) !== null && _c !== void 0 ? _c : undefined));
|
|
15008
|
-
output.userLastName = (launchInfo
|
|
15009
|
-
? launchInfo.userLastName
|
|
15010
|
-
: ((_d = output.userLastName) !== null && _d !== void 0 ? _d : undefined));
|
|
15011
|
-
output.userEmail = (launchInfo
|
|
15012
|
-
? launchInfo.userEmail
|
|
15013
|
-
: ((_e = output.userEmail) !== null && _e !== void 0 ? _e : undefined));
|
|
15014
|
-
output.userAvatarURL = (launchInfo
|
|
15015
|
-
? ((_f = launchInfo.userImage) !== null && _f !== void 0 ? _f : 'http://www.gravatar.com/avatar/?d=identicon')
|
|
15016
|
-
: ((_g = output.userAvatarURL) !== null && _g !== void 0 ? _g : undefined));
|
|
15017
|
-
output.isLearner = (launchInfo
|
|
15018
|
-
? !!launchInfo.isLearner
|
|
15019
|
-
: ((_h = output.isLearner) !== null && _h !== void 0 ? _h : undefined));
|
|
15020
|
-
output.isTTM = (launchInfo
|
|
15021
|
-
? !!launchInfo.isTTM
|
|
15022
|
-
: ((_j = output.isTTM) !== null && _j !== void 0 ? _j : undefined));
|
|
15023
|
-
output.isAdmin = (launchInfo
|
|
15024
|
-
? !!launchInfo.isAdmin
|
|
15025
|
-
: ((_k = output.isAdmin) !== null && _k !== void 0 ? _k : undefined));
|
|
15026
|
-
output.courseId = (launchInfo
|
|
15027
|
-
? ((_l = output.courseId) !== null && _l !== void 0 ? _l : launchInfo.courseId)
|
|
15028
|
-
: ((_m = output.courseId) !== null && _m !== void 0 ? _m : undefined));
|
|
15029
|
-
output.courseName = (launchInfo
|
|
15030
|
-
? launchInfo.contextLabel
|
|
15031
|
-
: ((_o = output.courseName) !== null && _o !== void 0 ? _o : undefined));
|
|
15032
|
-
// Add other session variables
|
|
15033
|
-
Object.keys(req.session).forEach((propName) => {
|
|
15034
|
-
// Skip if prop already in output
|
|
15035
|
-
if (output[propName] !== undefined) {
|
|
15036
|
-
return;
|
|
15037
|
-
}
|
|
15038
|
-
// Add to output
|
|
15039
|
-
const value = req.session[propName];
|
|
15040
|
-
if (typeof value === 'string'
|
|
15041
|
-
|| typeof value === 'boolean'
|
|
15042
|
-
|| typeof value === 'number') {
|
|
15043
|
-
output[propName] = value;
|
|
15044
|
-
}
|
|
15045
|
-
});
|
|
15046
|
-
/*----------------------------------------*/
|
|
15047
|
-
/* ----- Require Course Consistency ----- */
|
|
15048
|
-
/*----------------------------------------*/
|
|
15049
|
-
// Make sure the user actually launched from the appropriate course
|
|
15050
|
-
if (output.courseId
|
|
15051
|
-
&& launchInfo
|
|
15052
|
-
&& launchInfo.courseId
|
|
15053
|
-
&& output.courseId !== launchInfo.courseId
|
|
15054
|
-
&& !output.isTTM
|
|
15055
|
-
&& !output.isAdmin) {
|
|
15056
|
-
// Course of interest is not the launch course
|
|
15057
|
-
return handleError(res, {
|
|
15058
|
-
message: 'You switched sessions by opening this app in another tab. Please refresh the page and try again.',
|
|
15059
|
-
code: ReactKitErrorCode$1.WrongCourse,
|
|
15060
|
-
status: 401,
|
|
15061
|
-
});
|
|
15062
|
-
}
|
|
15063
|
-
/*----------------------------------------*/
|
|
15064
|
-
/* Require Proper Permissions */
|
|
15065
|
-
/*----------------------------------------*/
|
|
15066
|
-
// Add TTM endpoint security
|
|
15067
|
-
if (
|
|
15068
|
-
// This is a TTM endpoint
|
|
15069
|
-
req.path.startsWith('/api/ttm')
|
|
15070
|
-
// User is not a TTM
|
|
15071
|
-
&& (
|
|
15072
|
-
// User is not a TTM
|
|
15073
|
-
!output.isTTM
|
|
15074
|
-
// User is not an admin
|
|
15075
|
-
&& !output.isAdmin)) {
|
|
15076
|
-
// User does not have access
|
|
15077
|
-
return handleError(res, {
|
|
15078
|
-
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.',
|
|
15079
|
-
code: ReactKitErrorCode$1.NotTTM,
|
|
15080
|
-
status: 401,
|
|
15081
|
-
});
|
|
15082
|
-
}
|
|
15083
|
-
// Add Admin endpoint security
|
|
15084
|
-
if (
|
|
15085
|
-
// This is an admin endpoint
|
|
15086
|
-
req.path.startsWith('/api/admin')
|
|
15087
|
-
// User is not an admin
|
|
15088
|
-
&& !output.isAdmin) {
|
|
15089
|
-
// User does not have access
|
|
15090
|
-
return handleError(res, {
|
|
15091
|
-
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.',
|
|
15092
|
-
code: ReactKitErrorCode$1.NotAdmin,
|
|
15093
|
-
status: 401,
|
|
15094
|
-
});
|
|
15095
|
-
}
|
|
15096
|
-
/*----------------------------------------*/
|
|
15097
|
-
/* ------------- Log Handler ------------ */
|
|
15098
|
-
/*----------------------------------------*/
|
|
15099
|
-
// Create a log handler function
|
|
15100
|
-
/**
|
|
15101
|
-
* Log an event on the server
|
|
15102
|
-
* @author Gabe Abrams
|
|
15103
|
-
*/
|
|
15104
|
-
const logServerEvent = (logOpts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15105
|
-
var _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
|
|
15106
|
-
// NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
|
|
15107
|
-
// that indicates that this is actually a client event, but we don't
|
|
15108
|
-
// include that in the LogFunction type because this is internal and
|
|
15109
|
-
// hidden from users
|
|
15110
|
-
try {
|
|
15111
|
-
// Parse user agent
|
|
15112
|
-
const { browser, device, } = parseUserAgent(req.headers['user-agent']);
|
|
15113
|
-
// Get time info in ET
|
|
15114
|
-
const { timestamp, year, month, day, hour, minute, } = getTimeInfoInET();
|
|
15115
|
-
// Main log info
|
|
15116
|
-
const mainLogInfo = {
|
|
15117
|
-
id: `${launchInfo ? launchInfo.userId : 'unknown'}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
|
|
15118
|
-
userFirstName: (launchInfo ? launchInfo.userFirstName : 'unknown'),
|
|
15119
|
-
userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'),
|
|
15120
|
-
userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'),
|
|
15121
|
-
userId: (launchInfo ? launchInfo.userId : 'unknown'),
|
|
15122
|
-
isLearner: (launchInfo && !!launchInfo.isLearner),
|
|
15123
|
-
isAdmin: (launchInfo && !!launchInfo.isAdmin),
|
|
15124
|
-
isTTM: (launchInfo && !!launchInfo.isTTM),
|
|
15125
|
-
courseId: (launchInfo ? launchInfo.courseId : 'unknown'),
|
|
15126
|
-
courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'),
|
|
15127
|
-
browser,
|
|
15128
|
-
device,
|
|
15129
|
-
year,
|
|
15130
|
-
month,
|
|
15131
|
-
day,
|
|
15132
|
-
hour,
|
|
15133
|
-
minute,
|
|
15134
|
-
timestamp,
|
|
15135
|
-
context: (typeof logOpts.context === 'string'
|
|
15136
|
-
? logOpts.context
|
|
15137
|
-
: ((_q = ((_p = logOpts.context) !== null && _p !== void 0 ? _p : {})._) !== null && _q !== void 0 ? _q : LogBuiltInMetadata.Context.Uncategorized)),
|
|
15138
|
-
subcontext: ((_r = logOpts.subcontext) !== null && _r !== void 0 ? _r : LogBuiltInMetadata.Context.Uncategorized),
|
|
15139
|
-
tags: ((_s = logOpts.tags) !== null && _s !== void 0 ? _s : []),
|
|
15140
|
-
level: ((_t = logOpts.level) !== null && _t !== void 0 ? _t : LogLevel$1.Info),
|
|
15141
|
-
metadata: ((_u = logOpts.metadata) !== null && _u !== void 0 ? _u : {}),
|
|
15142
|
-
};
|
|
15143
|
-
// Type-specific info
|
|
15144
|
-
const typeSpecificInfo = (('error' in opts && opts.error)
|
|
15145
|
-
? {
|
|
15146
|
-
type: LogType$1.Error,
|
|
15147
|
-
errorMessage: (_v = logOpts.error.message) !== null && _v !== void 0 ? _v : 'Unknown message',
|
|
15148
|
-
errorCode: (_w = logOpts.error.code) !== null && _w !== void 0 ? _w : ReactKitErrorCode$1.NoCode,
|
|
15149
|
-
errorStack: (_x = logOpts.error.stack) !== null && _x !== void 0 ? _x : 'No stack',
|
|
15150
|
-
}
|
|
15151
|
-
: {
|
|
15152
|
-
type: LogType$1.Action,
|
|
15153
|
-
target: ((_y = logOpts.target) !== null && _y !== void 0 ? _y : LogBuiltInMetadata.Target.NoTarget),
|
|
15154
|
-
action: ((_z = logOpts.action) !== null && _z !== void 0 ? _z : LogAction$1.Unknown),
|
|
15155
|
-
});
|
|
15156
|
-
// Source-specific info
|
|
15157
|
-
const sourceSpecificInfo = (logOpts.overrideAsClientEvent
|
|
15158
|
-
? {
|
|
15159
|
-
source: LogSource$1.Client,
|
|
15160
|
-
}
|
|
15161
|
-
: {
|
|
15162
|
-
source: LogSource$1.Server,
|
|
15163
|
-
routePath: req.path,
|
|
15164
|
-
routeTemplate: req.route.path,
|
|
15165
|
-
});
|
|
15166
|
-
// Build log event
|
|
15167
|
-
const log = Object.assign(Object.assign(Object.assign({}, mainLogInfo), typeSpecificInfo), sourceSpecificInfo);
|
|
15168
|
-
// Either print to console or save to db
|
|
15169
|
-
const logCollection = internalGetLogCollection();
|
|
15170
|
-
if (logCollection) {
|
|
15171
|
-
// Store to the log collection
|
|
15172
|
-
yield logCollection.insert(log);
|
|
15173
|
-
}
|
|
15174
|
-
else if (log.type === LogType$1.Error) {
|
|
15175
|
-
// Print to console
|
|
15176
|
-
// eslint-disable-next-line no-console
|
|
15177
|
-
console.error('dce-reactkit error log:', log);
|
|
15178
|
-
}
|
|
15179
|
-
else {
|
|
15180
|
-
// eslint-disable-next-line no-console
|
|
15181
|
-
console.log('dce-reactkit action log:', log);
|
|
15182
|
-
}
|
|
15183
|
-
// Return log entry
|
|
15184
|
-
return log;
|
|
15185
|
-
}
|
|
15186
|
-
catch (err) {
|
|
15187
|
-
// Print because we cannot store the error
|
|
15188
|
-
// eslint-disable-next-line no-console
|
|
15189
|
-
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);
|
|
15190
|
-
// Create a dummy log to return
|
|
15191
|
-
const dummyMainInfo = {
|
|
15192
|
-
id: '-1',
|
|
15193
|
-
userFirstName: 'Unknown',
|
|
15194
|
-
userLastName: 'Unknown',
|
|
15195
|
-
userEmail: 'unknown@harvard.edu',
|
|
15196
|
-
userId: 1,
|
|
15197
|
-
isLearner: false,
|
|
15198
|
-
isAdmin: false,
|
|
15199
|
-
isTTM: false,
|
|
15200
|
-
courseId: 1,
|
|
15201
|
-
courseName: 'Unknown',
|
|
15202
|
-
browser: {
|
|
15203
|
-
name: 'Unknown',
|
|
15204
|
-
version: 'Unknown',
|
|
15205
|
-
},
|
|
15206
|
-
device: {
|
|
15207
|
-
isMobile: false,
|
|
15208
|
-
os: 'Unknown',
|
|
15209
|
-
},
|
|
15210
|
-
year: 1,
|
|
15211
|
-
month: 1,
|
|
15212
|
-
day: 1,
|
|
15213
|
-
hour: 1,
|
|
15214
|
-
minute: 1,
|
|
15215
|
-
timestamp: Date.now(),
|
|
15216
|
-
tags: [],
|
|
15217
|
-
level: LogLevel$1.Warn,
|
|
15218
|
-
metadata: {},
|
|
15219
|
-
context: LogBuiltInMetadata.Context.Uncategorized,
|
|
15220
|
-
subcontext: LogBuiltInMetadata.Context.Uncategorized,
|
|
15221
|
-
};
|
|
15222
|
-
const dummyTypeSpecificInfo = {
|
|
15223
|
-
type: LogType$1.Error,
|
|
15224
|
-
errorMessage: 'Unknown',
|
|
15225
|
-
errorCode: 'Unknown',
|
|
15226
|
-
errorStack: 'No Stack',
|
|
15227
|
-
};
|
|
15228
|
-
const dummySourceSpecificInfo = {
|
|
15229
|
-
source: LogSource$1.Server,
|
|
15230
|
-
routePath: req.path,
|
|
15231
|
-
routeTemplate: req.route.path,
|
|
15232
|
-
};
|
|
15233
|
-
const log = Object.assign(Object.assign(Object.assign({}, dummyMainInfo), dummyTypeSpecificInfo), dummySourceSpecificInfo);
|
|
15234
|
-
return log;
|
|
15235
|
-
}
|
|
15236
|
-
});
|
|
15237
|
-
/*------------------------------------------------------------------------*/
|
|
15238
|
-
/* Call handler */
|
|
15239
|
-
/*------------------------------------------------------------------------*/
|
|
15240
|
-
// Keep track of whether a response was already sent
|
|
15241
|
-
let responseSent = false;
|
|
15242
|
-
/**
|
|
15243
|
-
* Redirect the user to another path or url
|
|
15244
|
-
* @author Gabe Abrams
|
|
15245
|
-
* @param pathOrURL the path or url to redirect to
|
|
15246
|
-
*/
|
|
15247
|
-
const redirect = (pathOrURL) => {
|
|
15248
|
-
responseSent = true;
|
|
15249
|
-
res.redirect(pathOrURL);
|
|
15250
|
-
};
|
|
15251
|
-
/**
|
|
15252
|
-
* Send text to the client (with an optional status code)
|
|
15253
|
-
* @author Gabe Abrams
|
|
15254
|
-
* @param text the text to send to the client
|
|
15255
|
-
* @parm [status=200] the http status code to send
|
|
15256
|
-
*/
|
|
15257
|
-
const send = (text, status = 200) => {
|
|
15258
|
-
responseSent = true;
|
|
15259
|
-
res.status(status).send(text);
|
|
15260
|
-
};
|
|
15261
|
-
/**
|
|
15262
|
-
* Render an error page
|
|
15263
|
-
* @author Gabe Abrams
|
|
15264
|
-
* @param renderOpts object containing all arguments
|
|
15265
|
-
* @param [renderOpts.title=An Error Occurred] title of the error box
|
|
15266
|
-
* @param [renderOpts.description=An unknown server error occurred. Please contact support.]
|
|
15267
|
-
* a human-readable description of the error
|
|
15268
|
-
* @param [renderOpts.code=ReactKitErrorCode.NoCode] error code to show
|
|
15269
|
-
* @param [renderOpts.pageTitle=renderOpts.title] title of the page/tab if it differs from
|
|
15270
|
-
* the title of the error
|
|
15271
|
-
* @param [renderOpts.status=500] http status code
|
|
15272
|
-
*/
|
|
15273
|
-
const renderErrorPage = (renderOpts = {}) => {
|
|
15274
|
-
var _a, _b, _c;
|
|
15275
|
-
const html = genErrorPage(renderOpts);
|
|
15276
|
-
send(html, (_a = renderOpts.status) !== null && _a !== void 0 ? _a : 500);
|
|
15277
|
-
// Log server-side error if not a session expired error or 404
|
|
15278
|
-
if (renderOpts.status && renderOpts.status === 404) {
|
|
15279
|
-
return;
|
|
15280
|
-
}
|
|
15281
|
-
if ((_b = renderOpts.title) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('session expired')) {
|
|
15282
|
-
return;
|
|
15283
|
-
}
|
|
15284
|
-
logServerEvent({
|
|
15285
|
-
context: LogBuiltInMetadata.Context.ServerRenderedErrorPage,
|
|
15286
|
-
error: {
|
|
15287
|
-
message: `${renderOpts.title}: ${renderOpts.description}`,
|
|
15288
|
-
code: renderOpts.code,
|
|
15289
|
-
},
|
|
15290
|
-
metadata: {
|
|
15291
|
-
title: renderOpts.title,
|
|
15292
|
-
description: renderOpts.description,
|
|
15293
|
-
code: renderOpts.code,
|
|
15294
|
-
pageTitle: renderOpts.pageTitle,
|
|
15295
|
-
status: (_c = renderOpts.status) !== null && _c !== void 0 ? _c : 500,
|
|
15296
|
-
},
|
|
15297
|
-
});
|
|
15298
|
-
};
|
|
15299
|
-
/**
|
|
15300
|
-
* Render an info page
|
|
15301
|
-
* @author Gabe Abrams
|
|
15302
|
-
* @param renderOpts object containing all arguments
|
|
15303
|
-
* @param renderOpts.title title of the info box
|
|
15304
|
-
* @param renderOpts.body a human-readable text body for the info alert
|
|
15305
|
-
*/
|
|
15306
|
-
const renderInfoPage = (renderOpts) => {
|
|
15307
|
-
const html = genInfoPage(renderOpts);
|
|
15308
|
-
send(html, 200);
|
|
15309
|
-
};
|
|
15310
|
-
/**
|
|
15311
|
-
* Render custom HTML
|
|
15312
|
-
* @author Gabe Abrams
|
|
15313
|
-
* @param htmlOpts object containing all arguments
|
|
15314
|
-
* @param htmlOpts.html the HTML to send to the client
|
|
15315
|
-
* @param [ejsOpts.status=200] the http status code to send
|
|
15316
|
-
*/
|
|
15317
|
-
const renderCustomHTML = (htmlOpts) => {
|
|
15318
|
-
var _a;
|
|
15319
|
-
send(htmlOpts.html, (_a = htmlOpts.status) !== null && _a !== void 0 ? _a : 200);
|
|
15320
|
-
};
|
|
15321
|
-
// Call the handler
|
|
15322
|
-
try {
|
|
15323
|
-
const results = yield opts.handler({
|
|
15324
|
-
params: output,
|
|
15325
|
-
req,
|
|
15326
|
-
send,
|
|
15327
|
-
next: () => {
|
|
15328
|
-
responseSent = true;
|
|
15329
|
-
next();
|
|
15330
|
-
},
|
|
15331
|
-
redirect,
|
|
15332
|
-
renderErrorPage,
|
|
15333
|
-
renderInfoPage,
|
|
15334
|
-
renderCustomHTML,
|
|
15335
|
-
logServerEvent,
|
|
15336
|
-
});
|
|
15337
|
-
// Send results to client (only if next wasn't called)
|
|
15338
|
-
if (!responseSent) {
|
|
15339
|
-
return handleSuccess(res, results !== null && results !== void 0 ? results : undefined);
|
|
15340
|
-
}
|
|
15341
|
-
}
|
|
15342
|
-
catch (err) {
|
|
15343
|
-
// Prefix error message if needed
|
|
15344
|
-
if (opts.unhandledErrorMessagePrefix
|
|
15345
|
-
&& err instanceof Error
|
|
15346
|
-
&& err.message
|
|
15347
|
-
&& err.name !== 'ErrorWithCode') {
|
|
15348
|
-
err.message = `${opts.unhandledErrorMessagePrefix.trim()} ${err.message.trim()}`;
|
|
15349
|
-
}
|
|
15350
|
-
// Send error to client (only if next wasn't called)
|
|
15351
|
-
if (!responseSent) {
|
|
15352
|
-
handleError(res, err);
|
|
15353
|
-
// Log server-side error
|
|
15354
|
-
logServerEvent({
|
|
15355
|
-
context: LogBuiltInMetadata.Context.ServerEndpointError,
|
|
15356
|
-
error: err,
|
|
15357
|
-
});
|
|
15358
|
-
return;
|
|
15359
|
-
}
|
|
15360
|
-
// Log error that was not responded with
|
|
15361
|
-
// eslint-disable-next-line no-console
|
|
15362
|
-
console.log('Error occurred but could not be sent to client because a response was already sent:', err);
|
|
15363
|
-
}
|
|
15364
|
-
});
|
|
15365
|
-
};
|
|
15366
|
-
|
|
15367
|
-
/**
|
|
15368
|
-
* Stub a server endpoint response
|
|
15369
|
-
* @author Gabe Abrams
|
|
15370
|
-
* @param opts object containing all arguments
|
|
15371
|
-
* @param [opts.method=GET] http method to stub
|
|
15372
|
-
* @param opts.path full pathname to stub
|
|
15373
|
-
* @param opts.body body of response if stubbing a successful response
|
|
15374
|
-
* @param opts.errorMessage message of error if stubbing a failed response
|
|
15375
|
-
* @param [opts.errorCode] error code if stubbing a failed response
|
|
15376
|
-
*/
|
|
15377
|
-
const stubServerEndpoint = _setStubResponse;
|
|
15378
|
-
|
|
15379
|
-
/**
|
|
15380
|
-
* Start a dynamic wait, call the function once the operation has completed and
|
|
15381
|
-
* the dynamic wait will continue waiting for the rest of the minimum time
|
|
15382
|
-
* @author Gabe Abrams
|
|
15383
|
-
* @param minWaitMs the minimum number of ms to wait
|
|
15384
|
-
* @returns async function to call to finish the wait
|
|
14028
|
+
* @param minWaitMs the minimum number of ms to wait
|
|
14029
|
+
* @returns async function to call to finish the wait
|
|
15385
14030
|
*/
|
|
15386
14031
|
const startMinWait = (minWaitMs) => {
|
|
15387
14032
|
const startTimestamp = Date.now();
|
|
@@ -15517,27 +14162,11 @@ const parallelLimit = (taskFunctions, limit) => __awaiter(void 0, void 0, void 0
|
|
|
15517
14162
|
});
|
|
15518
14163
|
|
|
15519
14164
|
/**
|
|
15520
|
-
*
|
|
14165
|
+
* Route for checking the status of the current user's
|
|
14166
|
+
* access to log review
|
|
15521
14167
|
* @author Gabe Abrams
|
|
15522
|
-
* @param Collection the Collection class from dce-mango
|
|
15523
|
-
* @returns initialized logCollection
|
|
15524
14168
|
*/
|
|
15525
|
-
const
|
|
15526
|
-
return new Collection('Log', {
|
|
15527
|
-
uniqueIndexKey: 'id',
|
|
15528
|
-
indexKeys: [
|
|
15529
|
-
'courseId',
|
|
15530
|
-
'context',
|
|
15531
|
-
'subcontext',
|
|
15532
|
-
'tags',
|
|
15533
|
-
'year',
|
|
15534
|
-
'month',
|
|
15535
|
-
'day',
|
|
15536
|
-
'hour',
|
|
15537
|
-
'type',
|
|
15538
|
-
],
|
|
15539
|
-
});
|
|
15540
|
-
};
|
|
14169
|
+
const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
|
|
15541
14170
|
|
|
15542
14171
|
/*------------------------------------------------------------------------*/
|
|
15543
14172
|
/* -------------------------------- Cache ------------------------------- */
|
|
@@ -15679,66 +14308,6 @@ const getLocalTimeInfo = (dateOrTimestamp) => {
|
|
|
15679
14308
|
};
|
|
15680
14309
|
};
|
|
15681
14310
|
|
|
15682
|
-
/**
|
|
15683
|
-
* Add all routes for the DBEditor
|
|
15684
|
-
* @author Yuen Ler Chow
|
|
15685
|
-
* @param opts object containing all arguments
|
|
15686
|
-
* @param opts.app express app to add routes too
|
|
15687
|
-
* @param opts.collectionName the name of the collection
|
|
15688
|
-
* @param opts.adminsOnly true if the endpoint is for admins only
|
|
15689
|
-
* @param opts.collection dce-mango db collection
|
|
15690
|
-
*/
|
|
15691
|
-
const addDBEditorEndpoints = (opts) => {
|
|
15692
|
-
const { app, collectionName, adminsOnly, collection, } = opts;
|
|
15693
|
-
// Generate the endpoint path
|
|
15694
|
-
const endpointPath = generateEndpointPath(collectionName, adminsOnly);
|
|
15695
|
-
/**
|
|
15696
|
-
* List all items in the collection
|
|
15697
|
-
* @author Yuen Ler Chow
|
|
15698
|
-
* @returns {any[]} the list of items in the collection
|
|
15699
|
-
*/
|
|
15700
|
-
app.get(endpointPath, genRouteHandler({
|
|
15701
|
-
paramTypes: {
|
|
15702
|
-
filterQuery: ParamType$1.JSONOptional,
|
|
15703
|
-
},
|
|
15704
|
-
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15705
|
-
var _a;
|
|
15706
|
-
const filterQuery = (_a = params.filterQuery) !== null && _a !== void 0 ? _a : {};
|
|
15707
|
-
const categories = yield collection.find(filterQuery);
|
|
15708
|
-
return categories;
|
|
15709
|
-
}),
|
|
15710
|
-
}));
|
|
15711
|
-
/**
|
|
15712
|
-
* Create a new item in the collection
|
|
15713
|
-
* @author Yuen Ler Chow
|
|
15714
|
-
* @param {any} item the item to create
|
|
15715
|
-
*/
|
|
15716
|
-
app.post(endpointPath, genRouteHandler({
|
|
15717
|
-
paramTypes: {
|
|
15718
|
-
item: ParamType$1.JSON,
|
|
15719
|
-
},
|
|
15720
|
-
handler: ({ params, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15721
|
-
// Destructure params
|
|
15722
|
-
const { item, } = params;
|
|
15723
|
-
yield collection.insert(item);
|
|
15724
|
-
}),
|
|
15725
|
-
}));
|
|
15726
|
-
/**
|
|
15727
|
-
* Remove an item from the collection by id
|
|
15728
|
-
* @author Yuen Ler Chow
|
|
15729
|
-
*/
|
|
15730
|
-
app.delete(`${endpointPath}/:id`, genRouteHandler({
|
|
15731
|
-
paramTypes: {
|
|
15732
|
-
id: ParamType$1.String,
|
|
15733
|
-
},
|
|
15734
|
-
handler: ({ params, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15735
|
-
// Destructure params
|
|
15736
|
-
const { id, } = params;
|
|
15737
|
-
yield collection.delete({ id });
|
|
15738
|
-
}),
|
|
15739
|
-
}));
|
|
15740
|
-
};
|
|
15741
|
-
|
|
15742
14311
|
/**
|
|
15743
14312
|
* Given an array of strings, create a single comma-separated string that includes
|
|
15744
14313
|
* 'and' as well as an oxford comma.
|
|
@@ -16280,203 +14849,6 @@ const shuffleArray = (arr) => {
|
|
|
16280
14849
|
return newArr;
|
|
16281
14850
|
};
|
|
16282
14851
|
|
|
16283
|
-
/**
|
|
16284
|
-
* Sends and retries an http request
|
|
16285
|
-
* @author Gabriel Abrams
|
|
16286
|
-
* @param opts object containing all arguments
|
|
16287
|
-
* @param opts.path path to send request to
|
|
16288
|
-
* @param [opts.host] host to send request to
|
|
16289
|
-
* @param [opts.method=GET] http method to use
|
|
16290
|
-
* @param [opts.params] body/data to include in the request
|
|
16291
|
-
* @param [opts.headers] headers to include in the request
|
|
16292
|
-
* @param [opts.sendCrossDomainCredentials=true if in development mode] if true,
|
|
16293
|
-
* send cross-domain credentials even if not in dev mode
|
|
16294
|
-
* @param [opts.responseType=JSON] expected response type
|
|
16295
|
-
* @returns { body, status, headers } on success
|
|
16296
|
-
*/
|
|
16297
|
-
const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16298
|
-
var _a;
|
|
16299
|
-
// Process method
|
|
16300
|
-
const method = (opts.method || 'GET');
|
|
16301
|
-
// Encode objects within params
|
|
16302
|
-
let params;
|
|
16303
|
-
if (opts.params) {
|
|
16304
|
-
params = {};
|
|
16305
|
-
Object.entries(opts.params).forEach(([key, val]) => {
|
|
16306
|
-
if (typeof val === 'object' && !Array.isArray(val)) {
|
|
16307
|
-
params[key] = JSON.stringify(val);
|
|
16308
|
-
}
|
|
16309
|
-
else {
|
|
16310
|
-
params[key] = val;
|
|
16311
|
-
}
|
|
16312
|
-
});
|
|
16313
|
-
}
|
|
16314
|
-
// Stringify parameters
|
|
16315
|
-
const stringifiedParams = qs.stringify(params || {}, {
|
|
16316
|
-
encodeValuesOnly: true,
|
|
16317
|
-
arrayFormat: 'brackets',
|
|
16318
|
-
});
|
|
16319
|
-
// Create url (include query if GET)
|
|
16320
|
-
const query = (method === 'GET' ? `?${stringifiedParams}` : '');
|
|
16321
|
-
let url;
|
|
16322
|
-
if (!opts.host) {
|
|
16323
|
-
// No host included at all. Just send to a path
|
|
16324
|
-
url = `${opts.path}${query}`;
|
|
16325
|
-
}
|
|
16326
|
-
else {
|
|
16327
|
-
url = `https://${opts.host}${opts.path}${query}`;
|
|
16328
|
-
}
|
|
16329
|
-
// Update headers
|
|
16330
|
-
const headers = opts.headers || {};
|
|
16331
|
-
let data = null;
|
|
16332
|
-
if (!headers['Content-Type']) {
|
|
16333
|
-
// Form encoded
|
|
16334
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
16335
|
-
// Add data if applicable
|
|
16336
|
-
data = (method !== 'GET' ? stringifiedParams : null);
|
|
16337
|
-
}
|
|
16338
|
-
else {
|
|
16339
|
-
// JSON encode
|
|
16340
|
-
data = params;
|
|
16341
|
-
}
|
|
16342
|
-
// Encode data
|
|
16343
|
-
let encodedData;
|
|
16344
|
-
if (data) {
|
|
16345
|
-
if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
|
|
16346
|
-
encodedData = new URLSearchParams(params);
|
|
16347
|
-
}
|
|
16348
|
-
else {
|
|
16349
|
-
encodedData = JSON.stringify(data);
|
|
16350
|
-
}
|
|
16351
|
-
}
|
|
16352
|
-
// Send request
|
|
16353
|
-
try {
|
|
16354
|
-
const response = yield fetch(url, {
|
|
16355
|
-
method,
|
|
16356
|
-
mode: 'cors',
|
|
16357
|
-
headers: headers !== null && headers !== void 0 ? headers : {},
|
|
16358
|
-
body: ((method !== 'GET' && encodedData)
|
|
16359
|
-
? encodedData
|
|
16360
|
-
: undefined),
|
|
16361
|
-
redirect: 'follow',
|
|
16362
|
-
});
|
|
16363
|
-
// Get headers map
|
|
16364
|
-
const responseHeaders = {};
|
|
16365
|
-
response.headers.forEach((value, key) => {
|
|
16366
|
-
responseHeaders[key] = value;
|
|
16367
|
-
});
|
|
16368
|
-
// Process response based on responseType
|
|
16369
|
-
try {
|
|
16370
|
-
// Parse response
|
|
16371
|
-
let responseBody;
|
|
16372
|
-
if (opts.responseType
|
|
16373
|
-
&& opts.responseType === 'Text') {
|
|
16374
|
-
// Response type is text
|
|
16375
|
-
responseBody = yield response.text();
|
|
16376
|
-
}
|
|
16377
|
-
else {
|
|
16378
|
-
// Response type is JSON
|
|
16379
|
-
responseBody = yield response.json();
|
|
16380
|
-
}
|
|
16381
|
-
// Return response
|
|
16382
|
-
return {
|
|
16383
|
-
body: responseBody,
|
|
16384
|
-
status: response.status,
|
|
16385
|
-
headers: responseHeaders,
|
|
16386
|
-
};
|
|
16387
|
-
}
|
|
16388
|
-
catch (err) {
|
|
16389
|
-
throw new ErrorWithCode(`Failed to parse response as ${opts.responseType}: ${err === null || err === void 0 ? void 0 : err.message}`, ReactKitErrorCode$1.ResponseParseError);
|
|
16390
|
-
}
|
|
16391
|
-
}
|
|
16392
|
-
catch (err) {
|
|
16393
|
-
// Self-signed certificate error:
|
|
16394
|
-
if ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes('self signed certificate')) {
|
|
16395
|
-
throw new ErrorWithCode('We refused to send a request because the receiver has self-signed certificates.', ReactKitErrorCode$1.SelfSigned);
|
|
16396
|
-
}
|
|
16397
|
-
// No tries left
|
|
16398
|
-
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);
|
|
16399
|
-
}
|
|
16400
|
-
});
|
|
16401
|
-
|
|
16402
|
-
/**
|
|
16403
|
-
* Send a server-to-server request from this sever to another server that uses
|
|
16404
|
-
* dce-reactkit [for server only]
|
|
16405
|
-
* @author Gabe Abrams
|
|
16406
|
-
* @param opts object containing all arguments
|
|
16407
|
-
* @param opts.host - the host of the other server
|
|
16408
|
-
* @param opts.path - the path of the other server's endpoint
|
|
16409
|
-
* @param [opts.method=GET] - the method of the endpoint
|
|
16410
|
-
* @param [opts.params] - query/body parameters to include
|
|
16411
|
-
* @param [opts.headers] - headers to include
|
|
16412
|
-
* @returns response from server
|
|
16413
|
-
*/
|
|
16414
|
-
const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16415
|
-
var _a;
|
|
16416
|
-
// Remove properties with undefined values
|
|
16417
|
-
let params;
|
|
16418
|
-
if (opts.params) {
|
|
16419
|
-
params = Object.fromEntries(Object
|
|
16420
|
-
.entries(opts.params)
|
|
16421
|
-
.filter(([, value]) => {
|
|
16422
|
-
return value !== undefined;
|
|
16423
|
-
}));
|
|
16424
|
-
}
|
|
16425
|
-
// Automatically JSONify arrays and objects
|
|
16426
|
-
if (params) {
|
|
16427
|
-
params = Object.fromEntries(Object
|
|
16428
|
-
.entries(params)
|
|
16429
|
-
.map(([key, value]) => {
|
|
16430
|
-
if (Array.isArray(value) || typeof value === 'object') {
|
|
16431
|
-
return [key, JSON.stringify(value)];
|
|
16432
|
-
}
|
|
16433
|
-
return [key, value];
|
|
16434
|
-
}));
|
|
16435
|
-
}
|
|
16436
|
-
// Send the request
|
|
16437
|
-
const response = yield sendServerToServerRequest({
|
|
16438
|
-
host: opts.host,
|
|
16439
|
-
path: opts.path,
|
|
16440
|
-
method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
|
|
16441
|
-
params,
|
|
16442
|
-
});
|
|
16443
|
-
// Check for failure
|
|
16444
|
-
if (!response || !response.body) {
|
|
16445
|
-
throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
|
|
16446
|
-
}
|
|
16447
|
-
if (!response.body.success) {
|
|
16448
|
-
// Other errors
|
|
16449
|
-
throw new ErrorWithCode((response.body.message
|
|
16450
|
-
|| 'An unknown error occurred. Please contact an admin.'), (response.body.code
|
|
16451
|
-
|| ReactKitErrorCode$1.NoCode));
|
|
16452
|
-
}
|
|
16453
|
-
// Success! Extract the body
|
|
16454
|
-
const { body } = response.body;
|
|
16455
|
-
// Return
|
|
16456
|
-
return body;
|
|
16457
|
-
});
|
|
16458
|
-
|
|
16459
|
-
const punctuationRegex = /[!@#$%^&*(),.?\/;:'"\-\[\]]/g;
|
|
16460
|
-
/**
|
|
16461
|
-
* Get number of words in string
|
|
16462
|
-
* @author Gardenia Liu
|
|
16463
|
-
* @author Allison Zhang
|
|
16464
|
-
* @author Gabe Abrams
|
|
16465
|
-
* @param text the string to check
|
|
16466
|
-
* @returns number of words in the string
|
|
16467
|
-
*/
|
|
16468
|
-
const getWordCount = (text) => {
|
|
16469
|
-
const trimmedTextWithoutPunctuation = (text
|
|
16470
|
-
// Remove leading and trailing whitespace
|
|
16471
|
-
.trim()
|
|
16472
|
-
// Remove punctuation
|
|
16473
|
-
.replace(punctuationRegex, ''));
|
|
16474
|
-
if (trimmedTextWithoutPunctuation.length === 0) {
|
|
16475
|
-
return 0;
|
|
16476
|
-
}
|
|
16477
|
-
return trimmedTextWithoutPunctuation.split(/\s+/g).length;
|
|
16478
|
-
};
|
|
16479
|
-
|
|
16480
14852
|
/**
|
|
16481
14853
|
* Days of the week
|
|
16482
14854
|
* @author Gabe Abrams
|
|
@@ -16493,5 +14865,5 @@ var DayOfWeek;
|
|
|
16493
14865
|
})(DayOfWeek || (DayOfWeek = {}));
|
|
16494
14866
|
var DayOfWeek$1 = DayOfWeek;
|
|
16495
14867
|
|
|
16496
|
-
export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntryFieldType$1 as DBEntryFieldType, DBEntryManagerPanel, DayOfWeek$1 as DayOfWeek, Drawer, Dropdown, DropdownItemType$1 as DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, MultiSwitch, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, TabBox, ToggleSwitch, Tooltip, Variant$1 as Variant, abbreviate,
|
|
14868
|
+
export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntryFieldType$1 as DBEntryFieldType, DBEntryManagerPanel, DayOfWeek$1 as DayOfWeek, Drawer, Dropdown, DropdownItemType$1 as DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogLevel$1 as LogLevel, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, MultiSwitch, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, TabBox, ToggleSwitch, Tooltip, Variant$1 as Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
|
|
16497
14869
|
//# sourceMappingURL=index.js.map
|