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

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,410 @@ 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 opts object containing all arguments
1234
+ * @param opts.paramTypes map containing the types for each parameter that is
1235
+ * included in the request (map: param name => type)
1236
+ * @param opts.handler function that processes the request
1237
+ * @returns express route handler that takes the following arguments:
1238
+ * params (map: param name => value), handleSuccess (function for handling
1239
+ * successful requests), handleError (function for handling failed requests),
1240
+ * req (express request object), res (express response object)
1241
+ */
1242
+ const genRouteHandler = (opts) => {
1243
+ // Return a route handler
1244
+ return (req, res) => __awaiter(void 0, void 0, void 0, function* () {
1245
+ var _a;
1246
+ // Output params
1247
+ const output = {};
1248
+ /*----------------------------------------*/
1249
+ /* Parse Params */
1250
+ /*----------------------------------------*/
1251
+ // Process items one by one
1252
+ const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
1253
+ for (let i = 0; i < paramList.length; i++) {
1254
+ const [name, type] = paramList[i];
1255
+ // Find the value as a string
1256
+ const value = (req.params[name]
1257
+ || req.query[name]
1258
+ || req.body[name]);
1259
+ // Parse
1260
+ if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
1261
+ // Boolean
1262
+ // Handle case where value doesn't exist
1263
+ if (value === undefined) {
1264
+ if (type === ParamType$1.BooleanOptional) {
1265
+ output[name] = undefined;
1266
+ }
1267
+ else {
1268
+ return handleError(res, {
1269
+ message: `Parameter ${name} is required, but it was not included.`,
1270
+ code: ReactKitErrorCode$1.MissingParameter,
1271
+ status: 422,
1272
+ });
1273
+ }
1274
+ }
1275
+ else {
1276
+ // Value exists
1277
+ // Simplify value
1278
+ const simpleVal = (String(value)
1279
+ .trim()
1280
+ .toLowerCase());
1281
+ // Parse
1282
+ output[name] = ([
1283
+ 'true',
1284
+ 'yes',
1285
+ 'y',
1286
+ '1',
1287
+ 't',
1288
+ ].indexOf(simpleVal) >= 0);
1289
+ }
1290
+ }
1291
+ else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
1292
+ // Float
1293
+ // Handle case where value doesn't exist
1294
+ if (value === undefined) {
1295
+ if (type === ParamType$1.FloatOptional) {
1296
+ output[name] = undefined;
1297
+ }
1298
+ else {
1299
+ return handleError(res, {
1300
+ message: `Parameter ${name} is required, but it was not included.`,
1301
+ code: ReactKitErrorCode$1.MissingParameter,
1302
+ status: 422,
1303
+ });
1304
+ }
1305
+ }
1306
+ else if (!Number.isNaN(Number.parseFloat(String(value)))) {
1307
+ // Value is a number
1308
+ output[name] = Number.parseFloat(String(value));
1309
+ }
1310
+ else {
1311
+ // Issue!
1312
+ return handleError(res, {
1313
+ message: `Request data was malformed: ${name} was not a valid float.`,
1314
+ code: ReactKitErrorCode$1.InvalidParameter,
1315
+ status: 422,
1316
+ });
1317
+ }
1318
+ }
1319
+ else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
1320
+ // Int
1321
+ // Handle case where value doesn't exist
1322
+ if (value === undefined) {
1323
+ if (type === ParamType$1.IntOptional) {
1324
+ output[name] = undefined;
1325
+ }
1326
+ else {
1327
+ return handleError(res, {
1328
+ message: `Parameter ${name} is required, but it was not included.`,
1329
+ code: ReactKitErrorCode$1.MissingParameter,
1330
+ status: 422,
1331
+ });
1332
+ }
1333
+ }
1334
+ else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
1335
+ // Value is a number
1336
+ output[name] = Number.parseInt(String(value), 10);
1337
+ }
1338
+ else {
1339
+ // Issue!
1340
+ return handleError(res, {
1341
+ message: `Request data was malformed: ${name} was not a valid int.`,
1342
+ code: ReactKitErrorCode$1.InvalidParameter,
1343
+ status: 422,
1344
+ });
1345
+ }
1346
+ }
1347
+ else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
1348
+ // Stringified JSON
1349
+ // Handle case where value doesn't exist
1350
+ if (value === undefined) {
1351
+ if (type === ParamType$1.JSONOptional) {
1352
+ output[name] = undefined;
1353
+ }
1354
+ else {
1355
+ return handleError(res, {
1356
+ message: `Parameter ${name} is required, but it was not included.`,
1357
+ code: ReactKitErrorCode$1.MissingParameter,
1358
+ status: 422,
1359
+ });
1360
+ }
1361
+ }
1362
+ else {
1363
+ // Value exists
1364
+ // Parse
1365
+ try {
1366
+ output[name] = JSON.parse(String(value));
1367
+ }
1368
+ catch (err) {
1369
+ return handleError(res, {
1370
+ message: `Request data was malformed: ${name} was not a valid JSON payload.`,
1371
+ code: ReactKitErrorCode$1.InvalidParameter,
1372
+ status: 422,
1373
+ });
1374
+ }
1375
+ }
1376
+ }
1377
+ else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
1378
+ // String
1379
+ // Handle case where value doesn't exist
1380
+ if (value === undefined) {
1381
+ if (type === ParamType$1.StringOptional) {
1382
+ output[name] = undefined;
1383
+ }
1384
+ else {
1385
+ return handleError(res, {
1386
+ message: `Parameter ${name} is required, but it was not included.`,
1387
+ code: ReactKitErrorCode$1.MissingParameter,
1388
+ status: 422,
1389
+ });
1390
+ }
1391
+ }
1392
+ else {
1393
+ // Value exists
1394
+ // Leave as is
1395
+ output[name] = value;
1396
+ }
1397
+ }
1398
+ else {
1399
+ // No valid data type
1400
+ return handleError(res, {
1401
+ message: `An internal error occurred: we could not determine the type of ${name}.`,
1402
+ code: ReactKitErrorCode$1.InvalidParameter,
1403
+ status: 422,
1404
+ });
1405
+ }
1406
+ }
1407
+ /*----------------------------------------*/
1408
+ /* Launch Info */
1409
+ /*----------------------------------------*/
1410
+ // Get launch info
1411
+ const { launched, launchInfo } = cacclGetLaunchInfo(req);
1412
+ if (!launched || !launchInfo) {
1413
+ return handleError(res, {
1414
+ message: 'Your session has expired. Please refresh the page and try again.',
1415
+ code: ReactKitErrorCode$1.SessionExpired,
1416
+ status: 440,
1417
+ });
1418
+ }
1419
+ // Error if user info cannot be found
1420
+ if (!launchInfo.userId
1421
+ || !launchInfo.userFirstName
1422
+ || !launchInfo.userLastName
1423
+ || (launchInfo.notInCourse
1424
+ && !launchInfo.isAdmin)
1425
+ || (!launchInfo.isTTM
1426
+ && !launchInfo.isLearner
1427
+ && !launchInfo.isAdmin)) {
1428
+ return handleError(res, {
1429
+ message: 'Your session was invalid. Please refresh the page and try again.',
1430
+ code: ReactKitErrorCode$1.SessionExpired,
1431
+ status: 440,
1432
+ });
1433
+ }
1434
+ // Add launch info to output
1435
+ output.userId = launchInfo.userId;
1436
+ output.userFirstName = launchInfo.userFirstName;
1437
+ output.userLastName = launchInfo.userLastName;
1438
+ output.isLearner = !!launchInfo.isLearner;
1439
+ output.isTTM = !!launchInfo.isTTM;
1440
+ output.isAdmin = !!launchInfo.isAdmin;
1441
+ output.isWatchingInPrivate = !!(req.session.isWatchingInPrivate);
1442
+ /*----------------------------------------*/
1443
+ /* Require Course Consistency */
1444
+ /*----------------------------------------*/
1445
+ // Make sure the user actually launched from the appropriate course
1446
+ if (output.courseId
1447
+ && launchInfo.courseId
1448
+ && output.courseId !== launchInfo.courseId
1449
+ && !output.isTTM
1450
+ && !output.isAdmin) {
1451
+ // Course of interest is not the launch course
1452
+ return handleError(res, {
1453
+ message: 'You switched sessions by opening Immersive Classroom in another tab. Please refresh the page and try again.',
1454
+ code: ReactKitErrorCode$1.WrongCourse,
1455
+ status: 401,
1456
+ });
1457
+ }
1458
+ /*------------------------------------------------------------------------*/
1459
+ /* Call handler */
1460
+ /*------------------------------------------------------------------------*/
1461
+ opts.handler({
1462
+ params: output,
1463
+ handleSuccess: (body) => {
1464
+ return handleSuccess(res, body);
1465
+ },
1466
+ handleError: (error) => {
1467
+ return handleError(res, error);
1468
+ },
1469
+ req,
1470
+ res,
1471
+ });
1472
+ });
1473
+ };
1474
+
1475
+ 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
1476
  //# sourceMappingURL=index.js.map