dce-reactkit 3.12.1-beta-cross-server.2 → 3.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.vscode/settings.json +3 -1
  2. package/README.md +0 -20
  3. package/dist/cjs/index.js +356 -406
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
  6. package/dist/cjs/types/components/Modal/ModalProps.d.ts +2 -0
  7. package/dist/cjs/types/components/TabBox.d.ts +1 -0
  8. package/dist/cjs/types/helpers/genRouteHandler.d.ts +10 -10
  9. package/dist/cjs/types/helpers/getWordCount.d.ts +10 -0
  10. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +13 -15
  11. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +6 -0
  12. package/dist/cjs/types/index.d.ts +2 -3
  13. package/dist/cjs/types/server/initServer.d.ts +0 -11
  14. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +1 -13
  15. package/dist/esm/index.js +357 -406
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
  18. package/dist/esm/types/components/Modal/ModalProps.d.ts +2 -0
  19. package/dist/esm/types/components/TabBox.d.ts +1 -0
  20. package/dist/esm/types/helpers/genRouteHandler.d.ts +10 -10
  21. package/dist/esm/types/helpers/getWordCount.d.ts +10 -0
  22. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +13 -15
  23. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +6 -0
  24. package/dist/esm/types/index.d.ts +2 -3
  25. package/dist/esm/types/server/initServer.d.ts +0 -11
  26. package/dist/esm/types/types/ReactKitErrorCode.d.ts +1 -13
  27. package/dist/index.d.ts +39 -62
  28. package/package.json +2 -5
  29. package/src/components/IntelliTable.tsx +1 -1
  30. package/src/components/ItemPicker/NestableItemList.tsx +1 -0
  31. package/src/components/ItemPicker/index.tsx +96 -0
  32. package/src/components/Modal/ModalProps.ts +4 -0
  33. package/src/components/Modal/index.tsx +59 -20
  34. package/src/components/TabBox.tsx +27 -9
  35. package/src/helpers/genRouteHandler.ts +95 -55
  36. package/src/helpers/getWordCount.ts +26 -0
  37. package/src/helpers/visitEndpointOnAnotherServer/index.ts +41 -42
  38. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +5 -3
  39. package/src/index.ts +2 -4
  40. package/src/server/initServer.ts +0 -18
  41. package/src/types/ReactKitErrorCode.tsx +1 -13
  42. package/dist/cjs/types/helpers/dataSigner.d.ts +0 -38
  43. package/dist/cjs/types/server/initCrossServerCredentialCollection.d.ts +0 -8
  44. package/dist/cjs/types/types/CrossServerCredential.d.ts +0 -11
  45. package/dist/esm/types/helpers/dataSigner.d.ts +0 -38
  46. package/dist/esm/types/server/initCrossServerCredentialCollection.d.ts +0 -8
  47. package/dist/esm/types/types/CrossServerCredential.d.ts +0 -11
  48. package/genEncodedSecret.ts +0 -28
  49. package/src/helpers/dataSigner.ts +0 -232
  50. package/src/server/initCrossServerCredentialCollection.ts +0 -16
  51. package/src/types/CrossServerCredential.ts +0 -16
package/dist/esm/index.js CHANGED
@@ -1,11 +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, 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';
3
+ import { faExclamationTriangle, faCircle, faHourglassEnd, 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 { createHash } from 'crypto';
8
- import jwt from 'jwt-simple';
9
7
  import qs from 'qs';
10
8
 
11
9
  /******************************************************************************
@@ -33,7 +31,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
33
31
  });
34
32
  }
35
33
 
36
- // Highest error code = DRK30
34
+ // Highest error code = DRK18
37
35
  /**
38
36
  * List of error codes built into the react kit
39
37
  * @author Gabe Abrams
@@ -59,18 +57,6 @@ var ReactKitErrorCode;
59
57
  ReactKitErrorCode["NotConnected"] = "DRK14";
60
58
  ReactKitErrorCode["SelfSigned"] = "DRK15";
61
59
  ReactKitErrorCode["ResponseParseError"] = "DRK16";
62
- ReactKitErrorCode["PackUnparseable"] = "DRK28";
63
- ReactKitErrorCode["PackInvalidMethod"] = "DRK19";
64
- ReactKitErrorCode["PackInvalidPath"] = "DRK20";
65
- ReactKitErrorCode["PackInvalidCollection"] = "DRK21";
66
- ReactKitErrorCode["PackInvalidCredential"] = "DRK23";
67
- ReactKitErrorCode["PackInvalidScope"] = "DRK22";
68
- ReactKitErrorCode["PackInvalidTimestamp"] = "DRK24";
69
- ReactKitErrorCode["PackInvalidSignature"] = "DRK25";
70
- ReactKitErrorCode["PackInvalidBody"] = "DRK26";
71
- ReactKitErrorCode["CrossServerNoCredentialsToSignWith"] = "DRK27";
72
- ReactKitErrorCode["CrossServerNoPack"] = "DRK29";
73
- ReactKitErrorCode["CrossServerNoCredentialEncodingSalt"] = "DRK30";
74
60
  })(ReactKitErrorCode || (ReactKitErrorCode = {}));
75
61
  var ReactKitErrorCode$1 = ReactKitErrorCode;
76
62
 
@@ -202,6 +188,98 @@ const LogBuiltInMetadata = {
202
188
  },
203
189
  };
204
190
 
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
+
205
283
  /**
206
284
  * Wait for a certain number of ms
207
285
  * @author Gabe Abrams
@@ -334,6 +412,7 @@ const BASE_Z_INDEX = 1000000000;
334
412
  const BASE_Z_INDEX_ON_TOP = 2000000000;
335
413
  // Constants
336
414
  const MS_TO_ANIMATE = 200; // Animation duration
415
+ const MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR = 1000;
337
416
  // Modal type to list of buttons
338
417
  const modalTypeToModalButtonTypes = {
339
418
  [ModalType$1.Okay]: [
@@ -447,7 +526,7 @@ const getNextUniqueId = () => {
447
526
  /*------------------------------------------------------------------------*/
448
527
  /* -------------------------------- Style ------------------------------- */
449
528
  /*------------------------------------------------------------------------*/
450
- const style$a = `
529
+ const style$9 = `
451
530
  .Modal-backdrop {
452
531
  position: fixed;
453
532
  top: 0;
@@ -519,13 +598,14 @@ const Modal = (props) => {
519
598
  /*------------------------------------------------------------------------*/
520
599
  var _a;
521
600
  /* -------------- Props ------------- */
522
- const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, largeTitle, children, onClose, dontAllowBackdropExit, dontShowXButton, onTopOfOtherModals, } = props;
601
+ const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, largeTitle, children, onClose, dontAllowBackdropExit, dontShowXButton, onTopOfOtherModals, isLoading, isLoadingCancelable, } = props;
523
602
  // Determine if no header either
524
603
  const noHeader = (!title && type === ModalType$1.NoButtons);
525
604
  /* -------------- State ------------- */
526
605
  // True if animation is in use
527
606
  const [animatingIn, setAnimatingIn] = useState(true);
528
607
  const [animatingPop, setAnimatingPop] = useState(false);
608
+ const [showModal, setShowModal] = useState(false);
529
609
  /* -------------- Refs -------------- */
530
610
  // Keep track of whether modal is still mounted
531
611
  const mounted = useRef(false);
@@ -552,6 +632,16 @@ const Modal = (props) => {
552
632
  if (mounted.current) {
553
633
  setAnimatingIn(false);
554
634
  }
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
+ }
555
645
  }))();
