dce-reactkit 3.11.4 → 3.12.1-beta-cross-server.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +1 -3
- package/README.md +20 -0
- package/dist/cjs/index.js +294 -162
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/helpers/dataSigner.d.ts +38 -0
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +10 -10
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +15 -13
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -6
- package/dist/cjs/types/index.d.ts +3 -2
- package/dist/cjs/types/server/initCrossServerCredentialCollection.d.ts +8 -0
- package/dist/cjs/types/server/initServer.d.ts +11 -0
- package/dist/cjs/types/types/CrossServerCredential.d.ts +11 -0
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +13 -1
- package/dist/esm/index.js +293 -162
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/helpers/dataSigner.d.ts +38 -0
- package/dist/esm/types/helpers/genRouteHandler.d.ts +10 -10
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +15 -13
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -6
- package/dist/esm/types/index.d.ts +3 -2
- package/dist/esm/types/server/initCrossServerCredentialCollection.d.ts +8 -0
- package/dist/esm/types/server/initServer.d.ts +11 -0
- package/dist/esm/types/types/CrossServerCredential.d.ts +11 -0
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +13 -1
- package/dist/index.d.ts +62 -35
- package/genEncodedSecret.ts +28 -0
- package/package.json +5 -2
- package/src/components/IntelliTable.tsx +1 -1
- package/src/components/ItemPicker/NestableItemList.tsx +0 -1
- package/src/helpers/dataSigner.ts +232 -0
- package/src/helpers/genRouteHandler.ts +55 -95
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +42 -41
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +3 -5
- package/src/index.ts +4 -2
- package/src/server/initCrossServerCredentialCollection.ts +16 -0
- package/src/server/initServer.ts +18 -0
- package/src/types/CrossServerCredential.ts +16 -0
- package/src/types/ReactKitErrorCode.tsx +13 -1
- package/dist/cjs/types/helpers/getWordCount.d.ts +0 -10
- package/dist/esm/types/helpers/getWordCount.d.ts +0 -10
- package/src/helpers/getWordCount.ts +0 -26
package/dist/esm/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import { faExclamationTriangle, faHourglassEnd, faCircle, faDotCircle, faCheckSq
|
|
|
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';
|
|
7
9
|
import qs from 'qs';
|
|
8
10
|
|
|
9
11
|
/******************************************************************************
|
|
@@ -31,7 +33,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
31
33
|
});
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
// Highest error code =
|
|
36
|
+
// Highest error code = DRK30
|
|
35
37
|
/**
|
|
36
38
|
* List of error codes built into the react kit
|
|
37
39
|
* @author Gabe Abrams
|
|
@@ -57,6 +59,18 @@ var ReactKitErrorCode;
|
|
|
57
59
|
ReactKitErrorCode["NotConnected"] = "DRK14";
|
|
58
60
|
ReactKitErrorCode["SelfSigned"] = "DRK15";
|
|
59
61
|
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";
|
|
60
74
|
})(ReactKitErrorCode || (ReactKitErrorCode = {}));
|
|
61
75
|
var ReactKitErrorCode$1 = ReactKitErrorCode;
|
|
62
76
|
|
|
@@ -2644,7 +2658,7 @@ const NestableItemList = (props) => {
|
|
|
2644
2658
|
React__default.createElement(FontAwesomeIcon, { icon: childExpanded[item.id] ? faChevronDown : faChevronRight })))),
|
|
2645
2659
|
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) => {
|
|
2646
2660
|
onChanged(changeChecked(item.id, checked, items));
|
|
2647
|
-
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light
|
|
2661
|
+
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2648
2662
|
(item.isGroup && childExpanded[item.id]) && (React__default.createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2649
2663
|
paddingLeft: '2.2rem',
|
|
2650
2664
|
} },
|
|
@@ -3049,7 +3063,7 @@ const IntelliTable = (props) => {
|
|
|
3049
3063
|
param: column.param,
|
|
3050
3064
|
visible: checked,
|
|
3051
3065
|
});
|
|
3052
|
-
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.
|
|
3066
|
+
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
|
|
3053
3067
|
}),
|
|
3054
3068
|
React__default.createElement("div", { className: "mt-3" }, "Or you can:"),
|
|
3055
3069
|
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: () => {
|
|
@@ -14021,6 +14035,8 @@ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
|
|
|
14021
14035
|
let _cacclGetLaunchInfo;
|
|
14022
14036
|
// Stored copy of dce-mango log collection
|
|
14023
14037
|
let _logCollection;
|
|
14038
|
+
// Stored copy of dce-mango cross-server credential collection
|
|
14039
|
+
let _crossServerCredentialCollection;
|
|
14024
14040
|
/*------------------------------------------------------------------------*/
|
|
14025
14041
|
/* Helpers */
|
|
14026
14042
|
/*------------------------------------------------------------------------*/
|
|
@@ -14045,6 +14061,15 @@ const cacclGetLaunchInfo = (req) => {
|
|
|
14045
14061
|
const internalGetLogCollection = () => {
|
|
14046
14062
|
return _logCollection !== null && _logCollection !== void 0 ? _logCollection : null;
|
|
14047
14063
|
};
|
|
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
|
+
};
|
|
14048
14073
|
/*------------------------------------------------------------------------*/
|
|
14049
14074
|
/* Main */
|
|
14050
14075
|
/*------------------------------------------------------------------------*/
|
|
@@ -14064,10 +14089,14 @@ const internalGetLogCollection = () => {
|
|
|
14064
14089
|
* userIds are allowed to review logs. If a dce-mango collection, only
|
|
14065
14090
|
* Canvas admins with entries in that collection ({ userId, ...}) are allowed
|
|
14066
14091
|
* 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
|
|
14067
14095
|
*/
|
|
14068
14096
|
const initServer = (opts) => {
|
|
14069
14097
|
_cacclGetLaunchInfo = opts.getLaunchInfo;
|
|
14070
14098
|
_logCollection = opts.logCollection;
|
|
14099
|
+
_crossServerCredentialCollection = opts.crossServerCredentialCollection;
|
|
14071
14100
|
/*----------------------------------------*/
|
|
14072
14101
|
/* Logging */
|
|
14073
14102
|
/*----------------------------------------*/
|
|
@@ -14610,6 +14639,154 @@ const parseUserAgent = (userAgent) => {
|
|
|
14610
14639
|
};
|
|
14611
14640
|
};
|
|
14612
14641
|
|
|
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
|
+
|
|
14613
14790
|
/**
|
|
14614
14791
|
* Generate an express API route handler
|
|
14615
14792
|
* @author Gabe Abrams
|
|
@@ -14617,14 +14794,15 @@ const parseUserAgent = (userAgent) => {
|
|
|
14617
14794
|
* @param opts.paramTypes map containing the types for each parameter that is
|
|
14618
14795
|
* included in the request (map: param name => type)
|
|
14619
14796
|
* @param opts.handler function that processes the request
|
|
14620
|
-
* @param [opts.
|
|
14621
|
-
*
|
|
14622
|
-
*
|
|
14623
|
-
*
|
|
14624
|
-
*
|
|
14625
|
-
*
|
|
14626
|
-
*
|
|
14627
|
-
*
|
|
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
|
|
14628
14806
|
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
14629
14807
|
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
14630
14808
|
* where the error message is prefixed with this string. For example,
|
|
@@ -14652,86 +14830,51 @@ const parseUserAgent = (userAgent) => {
|
|
|
14652
14830
|
const genRouteHandler = (opts) => {
|
|
14653
14831
|
// Return a route handler
|
|
14654
14832
|
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14655
|
-
|
|
14833
|
+
/*----------------------------------------*/
|
|
14834
|
+
/* ------------- Preparation ------------ */
|
|
14835
|
+
/*----------------------------------------*/
|
|
14836
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
14656
14837
|
// Output params
|
|
14657
14838
|
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);
|
|
14658
14849
|
/*----------------------------------------*/
|
|
14659
|
-
/*
|
|
14850
|
+
/* ------- Cross-Server Validation ------ */
|
|
14660
14851
|
/*----------------------------------------*/
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
.replace(/(^\w+:|^)\/\//, '')
|
|
14668
|
-
// Remove port
|
|
14669
|
-
.replace(/:\d+$/, ''));
|
|
14670
|
-
const serverHostname = String(req.hostname);
|
|
14671
|
-
// Check allowed
|
|
14672
|
-
if (opts.allowedHosts) {
|
|
14673
|
-
// Only accept requests from allowed hosts
|
|
14674
|
-
const allowedArray = (Array.isArray(opts.allowedHosts)
|
|
14675
|
-
? opts.allowedHosts
|
|
14676
|
-
: [opts.allowedHosts]);
|
|
14677
|
-
// Check if server is localhost
|
|
14678
|
-
if (serverHostname === 'localhost') {
|
|
14679
|
-
// Allow localhost
|
|
14680
|
-
allowedArray.push('localhost');
|
|
14681
|
-
}
|
|
14682
|
-
// Check if current host is allowed
|
|
14683
|
-
const allowed = allowedArray.some((allowedHost) => {
|
|
14684
|
-
if (allowedHost.startsWith('*')) {
|
|
14685
|
-
// Check end of hostname
|
|
14686
|
-
return originHostname.endsWith(allowedHost.substring(1));
|
|
14687
|
-
}
|
|
14688
|
-
// Check full hostname
|
|
14689
|
-
return originHostname.toLowerCase() === allowedHost.toLowerCase();
|
|
14690
|
-
});
|
|
14691
|
-
// If not allowed, return error
|
|
14692
|
-
if (!allowed) {
|
|
14693
|
-
return handleError(res, {
|
|
14694
|
-
message: 'You are not allowed to access this endpoint.',
|
|
14695
|
-
code: ReactKitErrorCode$1.HostNotAllowed,
|
|
14696
|
-
status: 403,
|
|
14697
|
-
});
|
|
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);
|
|
14698
14858
|
}
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
: [opts.bannedHosts]);
|
|
14706
|
-
// Check if current host is banned
|
|
14707
|
-
const banned = bannedArray.some((bannedHost) => {
|
|
14708
|
-
if (bannedHost.startsWith('*')) {
|
|
14709
|
-
// Check end of hostname
|
|
14710
|
-
return originHostname.endsWith(bannedHost.substring(1));
|
|
14711
|
-
}
|
|
14712
|
-
// Check full hostname
|
|
14713
|
-
return originHostname.toLowerCase() === bannedHost.toLowerCase();
|
|
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,
|
|
14714
14865
|
});
|
|
14715
|
-
//
|
|
14716
|
-
|
|
14717
|
-
return handleError(res, {
|
|
14718
|
-
message: 'You are not allowed to access this endpoint.',
|
|
14719
|
-
code: ReactKitErrorCode$1.HostBanned,
|
|
14720
|
-
status: 403,
|
|
14721
|
-
});
|
|
14722
|
-
}
|
|
14866
|
+
// Replace body with params from the pack
|
|
14867
|
+
requestBody = crossServerParams !== null && crossServerParams !== void 0 ? crossServerParams : {};
|
|
14723
14868
|
}
|
|
14724
14869
|
/*----------------------------------------*/
|
|
14725
14870
|
/* ------------ Parse Params ------------ */
|
|
14726
14871
|
/*----------------------------------------*/
|
|
14727
14872
|
// Process items one by one
|
|
14728
|
-
const paramList = Object.entries((
|
|
14873
|
+
const paramList = Object.entries((_c = opts.paramTypes) !== null && _c !== void 0 ? _c : {});
|
|
14729
14874
|
for (let i = 0; i < paramList.length; i++) {
|
|
14730
14875
|
const [name, type] = paramList[i];
|
|
14731
14876
|
// Find the value as a string
|
|
14732
|
-
const value =
|
|
14733
|
-
|| req.query[name]
|
|
14734
|
-
|| req.body[name]);
|
|
14877
|
+
const value = requestBody[name];
|
|
14735
14878
|
// Parse
|
|
14736
14879
|
if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
|
|
14737
14880
|
// Boolean
|
|
@@ -14889,7 +15032,7 @@ const genRouteHandler = (opts) => {
|
|
|
14889
15032
|
// Not launched
|
|
14890
15033
|
(!launched || !launchInfo)
|
|
14891
15034
|
// Not skipping the session check
|
|
14892
|
-
&& !
|
|
15035
|
+
&& !skipSessionCheck) {
|
|
14893
15036
|
return handleError(res, {
|
|
14894
15037
|
message: 'Your session has expired. Please refresh the page and try again.',
|
|
14895
15038
|
code: ReactKitErrorCode$1.SessionExpired,
|
|
@@ -14909,7 +15052,7 @@ const genRouteHandler = (opts) => {
|
|
|
14909
15052
|
&& !launchInfo.isLearner
|
|
14910
15053
|
&& !launchInfo.isAdmin))
|
|
14911
15054
|
// Not skipping the session check
|
|
14912
|
-
&& !
|
|
15055
|
+
&& !skipSessionCheck) {
|
|
14913
15056
|
return handleError(res, {
|
|
14914
15057
|
message: 'Your session was invalid. Please refresh the page and try again.',
|
|
14915
15058
|
code: ReactKitErrorCode$1.SessionExpired,
|
|
@@ -14919,34 +15062,34 @@ const genRouteHandler = (opts) => {
|
|
|
14919
15062
|
// Add launch info to output
|
|
14920
15063
|
output.userId = (launchInfo
|
|
14921
15064
|
? launchInfo.userId
|
|
14922
|
-
: ((
|
|
15065
|
+
: ((_d = output.userId) !== null && _d !== void 0 ? _d : undefined));
|
|
14923
15066
|
output.userFirstName = (launchInfo
|
|
14924
15067
|
? launchInfo.userFirstName
|
|
14925
|
-
: ((
|
|
15068
|
+
: ((_e = output.userFirstName) !== null && _e !== void 0 ? _e : undefined));
|
|
14926
15069
|
output.userLastName = (launchInfo
|
|
14927
15070
|
? launchInfo.userLastName
|
|
14928
|
-
: ((
|
|
15071
|
+
: ((_f = output.userLastName) !== null && _f !== void 0 ? _f : undefined));
|
|
14929
15072
|
output.userEmail = (launchInfo
|
|
14930
15073
|
? launchInfo.userEmail
|
|
14931
|
-
: ((
|
|
15074
|
+
: ((_g = output.userEmail) !== null && _g !== void 0 ? _g : undefined));
|
|
14932
15075
|
output.userAvatarURL = (launchInfo
|
|
14933
|
-
? ((
|
|
14934
|
-
: ((
|
|
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));
|
|
14935
15078
|
output.isLearner = (launchInfo
|
|
14936
15079
|
? !!launchInfo.isLearner
|
|
14937
|
-
: ((
|
|
15080
|
+
: ((_k = output.isLearner) !== null && _k !== void 0 ? _k : undefined));
|
|
14938
15081
|
output.isTTM = (launchInfo
|
|
14939
15082
|
? !!launchInfo.isTTM
|
|
14940
|
-
: ((
|
|
15083
|
+
: ((_l = output.isTTM) !== null && _l !== void 0 ? _l : undefined));
|
|
14941
15084
|
output.isAdmin = (launchInfo
|
|
14942
15085
|
? !!launchInfo.isAdmin
|
|
14943
|
-
: ((
|
|
15086
|
+
: ((_m = output.isAdmin) !== null && _m !== void 0 ? _m : undefined));
|
|
14944
15087
|
output.courseId = (launchInfo
|
|
14945
|
-
? ((
|
|
14946
|
-
: ((
|
|
15088
|
+
? ((_o = output.courseId) !== null && _o !== void 0 ? _o : launchInfo.courseId)
|
|
15089
|
+
: ((_p = output.courseId) !== null && _p !== void 0 ? _p : undefined));
|
|
14947
15090
|
output.courseName = (launchInfo
|
|
14948
15091
|
? launchInfo.contextLabel
|
|
14949
|
-
: ((
|
|
15092
|
+
: ((_q = output.courseName) !== null && _q !== void 0 ? _q : undefined));
|
|
14950
15093
|
// Add other session variables
|
|
14951
15094
|
Object.keys(req.session).forEach((propName) => {
|
|
14952
15095
|
// Skip if prop already in output
|
|
@@ -15020,7 +15163,7 @@ const genRouteHandler = (opts) => {
|
|
|
15020
15163
|
* @author Gabe Abrams
|
|
15021
15164
|
*/
|
|
15022
15165
|
const logServerEvent = (logOpts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15023
|
-
var
|
|
15166
|
+
var _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
|
|
15024
15167
|
// NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
|
|
15025
15168
|
// that indicates that this is actually a client event, but we don't
|
|
15026
15169
|
// include that in the LogFunction type because this is internal and
|
|
@@ -15052,24 +15195,24 @@ const genRouteHandler = (opts) => {
|
|
|
15052
15195
|
timestamp,
|
|
15053
15196
|
context: (typeof logOpts.context === 'string'
|
|
15054
15197
|
? logOpts.context
|
|
15055
|
-
: ((
|
|
15056
|
-
subcontext: ((
|
|
15057
|
-
tags: ((
|
|
15058
|
-
level: ((
|
|
15059
|
-
metadata: ((
|
|
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 : {}),
|
|
15060
15203
|
};
|
|
15061
15204
|
// Type-specific info
|
|
15062
15205
|
const typeSpecificInfo = (('error' in opts && opts.error)
|
|
15063
15206
|
? {
|
|
15064
15207
|
type: LogType$1.Error,
|
|
15065
|
-
errorMessage: (
|
|
15066
|
-
errorCode: (
|
|
15067
|
-
errorStack: (
|
|
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',
|
|
15068
15211
|
}
|
|
15069
15212
|
: {
|
|
15070
15213
|
type: LogType$1.Action,
|
|
15071
|
-
target: ((
|
|
15072
|
-
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),
|
|
15073
15216
|
});
|
|
15074
15217
|
// Source-specific info
|
|
15075
15218
|
const sourceSpecificInfo = (logOpts.overrideAsClientEvent
|
|
@@ -15104,7 +15247,7 @@ const genRouteHandler = (opts) => {
|
|
|
15104
15247
|
catch (err) {
|
|
15105
15248
|
// Print because we cannot store the error
|
|
15106
15249
|
// eslint-disable-next-line no-console
|
|
15107
|
-
console.error('Could not log the following:', logOpts, 'due to this error:', ((
|
|
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);
|
|
15108
15251
|
// Create a dummy log to return
|
|
15109
15252
|
const dummyMainInfo = {
|
|
15110
15253
|
id: '-1',
|
|
@@ -15457,6 +15600,18 @@ const initLogCollection = (Collection) => {
|
|
|
15457
15600
|
});
|
|
15458
15601
|
};
|
|
15459
15602
|
|
|
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
|
+
|
|
15460
15615
|
/*------------------------------------------------------------------------*/
|
|
15461
15616
|
/* -------------------------------- Cache ------------------------------- */
|
|
15462
15617
|
/*------------------------------------------------------------------------*/
|
|
@@ -16206,9 +16361,6 @@ const shuffleArray = (arr) => {
|
|
|
16206
16361
|
* @param [opts.host] host to send request to
|
|
16207
16362
|
* @param [opts.method=GET] http method to use
|
|
16208
16363
|
* @param [opts.params] body/data to include in the request
|
|
16209
|
-
* @param [opts.headers] headers to include in the request
|
|
16210
|
-
* @param [opts.sendCrossDomainCredentials=true if in development mode] if true,
|
|
16211
|
-
* send cross-domain credentials even if not in dev mode
|
|
16212
16364
|
* @param [opts.responseType=JSON] expected response type
|
|
16213
16365
|
* @returns { body, status, headers } on success
|
|
16214
16366
|
*/
|
|
@@ -16245,7 +16397,7 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
|
|
|
16245
16397
|
url = `https://${opts.host}${opts.path}${query}`;
|
|
16246
16398
|
}
|
|
16247
16399
|
// Update headers
|
|
16248
|
-
const headers =
|
|
16400
|
+
const headers = {};
|
|
16249
16401
|
let data = null;
|
|
16250
16402
|
if (!headers['Content-Type']) {
|
|
16251
16403
|
// Form encoded
|
|
@@ -16318,49 +16470,49 @@ const sendServerToServerRequest = (opts) => __awaiter(void 0, void 0, void 0, fu
|
|
|
16318
16470
|
});
|
|
16319
16471
|
|
|
16320
16472
|
/**
|
|
16321
|
-
*
|
|
16322
|
-
* dce-reactkit [for server only]
|
|
16473
|
+
* Visit an endpoint on another server
|
|
16323
16474
|
* @author Gabe Abrams
|
|
16324
16475
|
* @param opts object containing all arguments
|
|
16325
|
-
* @param opts.
|
|
16326
|
-
* @param opts.path
|
|
16327
|
-
* @param
|
|
16328
|
-
* @param [opts.
|
|
16329
|
-
*
|
|
16330
|
-
* @
|
|
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
|
|
16331
16485
|
*/
|
|
16332
16486
|
const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16333
|
-
var _a;
|
|
16334
|
-
//
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
params =
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
return [key, JSON.stringify(value)];
|
|
16350
|
-
}
|
|
16351
|
-
return [key, value];
|
|
16352
|
-
}));
|
|
16353
|
-
}
|
|
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
|
+
});
|
|
16354
16503
|
// Send the request
|
|
16355
16504
|
const response = yield sendServerToServerRequest({
|
|
16356
|
-
host: opts.host,
|
|
16357
16505
|
path: opts.path,
|
|
16358
|
-
|
|
16359
|
-
|
|
16506
|
+
host: opts.host,
|
|
16507
|
+
method: opts.method,
|
|
16508
|
+
params: {
|
|
16509
|
+
signedPack,
|
|
16510
|
+
},
|
|
16511
|
+
responseType: opts.responseType,
|
|
16360
16512
|
});
|
|
16361
16513
|
// Check for failure
|
|
16362
16514
|
if (!response || !response.body) {
|
|
16363
|
-
throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
|
|
16515
|
+
throw new ErrorWithCode('We didn\'t get a response from the other server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
|
|
16364
16516
|
}
|
|
16365
16517
|
if (!response.body.success) {
|
|
16366
16518
|
// Other errors
|
|
@@ -16374,27 +16526,6 @@ const visitEndpointOnAnotherServer = (opts) => __awaiter(void 0, void 0, void 0,
|
|
|
16374
16526
|
return body;
|
|
16375
16527
|
});
|
|
16376
16528
|
|
|
16377
|
-
const punctuationRegex = /[!@#$%^&*(),.?\/;:'"\-\[\]]/g;
|
|
16378
|
-
/**
|
|
16379
|
-
* Get number of words in string
|
|
16380
|
-
* @author Gardenia Liu
|
|
16381
|
-
* @author Allison Zhang
|
|
16382
|
-
* @author Gabe Abrams
|
|
16383
|
-
* @param text the string to check
|
|
16384
|
-
* @returns number of words in the string
|
|
16385
|
-
*/
|
|
16386
|
-
const getWordCount = (text) => {
|
|
16387
|
-
const trimmedTextWithoutPunctuation = (text
|
|
16388
|
-
// Remove leading and trailing whitespace
|
|
16389
|
-
.trim()
|
|
16390
|
-
// Remove punctuation
|
|
16391
|
-
.replace(punctuationRegex, ''));
|
|
16392
|
-
if (trimmedTextWithoutPunctuation.length === 0) {
|
|
16393
|
-
return 0;
|
|
16394
|
-
}
|
|
16395
|
-
return trimmedTextWithoutPunctuation.split(/\s+/g).length;
|
|
16396
|
-
};
|
|
16397
|
-
|
|
16398
16529
|
/**
|
|
16399
16530
|
* Days of the week
|
|
16400
16531
|
* @author Gabe Abrams
|
|
@@ -16411,5 +16542,5 @@ var DayOfWeek;
|
|
|
16411
16542
|
})(DayOfWeek || (DayOfWeek = {}));
|
|
16412
16543
|
var DayOfWeek$1 = DayOfWeek;
|
|
16413
16544
|
|
|
16414
|
-
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,
|
|
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 };
|
|
16415
16546
|
//# sourceMappingURL=index.js.map
|