studiokit-scaffolding-js 5.2.0-next.2.6 → 5.2.0-next.2.8

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.
@@ -46,6 +46,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
46
46
  var react_1 = __importStar(require("react"));
47
47
  var react_router_dom_1 = require("react-router-dom");
48
48
  var modelStatus_1 = require("../../constants/modelStatus");
49
+ var types_1 = require("../../types");
49
50
  /**
50
51
  * This component exists as a child to CollectionItemComponent to
51
52
  * redirect the user to a 404/500 page if errors occur while fetching data.
@@ -71,13 +72,13 @@ function modelErrorRedirectComponent(WrappedComponent) {
71
72
  model._metadata &&
72
73
  model._metadata.hasError &&
73
74
  model._metadata.lastFetchErrorData &&
74
- model._metadata.lastFetchErrorData.status === 404;
75
+ model._metadata.lastFetchErrorData.status === types_1.HTTP_STATUS_CODE.NOT_FOUND;
75
76
  var errorRoute = previousModelStatus === modelStatus_1.MODEL_STATUS.UNINITIALIZED &&
76
77
  modelStatus === modelStatus_1.MODEL_STATUS.ERROR &&
77
78
  model._metadata &&
78
79
  model._metadata.hasError &&
79
80
  model._metadata.lastFetchErrorData &&
80
- model._metadata.lastFetchErrorData.status === 500;
81
+ model._metadata.lastFetchErrorData.status === types_1.HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR;
81
82
  if (notFoundRoute) {
82
83
  var pathname = window.location.pathname;
83
84
  return (react_1.default.createElement(react_router_dom_1.Redirect, { to: {
@@ -96,7 +96,7 @@ export declare class UserRoles extends Component<UserRolesProps, UserRolesState>
96
96
  textForRole: (role: string, lowercase?: boolean) => string;
97
97
  singularArticleForRole: (role: string) => string;
98
98
  addUserRoles: (identifiers: string[], role: string) => void;
99
- didAdd: (data?: FetchErrorData | AddBusinessModel | undefined) => void;
99
+ didAdd: (data: AddBusinessModel | FetchErrorData | null) => void;
100
100
  updateUserRole: (userRoleToUpdate: UserRole, newRole: string) => void;
101
101
  didUpdate: (isSuccess: boolean) => void;
102
102
  alertRemoveUserRole: (userRoleToRemove: UserRole) => void;
@@ -43,6 +43,7 @@ var effects_1 = require("redux-saga/effects");
43
43
  var codeProviderService_1 = require("../../services/codeProviderService");
44
44
  var ticketProviderService_1 = require("../../services/ticketProviderService");
45
45
  var tokenPersistenceService_1 = require("../../services/tokenPersistenceService");
46
+ var types_1 = require("../../types");
46
47
  var logger_1 = require("../../utils/logger");
47
48
  var actions_1 = require("../actions");
48
49
  //#region Helpers
@@ -138,7 +139,8 @@ function getTokenFromRefreshToken(oauthTokenParam) {
138
139
  // any error response
139
140
  if (fetchErrorAction) {
140
141
  // ignore server errors
141
- if (((_b = fetchErrorAction.errorData) === null || _b === void 0 ? void 0 : _b.status) && fetchErrorAction.errorData.status >= 500) {
142
+ if (((_b = fetchErrorAction.errorData) === null || _b === void 0 ? void 0 : _b.status) &&
143
+ fetchErrorAction.errorData.status >= types_1.HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR) {
142
144
  return [2 /*return*/, oauthTokenParam];
143
145
  }
144
146
  return [2 /*return*/, null];