556
646
  return () => {
557
647
  mounted.current = false;
@@ -618,6 +708,15 @@ const Modal = (props) => {
618
708
  else if (animatingPop) {
619
709
  animationClass = 'Modal-animating-pop';
620
710
  }
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));
621
720
  // Render the modal
622
721
  const contentToRender = (React__default.createElement("div", { className: "modal show", tabIndex: -1, style: {
623
722
  zIndex: baseZIndex,
@@ -626,7 +725,7 @@ const Modal = (props) => {
626
725
  left: 0,
627
726
  right: 0,
628
727
  } },
629
- React__default.createElement("style", null, style$a),
728
+ React__default.createElement("style", null, style$9),
630
729
  React__default.createElement("div", { className: `Modal-backdrop ${backdropAnimationClass}`, style: {
631
730
  zIndex: baseZIndex + 1,
632
731
  }, onClick: () => __awaiter(void 0, void 0, void 0, function* () {
@@ -644,7 +743,7 @@ const Modal = (props) => {
644
743
  // Handle close
645
744
  handleClose(ModalButtonType$1.Cancel);
646
745
  }) }),
647
- React__default.createElement("div", { className: `modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`, style: {
746
+ showModal && (React__default.createElement("div", { className: `modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`, style: {
648
747
  zIndex: baseZIndex + 2,
649
748
  // Override sizes for even larger for XL
650
749
  width: (size === ModalSize$1.ExtraLarge
@@ -676,7 +775,7 @@ const Modal = (props) => {
676
775
  ? '1.6rem'
677
776
  : undefined),
678
777
  } }, title),
679
- (onClose && !dontShowXButton) && (React__default.createElement("button", { type: "button", className: "Modal-x-button btn-close", "aria-label": "Close", style: {
778
+ showCloseButton && (React__default.createElement("button", { type: "button", className: "Modal-x-button btn-close", "aria-label": "Close", style: {
680
779
  backgroundColor: (isDarkModeOn()
681
780
  ? 'white'
682
781
  : undefined),
@@ -684,14 +783,18 @@ const Modal = (props) => {
684
783
  // Handle close
685
784
  handleClose(ModalButtonType$1.Cancel);
686
785
  } })))),
687
- children && (React__default.createElement("div", { className: "modal-body", style: {
786
+ React__default.createElement("div", { className: "modal-body", style: {
688
787
  color: (isDarkModeOn()
689
788
  ? 'white'
690
789
  : undefined),
691
790
  backgroundColor: (isDarkModeOn()
692
791
  ? '#444'
693
792
  : undefined),
694
- } }, children)),
793
+ } }, isLoading
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),
695
798
  footer && (React__default.createElement("div", { className: "modal-footer pt-1 pb-1", style: {
696
799
  color: (isDarkModeOn()
697
800
  ? 'white'
@@ -702,7 +805,7 @@ const Modal = (props) => {
702
805
  borderTop: (isDarkModeOn()
703
806
  ? '0.1rem solid gray'
704
807
  : undefined),
705
- } }, footer))))));
808
+ } }, footer)))))));
706
809
  // Determine which portal to use
707
810
  const portalNumber = id.current % NUM_MODAL_PORTALS;
708
811
  // Render in a portal
@@ -1269,7 +1372,7 @@ const showSessionExpiredMessage = () => __awaiter(void 0, void 0, void 0, functi
1269
1372
  /*------------------------------------------------------------------------*/
1270
1373
  /* -------------------------------- Style ------------------------------- */
1271
1374
  /*------------------------------------------------------------------------*/
1272
- const style$9 = `
1375
+ const style$8 = `
1273
1376
  .AppWrapper-leave-to-url-notice {
1274
1377
  opacity: 0;
1275
1378
 
@@ -1483,105 +1586,13 @@ const AppWrapper = (props) => {
1483
1586
  /* --------------- Main UI -------------- */
1484
1587
  /*----------------------------------------*/
1485
1588
  return (React__default.createElement(React__default.Fragment, null,
1486
- React__default.createElement("style", null, style$9),
1589
+ React__default.createElement("style", null, style$8),
1487
1590
  React__default.createElement("style", null, tooltipStyle),
1488
1591
  modal,
1489
1592
  customModalPortals,
1490
1593
  body));
1491
1594
  };
1492
1595
 
1493
- /**
1494
- * Loading spinner/indicator
1495
- * @author Gabe Abrams
1496
- */
1497
- /*------------------------------------------------------------------------*/
1498
- /* -------------------------------- Style ------------------------------- */
1499
- /*------------------------------------------------------------------------*/
1500
- const style$8 = `
1501
- /* Container fades in */
1502
- .LoadingSpinner-container {
1503
- animation-name: LoadingSpinner-container-fade-in;
1504
- animation-duration: 0.3s;
1505
- animation-delay: 0.4s;
1506
- animation-fill-mode: both;
1507
- animation-timing-function: ease-out;
1508
- }
1509
- @keyframes LoadingSpinner-container-fade-in {
1510
- 0% {
1511
- opacity: 0;
1512
- }
1513
- 100% {
1514
- opacity: 1;
1515
- }
1516
- }
1517
-
1518
- /* Blips */
1519
- .LoadingSpinner-blip-1,
1520
- .LoadingSpinner-blip-2,
1521
- .LoadingSpinner-blip-3,
1522
- .LoadingSpinner-blip-4 {
1523
- font-size: 1.8rem;
1524
- opacity: 0.6;
1525
- margin-top: 1rem;
1526
- margin-bottom: 1rem;
1527
- }
1528
-
1529
- /* First Blip */
1530
- .LoadingSpinner-blip-1 {
1531
- animation: LoadingSpinner-pop-animation 2s infinite;
1532
- }
1533
-
1534
- /* Second Blip */
1535
- .LoadingSpinner-blip-2 {
1536
- animation: LoadingSpinner-pop-animation 2s infinite;
1537
- -webkit-animation-delay: 0.1s;
1538
- animation-delay: 0.1s;
1539
- }
1540
-
1541
- /* Third Blip */
1542
- .LoadingSpinner-blip-3 {
1543
- animation: LoadingSpinner-pop-animation 2s infinite;
1544
- animation-delay: 0.2s;
1545
- }
1546
-
1547
- /* Fourth Blip */
1548
- .LoadingSpinner-blip-4 {
1549
- animation: LoadingSpinner-pop-animation 2s infinite;
1550
- animation-delay: 0.3s;
1551
- }
1552
-
1553
- /* Pop Animation for Each Blip */
1554
- @keyframes LoadingSpinner-pop-animation {
1555
- 0% {
1556
- transform: scale(1.0);
1557
- }
1558
- 10% {
1559
- transform: scale(1.5);
1560
- }
1561
- 30% {
1562
- transform: scale(1.0);
1563
- }
1564
- 100% {
1565
- transform: scale(1.0);
1566
- }
1567
- }
1568
- `;
1569
- /*------------------------------------------------------------------------*/
1570
- /* ------------------------------ Component ----------------------------- */
1571
- /*------------------------------------------------------------------------*/
1572
- const LoadingSpinner = () => {
1573
- /*------------------------------------------------------------------------*/
1574
- /* ------------------------------- Render ------------------------------- */
1575
- /*------------------------------------------------------------------------*/
1576
- // Add all four blips to a container
1577
- return (React__default.createElement("div", { className: "text-center LoadingSpinner LoadingSpinner-container" },
1578
- React__default.createElement("style", null, style$8),
1579
- React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-1 me-1" }),
1580
- React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-2 me-1" }),
1581
- React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-3 me-1" }),
1582
- React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "LoadingSpinner-blip-4" })));
1583
- };
1584
-
1585
1596
  /**
1586
1597
  * A box with a tab on the top that holds buttons and other content
1587
1598
  * @author Gabe Abrams
@@ -1613,7 +1624,9 @@ const style$7 = `
1613
1624
 
1614
1625
  /* Container for Title */
1615
1626
  .TabBox-title-container {
1616
- /* Place on Left */
1627
+ display: flex;
1628
+ justify-content: space-between;
1629
+ align-items: center;
1617
1630
  position: relative;
1618
1631
  left: 0;
1619
1632
  text-align: left;
@@ -1646,6 +1659,19 @@ const style$7 = `
1646
1659
  background: #fdfdfd;
1647
1660
  }
1648
1661
 
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
+
1649
1675
  /* Make the TabBox's Children Appear Above Title if Overlap Occurs */
1650
1676
  .TabBox-children {
1651
1677
  position: relative;
@@ -1659,19 +1685,16 @@ const TabBox = (props) => {
1659
1685
  /*------------------------------------------------------------------------*/
1660
1686
  /* -------------------------------- Setup ------------------------------- */
1661
1687
  /*------------------------------------------------------------------------*/
1662
- /* -------------- Props ------------- */
1663
- const { title, children, noBottomPadding, noBottomMargin, } = props;
1688
+ const { title, children, topRightChildren, noBottomPadding, noBottomMargin, } = props;
1664
1689
  /*------------------------------------------------------------------------*/
1665
1690
  /* ------------------------------- Render ------------------------------- */
1666
1691
  /*------------------------------------------------------------------------*/
1667
- /*----------------------------------------*/
1668
- /* --------------- Main UI -------------- */
1669
- /*----------------------------------------*/
1670
- // Full UI
1671
1692
  return (React__default.createElement("div", { className: `TabBox-container ${noBottomMargin ? '' : 'mb-2'}` },
1672
1693
  React__default.createElement("style", null, style$7),
1673
1694
  React__default.createElement("div", { className: "TabBox-title-container" },
1674
- React__default.createElement("div", { className: "TabBox-title" }, title)),
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)))),
1675
1698
  React__default.createElement("div", { className: `TabBox-box ps-2 pt-2 pe-2 ${noBottomPadding ? '' : 'pb-2'}` },
1676
1699
  React__default.createElement("div", { className: "TabBox-children" }, children))));
1677
1700
  };
@@ -2658,7 +2681,7 @@ const NestableItemList = (props) => {
2658
2681
  React__default.createElement(FontAwesomeIcon, { icon: childExpanded[item.id] ? faChevronDown : faChevronRight })))),
2659
2682
  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) => {
2660
2683
  onChanged(changeChecked(item.id, checked, items));
2661
- }, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
2684
+ }, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
2662
2685
  (item.isGroup && childExpanded[item.id]) && (React__default.createElement("div", { className: "NestableItemList-children-container", style: {
2663
2686
  paddingLeft: '2.2rem',
2664
2687
  } },
@@ -2681,17 +2704,62 @@ const ItemPicker = (props) => {
2681
2704
  /*------------------------------------------------------------------------*/
2682
2705
  /* -------------- Props ------------- */
2683
2706
  // Destructure all props
2684
- const { title, items, onChanged, noBottomMargin, } = props;
2707
+ const { title, items, onChanged, noBottomMargin, hideSelectAllOrNoneButtons, } = props;
2685
2708
  /*------------------------------------------------------------------------*/
2686
2709
  /* ------------------------- Component Functions ------------------------ */
2687
2710
  /*------------------------------------------------------------------------*/
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
+ };
2688
2746
  /*------------------------------------------------------------------------*/
2689
2747
  /* ------------------------------- Render ------------------------------- */
2690
2748
  /*------------------------------------------------------------------------*/
2691
2749
  /*----------------------------------------*/
2692
2750
  /* --------------- Main UI -------------- */
2693
2751
  /*----------------------------------------*/
2694
- return (React__default.createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
2752
+ // Select all/none
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 },
2695
2763
  React__default.createElement("div", { style: { overflowX: 'auto' } },
2696
2764
  React__default.createElement(NestableItemList, { items: items, onChanged: onChanged }))));
2697
2765
  };
@@ -3063,7 +3131,7 @@ const IntelliTable = (props) => {
3063
3131
  param: column.param,
3064
3132
  visible: checked,
3065
3133
  });
3066
- }, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
3134
+ }, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
3067
3135
  }),
3068
3136
  React__default.createElement("div", { className: "mt-3" }, "Or you can:"),
3069
3137
  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: () => {
@@ -14035,8 +14103,6 @@ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
14035
14103
  let _cacclGetLaunchInfo;
14036
14104
  // Stored copy of dce-mango log collection
14037
14105
  let _logCollection;
14038
- // Stored copy of dce-mango cross-server credential collection
14039
- let _crossServerCredentialCollection;
14040
14106
  /*------------------------------------------------------------------------*/
14041
14107
  /* Helpers */
14042
14108
  /*------------------------------------------------------------------------*/
@@ -14061,15 +14127,6 @@ const cacclGetLaunchInfo = (req) => {
14061
14127
  const internalGetLogCollection = () => {
14062
14128
  return _logCollection !== null && _logCollection !== void 0 ? _logCollection : null;
14063
14129
  };
14064
- /**
14065
- * Get cross-server credential collection
14066
- * @author Gabe Abrams
14067
- * @return cross-server credential collection if one was included during launch or null
14068
- * if we don't have a cross-server credential collection (yet)
14069
- */
14070
- const internalGetCrossServerCredentialCollection = () => {
14071
- return _crossServerCredentialCollection !== null && _crossServerCredentialCollection !== void 0 ? _crossServerCredentialCollection : null;
14072
- };
14073
14130
  /*------------------------------------------------------------------------*/
14074
14131
  /* Main */
14075
14132
  /*------------------------------------------------------------------------*/
@@ -14089,14 +14146,10 @@ const internalGetCrossServerCredentialCollection = () => {
14089
14146
  * userIds are allowed to review logs. If a dce-mango collection, only
14090
14147
  * Canvas admins with entries in that collection ({ userId, ...}) are allowed
14091
14148
  * to review logs
14092
- * @param [opts.crossServerCredentialCollection] mongo collection from dce-mango to use for
14093
- * storing cross-server credentials. If none is included, cross-server credentials
14094
- * are not supported
14095
14149
  */
14096
14150
  const initServer = (opts) => {
14097
14151
  _cacclGetLaunchInfo = opts.getLaunchInfo;
14098
14152
  _logCollection = opts.logCollection;
14099
- _crossServerCredentialCollection = opts.crossServerCredentialCollection;
14100
14153
  /*----------------------------------------*/
14101
14154
  /* Logging */
14102
14155
  /*----------------------------------------*/
@@ -14639,154 +14692,6 @@ const parseUserAgent = (userAgent) => {
14639
14692
  };
14640
14693
  };
14641
14694
 
14642
- /*------------------------------------------------------------------------*/
14643
- /* ------------------------------ Encoding ------------------------------ */
14644
- /*------------------------------------------------------------------------*/
14645
- /**
14646
- * Validate a JWT token
14647
- * @author Gabe Abrams
14648
- * @param encodedSecret the encoded secret
14649
- * @return the secret
14650
- */
14651
- const decodeCredentialSecret = (encodedSecret) => {
14652
- // Get the credential encoding salt
14653
- const credentialEncodingSalt = process.env.REACTKIT_CRED_ENCODING_SALT;
14654
- if (!credentialEncodingSalt) {
14655
- throw new ErrorWithCode('Cannot decode a cross-server credential secret because the credential encoding salt was not found in env.', ReactKitErrorCode$1.CrossServerNoCredentialEncodingSalt);
14656
- }
14657
- // Decode the secret
14658
- return jwt.decode(encodedSecret, credentialEncodingSalt);
14659
- };
14660
- /*------------------------------------------------------------------------*/
14661
- /* ------------------------------- Signing ------------------------------ */
14662
- /*------------------------------------------------------------------------*/
14663
- /**
14664
- * Sign using sha 256
14665
- * @author Gabe Abrams
14666
- * @param opts object containing all arguments
14667
- * @param opts.pack the pack to sign
14668
- * @param opts.secret the reactkit secret to sign with
14669
- * @return the signed hash
14670
- */
14671
- const genSignature = (opts) => {
14672
- // Generate signature
14673
- return (createHash('sha256')
14674
- .update(JSON.stringify({ pack: opts.pack, secret: opts.secret }))
14675
- .digest('base64'));
14676
- };
14677
- /**
14678
- * Sign data with a private reactkit key, package it into a signed data pack
14679
- * @author Gabe Abrams
14680
- * @param opts object containing all arguments
14681
- * @param opts.method the method to sign
14682
- * @param opts.path the http request path
14683
- * @param opts.params the data in the body to sign
14684
- * @param opts.key the reactkit key to sign with
14685
- * @param opts.secret the reactkit secret to sign with
14686
- * @return the signed data
14687
- */
14688
- const createSignedPack = (opts) => {
14689
- // Create a timestamp
14690
- const timestamp = Date.now();
14691
- // Create the pack
14692
- const pack = JSON.stringify({
14693
- method: opts.method,
14694
- path: opts.path,
14695
- params: opts.params,
14696
- key: opts.key,
14697
- timestamp,
14698
- });
14699
- // Generate signature
14700
- const signature = genSignature({
14701
- pack,
14702
- secret: opts.secret,
14703
- });
14704
- // Create a signed pack
14705
- const signedPack = encodeURIComponent(JSON.stringify({
14706
- pack,
14707
- signature,
14708
- }));
14709
- // Return the signed pack
14710
- return signedPack;
14711
- };
14712
- /**
14713
- * Parse signed pack. Throws an error if invalid
14714
- * @author Gabe Abrams
14715
- * @param opts object containing all arguments
14716
- * @param opts.method the method of the data validate
14717
- * @param opts.path the http request path to validate
14718
- * @param opts.scope the name of the scope to validate
14719
- * @param opts.signedPack the signed data pack to validate
14720
- * @returns parsed and validated params
14721
- */
14722
- const parseSignedPack = (opts) => __awaiter(void 0, void 0, void 0, function* () {
14723
- // Extract signature
14724
- let pack;
14725
- let signature;
14726
- let method;
14727
- let path;
14728
- let key;
14729
- let timestamp;
14730
- let params;
14731
- try {
14732
- ({
14733
- pack,
14734
- signature,
14735
- } = JSON.parse(decodeURIComponent(opts.signedPack)));
14736
- // Unpack
14737
- ({
14738
- method,
14739
- path,
14740
- params,
14741
- key,
14742
- timestamp,
14743
- } = JSON.parse(pack));
14744
- }
14745
- catch (err) {
14746
- throw new ErrorWithCode('Could not validate a cross-server request because the request could not be parsed.', ReactKitErrorCode$1.PackUnparseable);
14747
- }
14748
- // Make sure the method and path match
14749
- if (method !== opts.method) {
14750
- throw new ErrorWithCode('Could not validate a cross-server request because the method did not match.', ReactKitErrorCode$1.PackInvalidMethod);
14751
- }
14752
- if (path !== opts.path) {
14753
- throw new ErrorWithCode('Could not validate a cross-server request because the path did not match.', ReactKitErrorCode$1.PackInvalidPath);
14754
- }
14755
- // Make sure the timestamp was recent enough
14756
- const elapsedMs = Math.abs(Date.now() - timestamp);
14757
- if (elapsedMs < MINUTE_IN_MS) {
14758
- throw new ErrorWithCode('Could not validate a cross-server request because the request was too old.', ReactKitErrorCode$1.PackInvalidTimestamp);
14759
- }
14760
- // Get the cross-server credential collection
14761
- const crossServerCredentialCollection = internalGetCrossServerCredentialCollection();
14762
- if (!crossServerCredentialCollection) {
14763
- throw new ErrorWithCode('Could not validate a cross-server request because the cross-server credential collection was not ready in time.', ReactKitErrorCode$1.PackInvalidCollection);
14764
- }
14765
- // Get the cross-server credential
14766
- const crossServerCredential = yield crossServerCredentialCollection.find({ key });
14767
- if (!crossServerCredential) {
14768
- throw new ErrorWithCode('Could not validate a cross-server request because the credential was not found.', ReactKitErrorCode$1.PackInvalidCredential);
14769
- }
14770
- // Make sure the scope is included
14771
- const allowedScopes = crossServerCredential.scopes;
14772
- if (!allowedScopes.includes(opts.scope)) {
14773
- throw new ErrorWithCode('Could not validate a cross-server request because the scope was not included.', ReactKitErrorCode$1.PackInvalidScope);
14774
- }
14775
- // Decode the secret
14776
- const secret = decodeCredentialSecret(crossServerCredential.encodedeSecret);
14777
- // Generate signature
14778
- const expectedSignature = genSignature({
14779
- pack,
14780
- secret,
14781
- });
14782
- // Make sure the signature matches
14783
- if (signature !== expectedSignature) {
14784
- throw new ErrorWithCode('Could not validate a cross-server request because the signature did not match.', ReactKitErrorCode$1.PackInvalidSignature);
14785
- }
14786
- // Return body
14787
- return params !== null && params !== void 0 ? params : {};
14788
- });
14789
-
14790
14695
  /**
14791
14696
  * Generate an express API route handler
14792
14697
  * @author Gabe Abrams
@@ -14794,15 +14699,14 @@ const parseSignedPack = (opts) => __awaiter(void 0, void 0, void 0, function* ()
14794
14699
  * @param opts.paramTypes map containing the types for each parameter that is
14795
14700
  * included in the request (map: param name => type)
14796
14701
  * @param opts.handler function that processes the request
14797
- * @param [opts.crossServerScope] the scope associated with this endpoint.
14798
- * If defined, this is a cross-server endpoint, which will never
14799
- * have any launch data, will never check Canvas roles or launch status, and will
14800
- * instead use scopes and reactkit credentials to sign and validate requests.
14801
- * Never start the path with /api/ttm or /api/admin if the endpoint is a cross-server
14802
- * endpoint because those roles will not be validated
14803
- * @param [opts.skipSessionCheck=true if crossServerScope defined] if true, skip
14804
- * the session check (allow users to not be logged in and launched via LTI).
14805
- * If crossServerScope is defined, this is always true
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
14806
14710
  * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
14807
14711
  * is not of type ErrorWithCode is thrown, the client will receive an error
14808
14712
  * where the error message is prefixed with this string. For example,
@@ -14830,51 +14734,86 @@ const parseSignedPack = (opts) => __awaiter(void 0, void 0, void 0, function* ()
14830
14734
  const genRouteHandler = (opts) => {
14831
14735
  // Return a route handler
14832
14736
  return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
14833
- /*----------------------------------------*/
14834
- /* ------------- Preparation ------------ */
14835
- /*----------------------------------------*/
14836
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
14737
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
14837
14738
  // Output params
14838
14739
  const output = {};
14839
- // Determine cross server scopes
14840
- let crossServerScope = null;
14841
- if (opts.crossServerScope) {
14842
- crossServerScope = (_a = opts.crossServerScope) !== null && _a !== void 0 ? _a : null;
14843
- }
14844
- // Determine whether we're skipping the session check
14845
- const skipSessionCheck = !!(opts.skipSessionCheck
14846
- || crossServerScope);
14847
- // Get body from everywhere it can come from
14848
- let requestBody = Object.assign(Object.assign(Object.assign({}, req.body), req.query), req.params);
14849
14740
  /*----------------------------------------*/
14850
- /* ------- Cross-Server Validation ------ */
14741
+ /* ----------- Hostname Check ----------- */
14851
14742
  /*----------------------------------------*/
14852
- if (crossServerScope) {
14853
- // Get the signed pack
14854
- const { signedPack } = requestBody;
14855
- // If no pack, throw error
14856
- if (!signedPack || typeof signedPack !== 'string') {
14857
- throw new ErrorWithCode('Could not process a cross server request because there was no valid signed pack.', ReactKitErrorCode$1.CrossServerNoPack);
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
+ });
14858
14780
  }
14859
- // Validate the request
14860
- const crossServerParams = yield parseSignedPack({
14861
- method: (_b = req.method) !== null && _b !== void 0 ? _b : 'GET',
14862
- path: req.path,
14863
- scope: crossServerScope,
14864
- signedPack,
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();
14865
14796
  });
14866
- // Replace body with params from the pack
14867
- requestBody = crossServerParams !== null && crossServerParams !== void 0 ? crossServerParams : {};
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
+ }
14868
14805
  }
14869
14806
  /*----------------------------------------*/
14870
14807
  /* ------------ Parse Params ------------ */
14871
14808
  /*----------------------------------------*/
14872
14809
  // Process items one by one
14873
- const paramList = Object.entries((_c = opts.paramTypes) !== null && _c !== void 0 ? _c : {});
14810
+ const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
14874
14811
  for (let i = 0; i < paramList.length; i++) {
14875
14812
  const [name, type] = paramList[i];
14876
14813
  // Find the value as a string
14877
- const value = requestBody[name];
14814
+ const value = (req.params[name]
14815
+ || req.query[name]
14816
+ || req.body[name]);
14878
14817
  // Parse
14879
14818
  if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
14880
14819
  // Boolean
@@ -15032,7 +14971,7 @@ const genRouteHandler = (opts) => {
15032
14971
  // Not launched
15033
14972
  (!launched || !launchInfo)
15034
14973
  // Not skipping the session check
15035
- && !skipSessionCheck) {
14974
+ && !opts.skipSessionCheck) {
15036
14975
  return handleError(res, {
15037
14976
  message: 'Your session has expired. Please refresh the page and try again.',
15038
14977
  code: ReactKitErrorCode$1.SessionExpired,
@@ -15052,7 +14991,7 @@ const genRouteHandler = (opts) => {
15052
14991
  && !launchInfo.isLearner
15053
14992
  && !launchInfo.isAdmin))
15054
14993
  // Not skipping the session check
15055
- && !skipSessionCheck) {
14994
+ && !opts.skipSessionCheck) {
15056
14995
  return handleError(res, {
15057
14996
  message: 'Your session was invalid. Please refresh the page and try again.',
15058
14997
  code: ReactKitErrorCode$1.SessionExpired,
@@ -15062,34 +15001,34 @@ const genRouteHandler = (opts) => {
15062
15001
  // Add launch info to output
15063
15002
  output.userId = (launchInfo
15064
15003
  ? launchInfo.userId
15065
- : ((_d = output.userId) !== null && _d !== void 0 ? _d : undefined));
15004
+ : ((_b = output.userId) !== null && _b !== void 0 ? _b : undefined));
15066
15005
  output.userFirstName = (launchInfo
15067
15006
  ? launchInfo.userFirstName
15068
- : ((_e = output.userFirstName) !== null && _e !== void 0 ? _e : undefined));
15007
+ : ((_c = output.userFirstName) !== null && _c !== void 0 ? _c : undefined));
15069
15008
  output.userLastName = (launchInfo
15070
15009
  ? launchInfo.userLastName
15071
- : ((_f = output.userLastName) !== null && _f !== void 0 ? _f : undefined));
15010
+ : ((_d = output.userLastName) !== null && _d !== void 0 ? _d : undefined));
15072
15011
  output.userEmail = (launchInfo
15073
15012
  ? launchInfo.userEmail
15074
- : ((_g = output.userEmail) !== null && _g !== void 0 ? _g : undefined));
15013
+ : ((_e = output.userEmail) !== null && _e !== void 0 ? _e : undefined));
15075
15014
  output.userAvatarURL = (launchInfo
15076
- ? ((_h = launchInfo.userImage) !== null && _h !== void 0 ? _h : 'http://www.gravatar.com/avatar/?d=identicon')
15077
- : ((_j = output.userAvatarURL) !== null && _j !== void 0 ? _j : undefined));
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));
15078
15017
  output.isLearner = (launchInfo
15079
15018
  ? !!launchInfo.isLearner
15080
- : ((_k = output.isLearner) !== null && _k !== void 0 ? _k : undefined));
15019
+ : ((_h = output.isLearner) !== null && _h !== void 0 ? _h : undefined));
15081
15020
  output.isTTM = (launchInfo
15082
15021
  ? !!launchInfo.isTTM
15083
- : ((_l = output.isTTM) !== null && _l !== void 0 ? _l : undefined));
15022
+ : ((_j = output.isTTM) !== null && _j !== void 0 ? _j : undefined));
15084
15023
  output.isAdmin = (launchInfo
15085
15024
  ? !!launchInfo.isAdmin
15086
- : ((_m = output.isAdmin) !== null && _m !== void 0 ? _m : undefined));
15025
+ : ((_k = output.isAdmin) !== null && _k !== void 0 ? _k : undefined));
15087
15026
  output.courseId = (launchInfo
15088
- ? ((_o = output.courseId) !== null && _o !== void 0 ? _o : launchInfo.courseId)
15089
- : ((_p = output.courseId) !== null && _p !== void 0 ? _p : undefined));
15027
+ ? ((_l = output.courseId) !== null && _l !== void 0 ? _l : launchInfo.courseId)
15028
+ : ((_m = output.courseId) !== null && _m !== void 0 ? _m : undefined));
15090
15029
  output.courseName = (launchInfo
15091
15030
  ? launchInfo.contextLabel
15092
- : ((_q = output.courseName) !== null && _q !== void 0 ? _q : undefined));
15031
+ : ((_o = output.courseName) !== null && _o !== void 0 ? _o : undefined));
15093
15032
  // Add other session variables
15094
15033
  Object.keys(req.session).forEach((propName) => {
15095
15034
  // Skip if prop already in output
@@ -15163,7 +15102,7 @@ const genRouteHandler = (opts) => {
15163
15102
  * @author Gabe Abrams
15164
15103
  */
15165
15104
  const logServerEvent = (logOpts) => __awaiter(void 0, void 0, void 0, function* () {
15166
- var _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
15105
+ var _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
15167
15106
  // NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
15168
15107
  // that indicates that this is actually a client event, but we don't
15169
15108
  // include that in the LogFunction type because this is internal and
@@ -15195,24 +15134,24 @@ const genRouteHandler = (opts) => {
15195
15134
  timestamp,
15196
15135
  context: (typeof logOpts.context === 'string'
15197
15136
  ? logOpts.context
15198
- : ((_s = ((_r = logOpts.context) !== null && _r !== void 0 ? _r : {})._) !== null && _s !== void 0 ? _s : LogBuiltInMetadata.Context.Uncategorized)),
15199
- subcontext: ((_t = logOpts.subcontext) !== null && _t !== void 0 ? _t : LogBuiltInMetadata.Context.Uncategorized),
15200
- tags: ((_u = logOpts.tags) !== null && _u !== void 0 ? _u : []),
15201
- level: ((_v = logOpts.level) !== null && _v !== void 0 ? _v : LogLevel$1.Info),
15202
- metadata: ((_w = logOpts.metadata) !== null && _w !== void 0 ? _w : {}),
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 : {}),
15203
15142
  };
15204
15143
  // Type-specific info
15205
15144
  const typeSpecificInfo = (('error' in opts && opts.error)
15206
15145
  ? {
15207
15146
  type: LogType$1.Error,
15208
- errorMessage: (_x = logOpts.error.message) !== null && _x !== void 0 ? _x : 'Unknown message',
15209
- errorCode: (_y = logOpts.error.code) !== null && _y !== void 0 ? _y : ReactKitErrorCode$1.NoCode,
15210
- errorStack: (_z = logOpts.error.stack) !== null && _z !== void 0 ? _z : 'No stack',
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',
15211
15150
  }
15212
15151
  : {
15213
15152
  type: LogType$1.Action,
15214
- target: ((_0 = logOpts.target) !== null && _0 !== void 0 ? _0 : LogBuiltInMetadata.Target.NoTarget),
15215
- action: ((_1 = logOpts.action) !== null && _1 !== void 0 ? _1 : LogAction$1.Unknown),
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),
15216
15155
  });
15217
15156
  // Source-specific info
15218
15157
  const sourceSpecificInfo = (logOpts.overrideAsClientEvent
@@ -15247,7 +15186,7 @@ const genRouteHandler = (opts) => {
15247
15186
  catch (err) {
15248
15187
  // Print because we cannot store the error
15249
15188
  // eslint-disable-next-line no-console
15250
- console.error('Could not log the following:', logOpts, 'due to this error:', ((_2 = err) !== null && _2 !== void 0 ? _2 : {}).message, ((_3 = err) !== null && _3 !== void 0 ? _3 : {}).stack);
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);
15251
15190
  // Create a dummy log to return
15252
15191
  const dummyMainInfo = {
15253
15192
  id: '-1',
@@ -15600,18 +15539,6 @@ const initLogCollection = (Collection) => {
15600
15539
  });
15601
15540
  };
15602
15541
 
15603
- /**
15604
- * Initialize a cross-server credential collection given the dce-mango Collection class
15605
- * @author Gabe Abrams
15606
- * @param Collection the Collection class from dce-mango
15607
- * @returns initialized logCollection
15608
- */
15609
- const initCrossServerCredentialCollection = (Collection) => {
15610
- return new Collection('CrossServerCredential', {
15611
- uniqueIndexKey: 'key',
15612
- });
15613
- };
15614
-
15615
15542
  /*------------------------------------------------------------------------*/
15616
15543
  /* -------------------------------- Cache ------------------------------- */
15617
15544
  /*------------------------------------------------------------------------*/
@@ -16361,6 +16288,9 @@ const shuffleArray = (arr) => {
16361
16288
  * @param [opts.host] host to send request to
16362
16289
  * @param [opts.method=GET] http method to use
16363
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
16364
16294
  * @param [opts.responseType=JSON] expected response type
16365
16295
  * @returns { body, status, headers } on success
16366
16296
  */
@@ -16397,7 +16327,7 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
16397
16327
  url = `https://${opts.host}${opts.path}${query}`;
16398
16328
  }
16399
16329
  // Update headers
16400
- const headers = {};
16330
+ const headers = opts.headers || {};
16401
16331
  let data = null;
16402
16332
  if (!headers['Content-Type']) {
16403
16333
  // Form encoded
@@ -16470,49 +16400,49 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
16470
16400
  });
