studiokit-scaffolding-js 4.5.12 → 4.6.0-next.1.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.
@@ -0,0 +1,4 @@
1
+ import { FunctionComponent } from 'react';
2
+ import { RouteComponentProps } from 'react-router';
3
+ declare const Error: FunctionComponent<RouteComponentProps>;
4
+ export default Error;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var query_string_1 = require("query-string");
7
+ var react_1 = __importDefault(require("react"));
8
+ var react_bootstrap_1 = require("react-bootstrap");
9
+ var react_helmet_1 = __importDefault(require("react-helmet"));
10
+ var lockDownBrowser_1 = require("../utils/lockDownBrowser");
11
+ var ExitButton_1 = require("./LockDownBrowser/ExitButton");
12
+ var Error = function (props) {
13
+ var message = query_string_1.parse(props.location.search).message;
14
+ var lockDownBrowserInfo = lockDownBrowser_1.getLockDownBrowserInfo();
15
+ return (react_1.default.createElement(react_1.default.Fragment, null,
16
+ react_1.default.createElement(react_helmet_1.default, { title: "Error" }),
17
+ react_1.default.createElement(react_bootstrap_1.Container, null,
18
+ react_1.default.createElement(react_bootstrap_1.Row, null,
19
+ react_1.default.createElement(react_bootstrap_1.Col, { md: 8 },
20
+ react_1.default.createElement("h1", null, "Uh oh!"),
21
+ message && react_1.default.createElement("blockquote", null, message),
22
+ react_1.default.createElement("p", null, " Sorry, but it looks like something went wrong while processing your request."),
23
+ react_1.default.createElement("p", null,
24
+ "If you have any questions or concerns please contact us at",
25
+ ' ',
26
+ react_1.default.createElement("a", { href: "mailto:tlt@purdue.edu" }, "tlt@purdue.edu")),
27
+ lockDownBrowserInfo.isClientLockDownBrowser && react_1.default.createElement(ExitButton_1.LockDownBrowserExitButton, null))))));
28
+ };
29
+ exports.default = Error;
@@ -0,0 +1,3 @@
1
+ import { FunctionComponent } from 'react';
2
+ declare const LockDownBrowserCheck: FunctionComponent;
3
+ export default LockDownBrowserCheck;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var Cancel_1 = __importDefault(require("@material-ui/icons/Cancel"));
7
+ var CheckCircle_1 = __importDefault(require("@material-ui/icons/CheckCircle"));
8
+ var react_1 = __importDefault(require("react"));
9
+ var react_bootstrap_1 = require("react-bootstrap");
10
+ var react_helmet_1 = require("react-helmet");
11
+ var lockDownBrowser_1 = require("../../utils/lockDownBrowser");
12
+ var ExitButton_1 = require("./ExitButton");
13
+ var LockDownBrowserCheck = function () {
14
+ var lockDownBrowserInfo = lockDownBrowser_1.getLockDownBrowserInfo();
15
+ return (react_1.default.createElement(react_1.default.Fragment, null,
16
+ react_1.default.createElement(react_helmet_1.Helmet, { title: "LockDown Browser Check" }),
17
+ react_1.default.createElement(react_bootstrap_1.Container, { className: "main-content" },
18
+ react_1.default.createElement(react_bootstrap_1.Row, null,
19
+ react_1.default.createElement(react_bootstrap_1.Col, { xs: 12 },
20
+ react_1.default.createElement("h1", null, lockDownBrowserInfo.isClientLockDownBrowser ? (react_1.default.createElement(react_1.default.Fragment, null,
21
+ react_1.default.createElement(CheckCircle_1.default, { className: "fill-green f1 mr2" }),
22
+ " Success! You are using LockDown Browser.")) : (react_1.default.createElement(react_1.default.Fragment, null,
23
+ react_1.default.createElement(Cancel_1.default, { className: "fill-red f1 mr2" }),
24
+ "Oops! You are not using LockDown Browser."))),
25
+ lockDownBrowserInfo.isClientLockDownBrowser && (react_1.default.createElement(react_1.default.Fragment, null,
26
+ react_1.default.createElement("p", null, "You\u2019re all set to use LockDown Browser for your assessments. You can close LockDown Browser when you\u2019re ready."),
27
+ react_1.default.createElement("dl", { className: "f6" },
28
+ lockDownBrowserInfo.buildId && (react_1.default.createElement(react_1.default.Fragment, null,
29
+ react_1.default.createElement("dt", null, "Build Id"),
30
+ react_1.default.createElement("dd", null, lockDownBrowserInfo.buildId))),
31
+ react_1.default.createElement("dt", null, "Platform"),
32
+ react_1.default.createElement("dd", null, lockDownBrowserInfo.buildDateWindows
33
+ ? 'Windows'
34
+ : lockDownBrowserInfo.buildDateMac
35
+ ? 'macOS'
36
+ : 'iPad'),
37
+ react_1.default.createElement("dt", null, "Build Date"),
38
+ react_1.default.createElement("dd", null, lockDownBrowserInfo.buildDateWindows ||
39
+ lockDownBrowserInfo.buildDateMac ||
40
+ lockDownBrowserInfo.buildDateIpad)),
41
+ react_1.default.createElement(ExitButton_1.LockDownBrowserExitButton, null))))))));
42
+ };
43
+ exports.default = LockDownBrowserCheck;
@@ -0,0 +1,2 @@
1
+ import { FunctionComponent } from 'react';
2
+ export declare const LockDownBrowserExitButton: FunctionComponent;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LockDownBrowserExitButton = void 0;
7
+ var Button_1 = __importDefault(require("@material-ui/core/Button"));
8
+ var react_1 = __importDefault(require("react"));
9
+ var react_router_1 = require("react-router");
10
+ var constants_1 = require("../../constants");
11
+ var LockDownBrowserExitButton = function () {
12
+ var history = react_router_1.useHistory();
13
+ var location = react_router_1.useLocation();
14
+ return (react_1.default.createElement(Button_1.default, { className: "btn-primary mb4", onClick: function () {
15
+ return history.push({
16
+ pathname: location.pathname,
17
+ search: "?" + constants_1.LOCKDOWN_BROWSER_KEY.EXIT_BROWSER + "=" + constants_1.LOCKDOWN_BROWSER_TRUE
18
+ });
19
+ } }, "Close LockDown Browser"));
20
+ };
21
+ exports.LockDownBrowserExitButton = LockDownBrowserExitButton;
@@ -0,0 +1,4 @@
1
+ import { FunctionComponent } from 'react';
2
+ import { RouteComponentProps } from 'react-router';
3
+ declare const LockDownBrowserLaunch: FunctionComponent<RouteComponentProps>;
4
+ export default LockDownBrowserLaunch;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var query_string_1 = require("query-string");
7
+ var react_1 = __importDefault(require("react"));
8
+ var react_2 = require("react");
9
+ var react_helmet_1 = require("react-helmet");
10
+ var constants_1 = require("../../constants");
11
+ var lockDownBrowser_1 = require("../../utils/lockDownBrowser");
12
+ var Loading_1 = __importDefault(require("../Loading"));
13
+ var errorPath = "/error?" + query_string_1.stringify({ message: constants_1.LOCKDOWN_BROWSER_LAUNCH_ERROR });
14
+ var LockDownBrowserLaunch = function (props) {
15
+ var search = query_string_1.parse(props.location.search);
16
+ var redirectPath = search.redirectPath;
17
+ var reloadCount = search.reloadCount ? parseInt("" + search.reloadCount) : 0;
18
+ // this effect will run once after this component renders
19
+ // allow LockDown Browser to set its cookies
20
+ react_2.useEffect(function () {
21
+ var lockDownBrowserInfo = lockDownBrowser_1.getLockDownBrowserInfo();
22
+ // redirect path is required and must be a single string
23
+ if (!redirectPath || typeof redirectPath !== 'string') {
24
+ console.error('redirectPath is undefined');
25
+ window.location.href = errorPath;
26
+ return;
27
+ }
28
+ // if LDB cookies are set, redirect to the final path
29
+ if (lockDownBrowserInfo.isClientLockDownBrowser) {
30
+ window.location.href = redirectPath;
31
+ return;
32
+ }
33
+ // increment reloadCount and reload the current url fully
34
+ // try 3 times before going to the error route
35
+ if (reloadCount < 2) {
36
+ console.info('could not determine if client is LockDown Browser, reloading');
37
+ search.reloadCount = "" + reloadCount++;
38
+ window.location.href =
39
+ window.location.protocol +
40
+ '//' +
41
+ window.location.host +
42
+ window.location.pathname +
43
+ '?' +
44
+ query_string_1.stringify(search);
45
+ return;
46
+ }
47
+ window.location.href = errorPath;
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ }, []);
50
+ return (react_1.default.createElement(react_1.default.Fragment, null,
51
+ react_1.default.createElement(react_helmet_1.Helmet, { title: "LockDown Browser Launch" }),
52
+ react_1.default.createElement(Loading_1.default, null)));
53
+ };
54
+ exports.default = LockDownBrowserLaunch;
@@ -6,3 +6,4 @@ export * from './notificationType';
6
6
  export * from './baseRole';