@@ -285,7 +287,7 @@ function handleAuthFailure(action) {
285
287
  return __generator(this, function (_a) {
286
288
  switch (_a.label) {
287
289
  case 0:
288
- if (!(oauthToken && action.errorData && action.errorData.status === 401)) return [3 /*break*/, 2];
290
+ if (!(oauthToken && action.errorData && action.errorData.status === types_1.HTTP_STATUS_CODE.UNAUTHORIZED)) return [3 /*break*/, 2];
289
291
  logger.debug('token expired - refreshing');
290
292
  return [4 /*yield*/, effects_1.call(performTokenRefresh)];
291
293
  case 1:
@@ -308,8 +308,10 @@ function modelFetch(modelFetchRequestAction) {
308
308
  // log to the console
309
309
  logger.error(error);
310
310
  didFail = true;
311
- // Do not continue to retry if the response is between 400-500 (except 408: server timeout)
312
- if (errorData.status >= 400 && errorData.status < 500 && errorData.status !== 408) {
311
+ // Do not continue to retry if the response is between 400-500 (except 408: request timeout)
312
+ if (errorData.status >= types_1.HTTP_STATUS_CODE.BAD_REQUEST &&
313
+ errorData.status < types_1.HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR &&
314
+ errorData.status !== types_1.HTTP_STATUS_CODE.REQUEST_TIMEOUT) {
313
315
  tryCount = tryLimit;
314
316
  }
315
317
  if (!(tryCount < tryLimit)) return [3 /*break*/, 16];
@@ -340,7 +342,7 @@ function modelFetch(modelFetchRequestAction) {
340
342
  // dispatch that the fetch failed, which updates `_metadata` (and `guid`, if provided) at the target redux `modelPath`
341
343
  _a.sent();
342
344
  // Send error to error handler, except for 401s
343
- if (errorData.status !== 401) {
345
+ if (errorData.status !== types_1.HTTP_STATUS_CODE.UNAUTHORIZED) {
344
346
  errorHandler(error, modelFetchRequestAction, fetchConfig, lastFetchResult, errorData);
345
347
  }
346
348
  _a.label = 20;
@@ -1,18 +1,18 @@
1
1
  import { SagaIterator } from '@redux-saga/core';
2
2
  import { AnyAction } from 'redux';
3
+ import { FetchErrorData } from '../../types';
3
4
  import { ModelFetchRequestAction } from '../actions';
4
- declare type HookFunction = (input: any) => void;
5
+ export declare type HookFunction<T = any> = (input: T | FetchErrorData | null) => void;
5
6
  export declare const matchesNoStoreAction: (incomingAction: AnyAction) => incomingAction is ModelFetchRequestAction;
6
7
  export declare const matchesFailedNoStoreHookAction: (incomingAction: AnyAction, noStoreAction: ModelFetchRequestAction) => boolean;
7
8
  export declare const takeMatchesFailedNoStoreHookAction: (noStoreAction: ModelFetchRequestAction) => (incomingAction: AnyAction) => boolean;
8
9
  export declare const matchesReceivedNoStoreHookAction: (incomingAction: AnyAction, noStoreAction: ModelFetchRequestAction) => boolean;
9
10
  export declare const takeMatchesReceivedNoStoreHookAction: (noStoreAction: ModelFetchRequestAction) => (incomingAction: AnyAction) => boolean;
10
- export declare const registerNoStoreActionHook: (key: string, hook: HookFunction) => void;
11
+ export declare const registerNoStoreActionHook: <T>(key: string, hook: HookFunction<T>) => void;
11
12
  export declare const unregisterNoStoreActionHook: (key: string) => void;
12
13
  export declare const noStoreHooks: {
13
- registerNoStoreActionHook: (key: string, hook: HookFunction) => void;
14
+ registerNoStoreActionHook: <T>(key: string, hook: HookFunction<T>) => void;
14
15
  unregisterNoStoreActionHook: (key: string) => void;
15
16
  };
16
17
  export declare function handleAction(noStoreAction: ModelFetchRequestAction): SagaIterator;
17
18
  export default function noStoreSaga(): SagaIterator;
18
- export {};
@@ -89,15 +89,15 @@ function handleAction(noStoreAction) {
89
89
  if (lodash_1.isNil(hook)) {
90
90
  return [2 /*return*/];
91
91
  }
92
- if (errorAction === null || errorAction === void 0 ? void 0 : errorAction.errorData) {
92
+ // return `errorData` on failure. modelFetchSaga will ALWAYS create `errorData`
93
+ if (errorAction) {
93
94
  hook(errorAction.errorData);
94
95
  return [2 /*return*/];
95
96
  }
96
- if (!(resultAction === null || resultAction === void 0 ? void 0 : resultAction.data)) {
97
- hook(null);
98
- return [2 /*return*/];
99
- }
100
- hook(resultAction.data);
97
+ // return `data` or `null` on a successful fetch result
98
+ // `data` can technically be `undefined`, e.g. if the server response is a NoContent (204),
99
+ // so the caller will need to determine if `null` is treated as an error or success
100
+ hook((resultAction === null || resultAction === void 0 ? void 0 : resultAction.data) || null);
101
101
  return [2 /*return*/];
102
102
  }
103
103
  });
@@ -30,6 +30,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
30
30
  exports.sendFetch = exports.constructPath = exports.getApiRoot = exports.setApiRoot = void 0;
31
31
  var lodash_1 = require("lodash");
32
32
  var effects_1 = require("redux-saga/effects");
33
+ var types_1 = require("../types");
33
34
  var error_1 = require("../utils/error");
34
35
  var apiRoot;
35
36
  /**
@@ -138,7 +139,7 @@ function sendFetch(config) {
138
139
  isResponseJson = !!response_1.headers &&
139
140
  response_1.headers.has('Content-Type') &&
140
141
  isContentTypeJson(response_1.headers.get('Content-Type'));
141
- if (!(response_1.status === 204)) return [3 /*break*/, 3];
142
+ if (!(response_1.status === types_1.HTTP_STATUS_CODE.NO_CONTENT)) return [3 /*break*/, 3];
142
143
  result.data = isBodyJson ? config.body : undefined;
143
144
  return [3 /*break*/, 8];
144
145
  case 3:
@@ -5,6 +5,7 @@ export declare enum HTTP_STATUS_CODE {
5
5
  BAD_REQUEST = 400,
6
6
  UNAUTHORIZED = 401,
7
7
  FORBIDDEN = 403,
8
+ NOT_FOUND = 404,
8
9
  REQUEST_TIMEOUT = 408,
9
10
  INTERNAL_SERVER_ERROR = 500
10
11
  }
@@ -9,6 +9,7 @@ var HTTP_STATUS_CODE;
9
9
  HTTP_STATUS_CODE[HTTP_STATUS_CODE["BAD_REQUEST"] = 400] = "BAD_REQUEST";
10
10
  HTTP_STATUS_CODE[HTTP_STATUS_CODE["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
11
11
  HTTP_STATUS_CODE[HTTP_STATUS_CODE["FORBIDDEN"] = 403] = "FORBIDDEN";
12
+ HTTP_STATUS_CODE[HTTP_STATUS_CODE["NOT_FOUND"] = 404] = "NOT_FOUND";
12
13
  HTTP_STATUS_CODE[HTTP_STATUS_CODE["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
13
14
  HTTP_STATUS_CODE[HTTP_STATUS_CODE["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
14
15
  })(HTTP_STATUS_CODE = exports.HTTP_STATUS_CODE || (exports.HTTP_STATUS_CODE = {}));
@@ -19,6 +19,7 @@ var quill_1 = __importDefault(require("quill"));
19
19
  var uuid_1 = require("uuid");
20
20
  var actionCreator_1 = require("../redux/actionCreator");
21
21
  var noStoreSaga_1 = require("../redux/sagas/noStoreSaga");
22
+ var types_1 = require("../types");
22
23
  var Delta = quill_1.default.import('delta');
23
24
  /**
24
25
  * Check for non-public in the provided quill contents. Update the ImageValue of an op if an image is found to be
@@ -31,40 +32,53 @@ var checkForNonPublicImages = function (quill, checkedImages) {
31
32
  var _a, _b;
32
33
  (_b = (_a = quill
33
34
  .getContents()) === null || _a === void 0 ? void 0 : _a.ops // Is the op an image and have we not seen it before?
34
- ) === null || _b === void 0 ? void 0 : _b.filter(function (op) { var _a; return ((_a = op.insert) === null || _a === void 0 ? void 0 : _a.image) && !checkedImages.find(function (image) { return image.src === op.insert.image.src; }); }).forEach(function (op) {
35
+ ) === null || _b === void 0 ? void 0 : _b.filter(function (op) {
36
+ return op.insert.image &&
37
+ !checkedImages.find(function (image) { return image.src === op.insert.image.src; }) &&
38
+ op.insert.image.src.startsWith('http');
39
+ }).forEach(function (op) {
35
40
  // Check if the image is public. Do not check data:image urls
36
- if (op.insert.image.src && op.insert.image.src.startsWith('http')) {
37
- // Use the backend to check if the image is public
38
- // Due to CSP and other security measures, we can't check this in the frontend
39
- // uuidv5 is a deterministic uuid generator based on a namespace and a name
40
- // uuidv5.URL is the namespace for urls
41
- var hookId_1 = uuid_1.v5(op.insert.image.src, uuid_1.v5.URL);
42
- noStoreSaga_1.noStoreHooks.registerNoStoreActionHook(hookId_1, function (data) {
43
- noStoreSaga_1.noStoreHooks.unregisterNoStoreActionHook(hookId_1);
44
- // Backend fetches the URL. If there is no data returned or if the data returned has headers
45
- // but those headers are not of image type (i.e. it was a 302 that, when followed returned HTML
46
- // (I'm looking at you, Brightspace), then we update the image insert op to be nonPublic
47
- if (!data || (data['Content-Type'] && !data['Content-Type'][0].startsWith('image'))) {
48
- var contents = quill.getContents();
49
- var ops = contents.map(function (o) {
50
- var _a, _b;
51
- return ((_a = o.insert.image) === null || _a === void 0 ? void 0 : _a.src) && ((_b = op.insert.image) === null || _b === void 0 ? void 0 : _b.src) && o.insert.image.src === op.insert.image.src
52
- ? __assign(__assign({}, o), { insert: __assign(__assign({}, o.insert), { image: __assign(__assign({}, o.insert.image), { nonPublic: true }) }) }) : o;
53
- });
54
- var updatedContents = contents.diff(new Delta(ops));
55
- quill.updateContents(updatedContents);
56
- }
57
- });
58
- // This endpoint returns headers for a url. If the url is nonexistent, it will return null
59
- actionCreator_1.dispatchModelFetchRequest({
60
- modelName: 'urlChecker',
61
- guid: hookId_1,
62
- noStore: true,
63
- queryParams: {
64
- url: op.insert.image.src
65
- }
66
- });
67
- }
41
+ // Use the backend to check if the image is public
42
+ // Due to CSP and other security measures, we can't check this in the frontend
43
+ // uuidv5 is a deterministic uuid generator based on a namespace and a name
44
+ // uuidv5.URL is the namespace for urls
45
+ var hookId = uuid_1.v5(op.insert.image.src, uuid_1.v5.URL);
46
+ noStoreSaga_1.noStoreHooks.registerNoStoreActionHook(hookId, function (data) {
47
+ noStoreSaga_1.noStoreHooks.unregisterNoStoreActionHook(hookId);
48
+ var contentTypeKey;
49
+ /**
50
+ * API fetches the URL, and `noStoreSaga` returns either the result or error
51
+ * * `data` is `null` when the API could not fetch the URL with a successful status code, and returns `null` => `nonPublic`
52
+ * * `data` is `FetchErrorData` if the API request itself failed, e.g. from some unknown error, we don't know if the image is public => do not update
53
+ * * `data` is a dictionary of headers when the API does fetch the URL
54
+ * * if there is no `Content-Type` header, we don't know if the image is public => do not update
55
+ * * if there is a `Content-Type` header, but is NOT an image type (i.e. it was a 302 that, when followed returned HTML)
56
+ * (I'm looking at you, Brightspace) => `nonPublic`
57
+ * * if there is a `Content-Type` header, and it is an image type, then the image is public => do not update
58
+ */
59
+ if (!data ||
60
+ (!types_1.isFetchErrorData(data) &&
61
+ (contentTypeKey = Object.keys(data).find(function (k) { return k.toLowerCase() === 'content-type'; })) &&
62
+ !data[contentTypeKey][0].startsWith('image'))) {
63
+ var contents = quill.getContents();
64
+ var ops = contents.map(function (o) {
65
+ var _a;
66
+ return ((_a = o.insert.image) === null || _a === void 0 ? void 0 : _a.src) && op.insert.image.src && o.insert.image.src === op.insert.image.src
67
+ ? __assign(__assign({}, o), { insert: __assign(__assign({}, o.insert), { image: __assign(__assign({}, o.insert.image), { nonPublic: true }) }) }) : o;
68
+ });
69
+ var updatedContents = contents.diff(new Delta(ops));
70
+ quill.updateContents(updatedContents);
71
+ }
72
+ });
73
+ // This endpoint returns headers for a url. If the url is nonexistent, it will return null
74
+ actionCreator_1.dispatchModelFetchRequest({
75
+ modelName: 'urlChecker',
76
+ guid: hookId,
77
+ noStore: true,
78
+ queryParams: {
79
+ url: op.insert.image.src
80
+ }
81
+ });
68
82
  });
69
83
  };
70
84
  exports.checkForNonPublicImages = checkForNonPublicImages;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "studiokit-scaffolding-js",
3
- "version": "5.2.0-next.2.6",
3
+ "version": "5.2.0-next.2.8",
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",