16471
16401
 
16472
16402
  /**
16473
- * Visit an endpoint on another server
16403
+ * Send a server-to-server request from this sever to another server that uses
16404
+ * dce-reactkit [for server only]
16474
16405
  * @author Gabe Abrams
16475
16406
  * @param opts object containing all arguments
16476
- * @param opts.method the method of the endpoint
16477
- * @param opts.path the path of the other server's endpoint
16478
- * @param opts.host the host of the other server
16479
- * @param [opts.key=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_KEY] reactkit cross-server
16480
- * credential key
16481
- * @param [opts.secret=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_SECRET reactkit cross-server
16482
- * credential secret
16483
- * @param [opts.params={}] query/body parameters to include
16484
- * @param [opts.responseType=JSON] the response type from the other server
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
16485
16413
  */
16486
16414
  const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0, function* () {
16487
- var _a, _b, _c;
16488
- // Get cross-server credentials
16489
- const key = (_a = opts.key) !== null && _a !== void 0 ? _a : process.env.REACTKIT_CROSS_SERVER_KEY;
16490
- const secret = (_b = opts.secret) !== null && _b !== void 0 ? _b : process.env.REACTKIT_CROSS_SERVER_SECRET;
16491
- // Throw error if no credentials
16492
- if (!key || !secret) {
16493
- throw new ErrorWithCode('Cannot send cross-server signed request because either or both the key and secret were not included or found in env.', ReactKitErrorCode$1.CrossServerNoCredentialsToSignWith);
16494
- }
16495
- // Create signed pack
16496
- const signedPack = createSignedPack({
16497
- method: opts.method,
16498
- path: opts.path,
16499
- params: (_c = opts.params) !== null && _c !== void 0 ? _c : {},
16500
- key,
16501
- secret,
16502
- });
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
+ }
16503
16436
  // Send the request
