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.
- package/lib/components/HOC/ModelErrorRedirectComponent.js +3 -2
- package/lib/components/UserRoles/index.d.ts +1 -1
- package/lib/redux/sagas/authSaga.js +4 -2
- package/lib/redux/sagas/modelFetchSaga.js +5 -3
- package/lib/redux/sagas/noStoreSaga.d.ts +4 -4
- package/lib/redux/sagas/noStoreSaga.js +6 -6
- package/lib/services/fetchService.js +2 -1
- package/lib/types/net/HTTPStatusCode.d.ts +1 -0
- package/lib/types/net/HTTPStatusCode.js +1 -0
- package/lib/utils/quill.js +47 -33
- package/package.json +1 -1
|
@@ -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 ===
|
|
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 ===
|
|
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
|
|
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) &&
|
|
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 ===
|
|
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:
|
|
312
|
-
if (errorData.status >=
|
|
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 !==
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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 ===
|
|
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:
|
|
@@ -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 = {}));
|
package/lib/utils/quill.js
CHANGED
|
@@ -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) {
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
noStoreSaga_1.noStoreHooks.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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.
|
|
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",
|