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