16504
16437
  const response = yield sendServerToServerRequest({
16505
- path: opts.path,
16506
16438
  host: opts.host,
16507
- method: opts.method,
16508
- params: {
16509
- signedPack,
16510
- },
16511
- responseType: opts.responseType,
16439
+ path: opts.path,
16440
+ method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
16441
+ params,
16512
16442
  });
16513
16443
  // Check for failure
16514
16444
  if (!response || !response.body) {
16515
- throw new ErrorWithCode('We didn\'t get a response from the other server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
16445
+ throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
16516
16446
  }
16517
16447
  if (!response.body.success) {
16518
16448
  // Other errors
@@ -16526,6 +16456,27 @@ const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0,
16526
16456
  return body;
16527
16457
  });
16528
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
+
16529
16480
  /**
16530
16481
  * Days of the week
16531
16482
  * @author Gabe Abrams
@@ -16542,5 +16493,5 @@ var DayOfWeek;
16542
16493
  })(DayOfWeek || (DayOfWeek = {}));
16543
16494
  var DayOfWeek$1 = DayOfWeek;
16544
16495
 
16545
- 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, addDBEditorEndpoints, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, genRouteHandler, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, idify, initClient, initCrossServerCredentialCollection, initLogCollection, initServer, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitEndpointOnAnotherServer, visitServerEndpoint, waitMs };
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, addDBEditorEndpoints, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, genRouteHandler, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getWordCount, handleError, handleSuccess, idify, initClient, initLogCollection, initServer, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitEndpointOnAnotherServer, visitServerEndpoint, waitMs };
16546
16497
  //# sourceMappingURL=index.js.map