dce-reactkit 3.0.0-beta.25 → 3.0.0-beta.27

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/dist/esm/index.js CHANGED
@@ -27,7 +27,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
27
27
  });
28
28
  }
29
29
 
30
- // Highest error code = DRK2
30
+ // Highest error code = DRK8
31
31
  /**
32
32
  * List of error codes built into the react kit
33
33
  * @author Gabe Abrams
@@ -40,6 +40,8 @@ var ReactKitErrorCode;
40
40
  ReactKitErrorCode["MissingParameter"] = "DRK4";
41
41
  ReactKitErrorCode["InvalidParameter"] = "DRK5";
42
42
  ReactKitErrorCode["WrongCourse"] = "DRK6";
43
+ ReactKitErrorCode["NoCACCLSendRequestFunction"] = "DRK7";
44
+ ReactKitErrorCode["NoCACCLGetLaunchInfoFunction"] = "DRK8";
43
45
  })(ReactKitErrorCode || (ReactKitErrorCode = {}));
44
46
  var ReactKitErrorCode$1 = ReactKitErrorCode;
45
47
 
@@ -551,6 +553,24 @@ class ErrorWithCode extends Error {
551
553
  /* Static Helpers */
552
554
  /*------------------------------------------------------------------------*/
553
555
  /*----------------------------------------*/
556
+ /* Send Request */
557
+ /*----------------------------------------*/
558
+ // Store copy of caccl send request
559
+ let _cacclSendRequest;
560
+ /**
561
+ * Send a request using caccl's send request feature
562
+ * @author Gabe Abrams
563
+ * @param opts send request options
564
+ * @returns send request response
565
+ */
566
+ const cacclSendRequest = (opts) => __awaiter(void 0, void 0, void 0, function* () {
567
+ // Make sure send request has been passed in
568
+ if (!_cacclSendRequest) {
569
+ throw new ErrorWithCode('The request could not be sent because the AppWrapper component does not have a copy of sendRequest from CACCL.', ReactKitErrorCode$1.NoCACCLSendRequestFunction);
570
+ }
571
+ return _cacclSendRequest(opts);
572
+ });
573
+ /*----------------------------------------*/
554
574
  /* Alert */
555
575
  /*----------------------------------------*/
556
576
  // Stored copies of setters
@@ -562,7 +582,7 @@ let onAlertClosed;
562
582
  * @param title the title text to display at the top of the alert
563
583
  * @param text the text to display in the alert
564
584
  */