7
7
  export * from './shard';
8
8
  export * from './tier';
9
+ export * from './lockDownBrowser';
@@ -18,3 +18,4 @@ __exportStar(require("./notificationType"), exports);
18
18
  __exportStar(require("./baseRole"), exports);
19
19
  __exportStar(require("./shard"), exports);
20
20
  __exportStar(require("./tier"), exports);
21
+ __exportStar(require("./lockDownBrowser"), exports);
@@ -0,0 +1,20 @@
1
+ export declare enum LOCKDOWN_BROWSER_KEY {
2
+ /** Cookie set by LDB to claim it is LDB. */
3
+ CLIENT_IS_LOCK_DOWN_BROWSER = "rldbci",
4
+ /** Cookie set by LDB containing the client build identifier. */
5
+ BUILD_ID = "rldbid",
6
+ /** Cookie set by LDB containing the build date, if the client is on windows. */
7
+ BUILD_DATE_WINDOWS = "rldbbdw",
8
+ /** Cookie set by LDB containing the build date, if the client is on mac. */
9
+ BUILD_DATE_MAC = "rldbbdm",
10
+ /** Cookie set by LDB containing the build date, if the client is on iPad. */
11
+ BUILD_DATE_IPAD = "rldbbdi",
12
+ /** Query param to direct LDB to close itself */
13
+ EXIT_BROWSER = "rldbxb"
14
+ }
15
+ export declare const LOCKDOWN_BROWSER_TRUE = "1";
16
+ export declare enum LOCKDOWN_BROWSER_PATH {
17
+ LAUNCH = "ldb-launch",
18
+ CHECK = "ldb-check"
19
+ }
20
+ export declare const LOCKDOWN_BROWSER_LAUNCH_ERROR = "A LockDown Browser session could not be started. Please close the browser and try again.";
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOCKDOWN_BROWSER_LAUNCH_ERROR = exports.LOCKDOWN_BROWSER_PATH = exports.LOCKDOWN_BROWSER_TRUE = exports.LOCKDOWN_BROWSER_KEY = void 0;
4
+ var LOCKDOWN_BROWSER_KEY;
5
+ (function (LOCKDOWN_BROWSER_KEY) {
6
+ /** Cookie set by LDB to claim it is LDB. */
7
+ LOCKDOWN_BROWSER_KEY["CLIENT_IS_LOCK_DOWN_BROWSER"] = "rldbci";
8
+ /** Cookie set by LDB containing the client build identifier. */
9
+ LOCKDOWN_BROWSER_KEY["BUILD_ID"] = "rldbid";
10
+ /** Cookie set by LDB containing the build date, if the client is on windows. */
11
+ LOCKDOWN_BROWSER_KEY["BUILD_DATE_WINDOWS"] = "rldbbdw";
12
+ /** Cookie set by LDB containing the build date, if the client is on mac. */
13
+ LOCKDOWN_BROWSER_KEY["BUILD_DATE_MAC"] = "rldbbdm";
14
+ /** Cookie set by LDB containing the build date, if the client is on iPad. */
15
+ LOCKDOWN_BROWSER_KEY["BUILD_DATE_IPAD"] = "rldbbdi";
16
+ /** Query param to direct LDB to close itself */
17
+ LOCKDOWN_BROWSER_KEY["EXIT_BROWSER"] = "rldbxb";
18
+ })(LOCKDOWN_BROWSER_KEY = exports.LOCKDOWN_BROWSER_KEY || (exports.LOCKDOWN_BROWSER_KEY = {}));
19
+ exports.LOCKDOWN_BROWSER_TRUE = '1';
20
+ var LOCKDOWN_BROWSER_PATH;
21
+ (function (LOCKDOWN_BROWSER_PATH) {
22
+ LOCKDOWN_BROWSER_PATH["LAUNCH"] = "ldb-launch";
23
+ LOCKDOWN_BROWSER_PATH["CHECK"] = "ldb-check";
24
+ })(LOCKDOWN_BROWSER_PATH = exports.LOCKDOWN_BROWSER_PATH || (exports.LOCKDOWN_BROWSER_PATH = {}));
25
+ exports.LOCKDOWN_BROWSER_LAUNCH_ERROR = 'A LockDown Browser session could not be started. Please close the browser and try again.';
@@ -0,0 +1,2 @@
1
+ /// <reference types="connected-react-router" />
2
+ export default function lockDownBrowserSaga(runIndefinitely?: boolean): Generator<import("redux-saga/effects").TakeEffect | import("redux-saga/effects").PutEffect<import("connected-react-router").CallHistoryMethodAction<[import("history").LocationDescriptorObject<unknown>]>>, void, unknown>;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __generator = (this && this.__generator) || function (thisArg, body) {
3
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
4
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
5
+ function verb(n) { return function (v) { return step([n, v]); }; }
6
+ function step(op) {
7
+ if (f) throw new TypeError("Generator is already executing.");
8
+ while (_) try {
9
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
10
+ if (y = 0, t) op = [op[0] & 2, t.value];
11
+ switch (op[0]) {
12
+ case 0: case 1: t = op; break;
13
+ case 4: _.label++; return { value: op[1], done: false };
14
+ case 5: _.label++; y = op[1]; op = [0]; continue;
15
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
16
+ default:
17
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
18
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
19
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
20
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
21
+ if (t[2]) _.ops.pop();
22
+ _.trys.pop(); continue;
23
+ }
24
+ op = body.call(thisArg, _);
25
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
26
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
27
+ }
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ var connected_react_router_1 = require("connected-react-router");
31
+ var query_string_1 = require("query-string");
32
+ var effects_1 = require("redux-saga/effects");
33
+ var studiokit_net_js_1 = require("studiokit-net-js");
34
+ function lockDownBrowserSaga(runIndefinitely) {
35
+ var _loop_1;
36
+ if (runIndefinitely === void 0) { runIndefinitely = true; }
37
+ return __generator(this, function (_a) {
38
+ switch (_a.label) {
39
+ case 0:
40
+ _loop_1 = function () {
41
+ var message;
42
+ return __generator(this, function (_a) {
43
+ switch (_a.label) {
44
+ case 0:
45
+ message = '';
46
+ // wait for any LDB error
47
+ return [4 /*yield*/, effects_1.take(function (action) {
48
+ var _a;
49
+ message = (_a = action === null || action === void 0 ? void 0 : action.errorData) === null || _a === void 0 ? void 0 : _a.message;
50
+ return (action === null || action === void 0 ? void 0 : action.type) === studiokit_net_js_1.NET_ACTION.TRY_FETCH_FAILED && !!(message === null || message === void 0 ? void 0 : message.includes('LockDown Browser'));
51
+ })
52
+ // redirect to known LDB error route
53
+ ];
54
+ case 1:
55
+ // wait for any LDB error
56
+ _a.sent();
57
+ // redirect to known LDB error route
58
+ return [4 /*yield*/, effects_1.put(connected_react_router_1.push({
59
+ pathname: '/error',
60
+ search: query_string_1.stringify({ message: message })
61
+ }))];
62
+ case 2:
63
+ // redirect to known LDB error route
64
+ _a.sent();
65
+ return [2 /*return*/];
66
+ }
67
+ });
68
+ };
69
+ _a.label = 1;
70
+ case 1: return [5 /*yield**/, _loop_1()];
71
+ case 2:
72
+ _a.sent();
73
+ _a.label = 3;
74
+ case 3:
75
+ if (runIndefinitely) return [3 /*break*/, 1];
76
+ _a.label = 4;
77
+ case 4: return [2 /*return*/];
78
+ }
79
+ });
80
+ }
81
+ exports.default = lockDownBrowserSaga;
@@ -76,6 +76,7 @@ var configurationSaga_1 = __importDefault(require("./configurationSaga"));
76
76
  var errorSaga_1 = __importDefault(require("./errorSaga"));
77
77
  var identityProviderSaga_1 = __importDefault(require("./identityProviderSaga"));
78
78
  var initialDataLoadSaga_1 = __importDefault(require("./initialDataLoadSaga"));
79
+ var lockDownBrowserErrorSaga_1 = __importDefault(require("./lockDownBrowserErrorSaga"));
79
80
  var postLoginDataSaga_1 = __importDefault(require("./postLoginDataSaga"));
80
81
  var postLoginRedirectSaga_1 = __importDefault(require("./postLoginRedirectSaga"));
81
82
  var sentrySaga_1 = __importDefault(require("./sentrySaga"));
@@ -89,7 +90,7 @@ exports.setOtherDependentSagas = setOtherDependentSagas;
89
90
  function dependentSagas() {
90
91
  return __generator(this, function (_a) {
91
92
  switch (_a.label) {
92
- case 0: return [4 /*yield*/, effects_1.all(__assign({ noStoreSaga: studiokit_net_js_1.sagas.noStoreSaga(), configurationSaga: configurationSaga_1.default(), caliperSaga: caliperSaga_1.default(), sentrySaga: sentrySaga_1.default(), initialDataLoadSaga: initialDataLoadSaga_1.default(), identityProviderSaga: identityProviderSaga_1.default(), postLoginRedirectSaga: postLoginRedirectSaga_1.default(), postLoginDataSaga: postLoginDataSaga_1.default(), clockOffsetSaga: clockOffsetSaga_1.default() }, otherDependentSagas))];
93
+ case 0: return [4 /*yield*/, effects_1.all(__assign({ noStoreSaga: studiokit_net_js_1.sagas.noStoreSaga(), configurationSaga: configurationSaga_1.default(), caliperSaga: caliperSaga_1.default(), sentrySaga: sentrySaga_1.default(), initialDataLoadSaga: initialDataLoadSaga_1.default(), identityProviderSaga: identityProviderSaga_1.default(), postLoginRedirectSaga: postLoginRedirectSaga_1.default(), postLoginDataSaga: postLoginDataSaga_1.default(), clockOffsetSaga: clockOffsetSaga_1.default(), lockDownBrowserErrorSaga: lockDownBrowserErrorSaga_1.default() }, otherDependentSagas))];
93
94
  case 1:
94
95
  _a.sent();
95
96
  return [2 /*return*/];
@@ -0,0 +1,2 @@
1
+ /** Get the contents of `document.cookie` as a Record with string keys and values. */
2
+ export declare const getCookies: () => Record<string, string>;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCookies = void 0;
4
+ /** Get the contents of `document.cookie` as a Record with string keys and values. */
5
+ var getCookies = function () {
6
+ var cookies = {};
7
+ if (document.cookie && document.cookie !== '') {
8
+ var cookieStrings = document.cookie.split(';');
9
+ cookieStrings.forEach(function (cookieString) {
10
+ var cookieParts = cookieString.split('=');
11
+ var name = decodeURIComponent(cookieParts[0].replace(/^ /, ''));
12
+ var value = decodeURIComponent(cookieParts[1]);
13
+ cookies[name] = value;
14
+ });
15
+ }
16
+ return cookies;
17
+ };
18
+ exports.getCookies = getCookies;
@@ -0,0 +1,8 @@
1
+ export interface LockDownBrowserInfo {
2
+ isClientLockDownBrowser: boolean;
3
+ buildId?: string;
4
+ buildDateWindows?: string;
5
+ buildDateMac?: string;
6
+ buildDateIpad?: string;
7
+ }
8
+ export declare const getLockDownBrowserInfo: () => LockDownBrowserInfo;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLockDownBrowserInfo = void 0;
4
+ var lockDownBrowser_1 = require("../constants/lockDownBrowser");
5
+ var cookies_1 = require("./cookies");
6
+ var getLockDownBrowserInfo = function () {
7
+ var cookies = cookies_1.getCookies();
8
+ return {
9
+ isClientLockDownBrowser: cookies[lockDownBrowser_1.LOCKDOWN_BROWSER_KEY.CLIENT_IS_LOCK_DOWN_BROWSER] === lockDownBrowser_1.LOCKDOWN_BROWSER_TRUE,
10
+ buildId: cookies[lockDownBrowser_1.LOCKDOWN_BROWSER_KEY.BUILD_ID],
11
+ buildDateWindows: cookies[lockDownBrowser_1.LOCKDOWN_BROWSER_KEY.BUILD_DATE_WINDOWS],
12
+ buildDateMac: cookies[lockDownBrowser_1.LOCKDOWN_BROWSER_KEY.BUILD_DATE_MAC],
13
+ buildDateIpad: cookies[lockDownBrowser_1.LOCKDOWN_BROWSER_KEY.BUILD_DATE_IPAD]
14
+ };
15
+ };
16
+ exports.getLockDownBrowserInfo = getLockDownBrowserInfo;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "studiokit-scaffolding-js",
3
- "version": "4.5.12",
3
+ "version": "4.6.0-next.1.2",
4
4
  "description": "Common scaffolding for Studio apps at Purdue",
5
5
  "repository": "https://gitlab.com/purdue-informatics/studiokit/studiokit-scaffolding-js",
6
6
  "license": "MIT",