565
- const alert = (title, text) => __awaiter(void 0, void 0, void 0, function* () {
585
+ const alert$1 = (title, text) => __awaiter(void 0, void 0, void 0, function* () {
566
586
  // Fallback if alert not available
567
587
  if (!setAlertInfo) {
568
588
  window.alert(`${title}\n\n${text}`);
@@ -636,7 +656,7 @@ const showFatalError = (error, errorTitle = 'An Error Occurred') => {
636
656
  : String((_b = error.code) !== null && _b !== void 0 ? _b : ReactKitErrorCode$1.NoCode));
637
657
  // Handle case where app hasn't loaded
638
658
  if (!setFatalErrorMessage || !setFatalErrorCode) {
639
- alert(errorTitle, `${message} (code: ${code}). Please contact support.`);
659
+ alert$1(errorTitle, `${message} (code: ${code}). Please contact support.`);
640
660
  return undefined;
641
661
  }
642
662
  // Use setters
@@ -653,7 +673,9 @@ const AppWrapper = (props) => {
653
673
  /* Setup */
654
674
  /*------------------------------------------------------------------------*/
655
675
  /* -------------- Props ------------- */
656
- const { children, dark, sessionExpiredMessage = 'Your session has expired. Please go back to Canvas and start over.', } = props;
676
+ const { children, sendRequest, dark, sessionExpiredMessage = 'Your session has expired. Please go back to Canvas and start over.', } = props;
677
+ // Store copy of send request
678
+ _cacclSendRequest = sendRequest;
657
679
  /* -------------- State ------------- */
658
680
  // Fatal error
659
681
  const [fatalErrorMessage, setFatalErrorMessageInner,] = useState();
@@ -1045,5 +1067,408 @@ const roundToNumDecimals = (num, numDecimals) => {
1045
1067
  return (Math.round(num * rounder) / rounder);
1046
1068
  };
1047
1069
 
1048
- export { AppWrapper, ErrorBox, ErrorWithCode, LoadingSpinner, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, ReactKitErrorCode$1 as ReactKitErrorCode, TabBox, Variant$1 as Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, showFatalError, sum, waitMs };
1070
+ // Keep track of whether or not session expiry has already been handled
1071
+ let sessionAlreadyExpired = false;
1072
+ /*------------------------------------------------------------------------*/
1073
+ /* Main */
1074
+ /*------------------------------------------------------------------------*/
1075
+ /**
1076
+ * Visit an endpoint on the server [for client only]
1077
+ * @author Gabe Abrams
1078
+ * @param opts object containing all arguments
1079
+ * @param opts.path - the path of the server endpoint
1080
+ * @param [opts.method=GET] - the method of the endpoint
1081
+ * @param [opts.params] - query/body parameters to include
1082
+ * @returns response from server
1083
+ */
1084
+ const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function* () {
1085
+ var _a;
1086
+ // Send the request
1087
+ const response = yield cacclSendRequest({
1088
+ path: opts.path,
1089
+ method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
1090
+ params: opts.params,
1091
+ });
1092
+ // Check for failure
1093
+ if (!response || !response.body) {
1094
+ throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
1095
+ }
1096
+ if (!response.body.success) {
1097
+ // Session expired
1098
+ if (response.body.code === ReactKitErrorCode$1.SessionExpired) {
1099
+ // Skip notice if session was already expired
1100
+ if (sessionAlreadyExpired) {
1101
+ // Never return (browser is already reloading)
1102
+ yield new Promise(() => {
1103
+ // Promise that never returns
1104
+ });
1105
+ }
1106
+ sessionAlreadyExpired = true;
1107
+ // Show session expiration message
1108
+ {
1109
+ // Fallback to alert
1110
+ // eslint-disable-next-line no-alert
1111
+ alert('Your session has expired. Please start over.');
1112
+ }
1113
+ // Never return (don't continue execution)
1114
+ yield new Promise(() => {
1115
+ // Promise that never returns
1116
+ });
1117
+ }
1118
+ // Other errors
1119
+ throw new ErrorWithCode((response.body.message
1120
+ || 'An unknown error occurred. Please contact an admin.'), (response.body.code
1121
+ || ReactKitErrorCode$1.NoCode));
1122
+ }
1123
+ // Success! Extract the body
1124
+ const { body } = response.body;
1125
+ // Return
1126
+ return body;
1127
+ });
1128
+
1129
+ // Import custom error
1130
+ // Stored copy of caccl functions
1131
+ let _cacclGetLaunchInfo;
1132
+ /*------------------------------------------------------------------------*/
1133
+ /* Helpers */
1134
+ /*------------------------------------------------------------------------*/
1135
+ /**
1136
+ * Get launch info via CACCL
1137
+ * @author Gabe Abrams
1138
+ * @param req express request object
1139
+ * @returns object { launched, launchInfo }
1140
+ */
1141
+ const cacclGetLaunchInfo = (req) => {
1142
+ {
1143
+ throw new ErrorWithCode('Could not get launch info because server was not initialized with dce-reactkit\'s initServer function', ReactKitErrorCode$1.NoCACCLGetLaunchInfoFunction);
1144
+ }
1145
+ };
1146
+
1147
+ /**
1148
+ * Server-side API param types
1149
+ * @author Gabe Abrams
1150
+ */
1151
+ var ParamType;
1152
+ (function (ParamType) {
1153
+ ParamType["Boolean"] = "boolean";
1154
+ ParamType["BooleanOptional"] = "boolean-optional";
1155
+ ParamType["Float"] = "float";
1156
+ ParamType["FloatOptional"] = "float-optional";
1157
+ ParamType["Int"] = "int";
1158
+ ParamType["IntOptional"] = "int-optional";
1159
+ ParamType["JSON"] = "json";
1160
+ ParamType["JSONOptional"] = "json-optional";
1161
+ ParamType["String"] = "string";
1162
+ ParamType["StringOptional"] = "string-optional";
1163
+ })(ParamType || (ParamType = {}));
1164
+ var ParamType$1 = ParamType;
1165
+
1166
+ // Import shared types
1167
+ /**
1168
+ * Handle an error and respond to the client
1169
+ * @author Gabe Abrams
1170
+ * @param res express response
1171
+ * @param error error info
1172
+ * @param opts.err the error to send to the client
1173
+ * or the error message
1174
+ * @param [opts.code] an error code (only used if err.code is not
1175
+ * included)
1176
+ * @param [opts.status=500] the https status code to use
1177
+ * defined)
1178
+ */
1179
+ const handleError = (res, error) => {
1180
+ // Get the error message
1181
+ let message;
1182
+ if (error && error.message) {
1183
+ message = (error.message || 'An unknown error occurred.');
1184
+ }
1185
+ else if (typeof error === 'string') {
1186
+ message = (error.trim().length > 0
1187
+ ? error
1188
+ : 'An unknown error occurred.');
1189
+ }
1190
+ else {
1191
+ message = 'An unknown error occurred.';
1192
+ }
1193
+ // Get the error code
1194
+ const code = (error.code || ReactKitErrorCode$1.NoCode);
1195
+ // Get the status code
1196
+ const status = (error.status || 500);
1197
+ // Respond to user
1198
+ res
1199
+ // Set the http status code
1200
+ .status(status)
1201
+ // Send a JSON response
1202
+ .json({
1203
+ // Error message
1204
+ message,
1205
+ // Error code
1206
+ code,
1207
+ // Success = false flag so client can detect server-side errors
1208
+ success: false,
1209
+ });
1210
+ return undefined;
1211
+ };
1212
+
1213
+ /**
1214
+ * Send successful API response
1215
+ * @author Gabe Abrams
1216
+ * @param res express response
1217
+ * @param body the body of the response to send to the client
1218
+ */
1219
+ const handleSuccess = (res, body) => {
1220
+ // Send a http 200 json response
1221
+ res.json({
1222
+ // Include the body as a parameter
1223
+ body,
1224
+ // Success = true flag so client can detect successful responses
1225
+ success: true,
1226
+ });
1227
+ return undefined;
1228
+ };
1229
+
1230
+ /**
1231
+ * Generate an express API route handler
1232
+ * @author Gabe Abrams
1233
+ * @param params map containing parameters that are included in the request
1234
+ * (map: param name => type)
1235
+ * @param handler function that processes the request
1236
+ * @returns express route handler that takes the following arguments:
1237
+ * params (map: param name => value), handleSuccess (function for handling
1238
+ * successful requests), handleError (function for handling failed requests),
1239
+ * req (express request object), res (express response object)
1240
+ */
1241
+ const genRouteHandler = (params, handler) => {
1242
+ // Return a route handler
1243
+ return (req, res) => __awaiter(void 0, void 0, void 0, function* () {
1244
+ // Output params
1245
+ const output = {};
1246
+ /*----------------------------------------*/
1247
+ /* Parse Params */
1248
+ /*----------------------------------------*/
1249
+ // Process items one by one
1250
+ const paramList = Object.entries(params);
1251
+ for (let i = 0; i < paramList.length; i++) {
1252
+ const [name, type] = paramList[i];
1253
+ // Find the value as a string
1254
+ const value = (req.params[name]
1255
+ || req.query[name]
1256
+ || req.body[name]);
1257
+ // Parse
1258
+ if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
1259
+ // Boolean
1260
+ // Handle case where value doesn't exist
1261
+ if (value === undefined) {
1262
+ if (type === ParamType$1.BooleanOptional) {
1263
+ output[name] = undefined;
1264
+ }
1265
+ else {
1266
+ return handleError(res, {
1267
+ message: `Parameter ${name} is required, but it was not included.`,
1268
+ code: ReactKitErrorCode$1.MissingParameter,
1269
+ status: 422,
1270
+ });
1271
+ }
1272
+ }
1273
+ else {
1274
+ // Value exists
1275
+ // Simplify value
1276
+ const simpleVal = (String(value)
1277
+ .trim()
1278
+ .toLowerCase());
1279
+ // Parse
1280
+ output[name] = ([
1281
+ 'true',
1282
+ 'yes',
1283
+ 'y',
1284
+ '1',
1285
+ 't',
1286
+ ].indexOf(simpleVal) >= 0);
1287
+ }
1288
+ }
1289
+ else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
1290
+ // Float
1291
+ // Handle case where value doesn't exist
1292
+ if (value === undefined) {
1293
+ if (type === ParamType$1.FloatOptional) {
1294
+ output[name] = undefined;
1295
+ }
1296
+ else {
1297
+ return handleError(res, {
1298
+ message: `Parameter ${name} is required, but it was not included.`,
1299
+ code: ReactKitErrorCode$1.MissingParameter,
1300
+ status: 422,
1301
+ });
1302
+ }
1303
+ }
1304
+ else if (!Number.isNaN(Number.parseFloat(String(value)))) {
1305
+ // Value is a number
1306
+ output[name] = Number.parseFloat(String(value));
1307
+ }
1308
+ else {
1309
+ // Issue!
1310
+ return handleError(res, {
1311
+ message: `Request data was malformed: ${name} was not a valid float.`,
1312
+ code: ReactKitErrorCode$1.InvalidParameter,
1313
+ status: 422,
1314
+ });
1315
+ }
1316
+ }
1317
+ else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
1318
+ // Int
1319
+ // Handle case where value doesn't exist
1320
+ if (value === undefined) {
1321
+ if (type === ParamType$1.IntOptional) {
1322
+ output[name] = undefined;
1323
+ }
1324
+ else {
1325
+ return handleError(res, {
1326
+ message: `Parameter ${name} is required, but it was not included.`,
1327
+ code: ReactKitErrorCode$1.MissingParameter,
1328
+ status: 422,
1329
+ });
1330
+ }
1331
+ }
1332
+ else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
1333
+ // Value is a number
1334
+ output[name] = Number.parseInt(String(value), 10);
1335
+ }
1336
+ else {
1337
+ // Issue!
1338
+ return handleError(res, {
1339
+ message: `Request data was malformed: ${name} was not a valid int.`,
1340
+ code: ReactKitErrorCode$1.InvalidParameter,
1341
+ status: 422,
1342
+ });
1343
+ }
1344
+ }
1345
+ else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
1346
+ // Stringified JSON
1347
+ // Handle case where value doesn't exist
1348
+ if (value === undefined) {
1349
+ if (type === ParamType$1.JSONOptional) {
1350
+ output[name] = undefined;
1351
+ }
1352
+ else {
1353
+ return handleError(res, {
1354
+ message: `Parameter ${name} is required, but it was not included.`,
1355
+ code: ReactKitErrorCode$1.MissingParameter,
1356
+ status: 422,
1357
+ });
1358
+ }
1359
+ }
1360
+ else {
1361
+ // Value exists
1362
+ // Parse
1363
+ try {
1364
+ output[name] = JSON.parse(String(value));
1365
+ }
1366
+ catch (err) {
1367
+ return handleError(res, {
1368
+ message: `Request data was malformed: ${name} was not a valid JSON payload.`,
1369
+ code: ReactKitErrorCode$1.InvalidParameter,
1370
+ status: 422,
1371
+ });
1372
+ }
1373
+ }
1374
+ }
1375
+ else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
1376
+ // String
1377
+ // Handle case where value doesn't exist
1378
+ if (value === undefined) {
1379
+ if (type === ParamType$1.StringOptional) {
1380
+ output[name] = undefined;
1381
+ }
1382
+ else {
1383
+ return handleError(res, {
1384
+ message: `Parameter ${name} is required, but it was not included.`,
1385
+ code: ReactKitErrorCode$1.MissingParameter,
1386
+ status: 422,
1387
+ });
1388
+ }
1389
+ }
1390
+ else {
1391
+ // Value exists
1392
+ // Leave as is
1393
+ output[name] = value;
1394
+ }
1395
+ }
1396
+ else {
1397
+ // No valid data type
1398
+ return handleError(res, {
1399
+ message: `An internal error occurred: we could not determine the type of ${name}.`,
1400
+ code: ReactKitErrorCode$1.InvalidParameter,
1401
+ status: 422,
1402
+ });
1403
+ }
1404
+ }
1405
+ /*----------------------------------------*/
1406
+ /* Launch Info */
1407
+ /*----------------------------------------*/
1408
+ // Get launch info
1409
+ const { launched, launchInfo } = cacclGetLaunchInfo(req);
1410
+ if (!launched || !launchInfo) {
1411
+ return handleError(res, {
1412
+ message: 'Your session has expired. Please refresh the page and try again.',
1413
+ code: ReactKitErrorCode$1.SessionExpired,
1414
+ status: 440,
1415
+ });
1416
+ }
1417
+ // Error if user info cannot be found
1418
+ if (!launchInfo.userId
1419
+ || !launchInfo.userFirstName
1420
+ || !launchInfo.userLastName
1421
+ || (launchInfo.notInCourse
1422
+ && !launchInfo.isAdmin)
1423
+ || (!launchInfo.isTTM
1424
+ && !launchInfo.isLearner
1425
+ && !launchInfo.isAdmin)) {
1426
+ return handleError(res, {
1427
+ message: 'Your session was invalid. Please refresh the page and try again.',
1428
+ code: ReactKitErrorCode$1.SessionExpired,
1429
+ status: 440,
1430
+ });
1431
+ }
1432
+ // Add launch info to output
1433
+ output.userId = launchInfo.userId;
1434
+ output.userFirstName = launchInfo.userFirstName;
1435
+ output.userLastName = launchInfo.userLastName;
1436
+ output.isLearner = !!launchInfo.isLearner;
1437
+ output.isTTM = !!launchInfo.isTTM;
1438
+ output.isAdmin = !!launchInfo.isAdmin;
1439
+ output.isWatchingInPrivate = !!(req.session.isWatchingInPrivate);
1440
+ /*----------------------------------------*/
1441
+ /* Require Course Consistency */
1442
+ /*----------------------------------------*/
1443
+ // Make sure the user actually launched from the appropriate course
1444
+ if (output.courseId
1445
+ && launchInfo.courseId
1446
+ && output.courseId !== launchInfo.courseId
1447
+ && !output.isTTM
1448
+ && !output.isAdmin) {
1449
+ // Course of interest is not the launch course
1450
+ return handleError(res, {
1451
+ message: 'You switched sessions by opening Immersive Classroom in another tab. Please refresh the page and try again.',
1452
+ code: ReactKitErrorCode$1.WrongCourse,
1453
+ status: 401,
1454
+ });
1455
+ }
1456
+ /*------------------------------------------------------------------------*/
1457
+ /* Call handler */
1458
+ /*------------------------------------------------------------------------*/
1459
+ handler({
1460
+ params: output,
1461
+ handleSuccess: (body) => {
1462
+ return handleSuccess(res, body);
1463
+ },
1464
+ handleError: (error) => {
1465
+ return handleError(res, error);
1466
+ },
1467
+ req,
1468
+ res,
1469
+ });
1470
+ });
1471
+ };
1472
+
1473
+ export { AppWrapper, ErrorBox, ErrorWithCode, LoadingSpinner, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, ParamType$1 as ParamType, ReactKitErrorCode$1 as ReactKitErrorCode, TabBox, Variant$1 as Variant, abbreviate, alert$1 as alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, handleError, handleSuccess, padDecimalZeros, padZerosLeft, roundToNumDecimals, showFatalError, sum, visitServerEndpoint, waitMs };
1049
1474
  //# sourceMappingURL=index.js.map