dce-reactkit 3.12.3 → 4.0.0-beta.1
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/.vscode/settings.json +1 -3
- package/README.md +20 -0
- package/dist/cjs/index.js +156 -1792
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ItemPicker/index.d.ts +0 -1
- package/dist/cjs/types/components/Modal/ModalProps.d.ts +0 -2
- package/dist/cjs/types/components/TabBox.d.ts +0 -1
- package/dist/cjs/types/index.d.ts +8 -12
- package/dist/cjs/types/types/ParamType.d.ts +10 -10
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +1 -15
- package/dist/esm/index.js +157 -1785
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ItemPicker/index.d.ts +0 -1
- package/dist/esm/types/components/Modal/ModalProps.d.ts +0 -2
- package/dist/esm/types/components/TabBox.d.ts +0 -1
- package/dist/esm/types/index.d.ts +8 -12
- package/dist/esm/types/types/ParamType.d.ts +10 -10
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +1 -15
- package/dist/index.d.ts +122 -351
- package/genEncodedSecret.ts +84 -0
- package/package.json +7 -5
- package/rollup.config.js +2 -0
- package/src/components/AppWrapper.tsx +3 -1
- package/src/components/CheckboxButton.tsx +1 -1
- package/src/components/Dropdown.tsx +1 -1
- package/src/components/IntelliTable.tsx +1 -1
- package/src/components/ItemPicker/NestableItemList.tsx +0 -1
- package/src/components/ItemPicker/index.tsx +0 -96
- package/src/components/Modal/ModalProps.ts +0 -4
- package/src/components/Modal/index.tsx +21 -60
- package/src/components/RadioButton.tsx +3 -1
- package/src/components/TabBox.tsx +9 -27
- package/src/{client → helpers}/initClient.tsx +5 -1
- package/src/helpers/logClientEvent.tsx +3 -1
- package/src/helpers/visitServerEndpoint.tsx +1 -1
- package/src/index.ts +12 -22
- package/src/types/ParamType.ts +10 -10
- package/src/types/ReactKitErrorCode.tsx +1 -17
- package/dist/cjs/types/helpers/addDBEditorEndpoints.d.ts +0 -41
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +0 -76
- package/dist/cjs/types/helpers/getWordCount.d.ts +0 -10
- package/dist/cjs/types/helpers/handleError.d.ts +0 -18
- package/dist/cjs/types/helpers/handleSuccess.d.ts +0 -8
- package/dist/cjs/types/helpers/parseUserAgent.d.ts +0 -17
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -24
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -33
- package/dist/cjs/types/server/initLogCollection.d.ts +0 -8
- package/dist/cjs/types/server/initServer.d.ts +0 -42
- package/dist/esm/types/helpers/addDBEditorEndpoints.d.ts +0 -41
- package/dist/esm/types/helpers/genRouteHandler.d.ts +0 -76
- package/dist/esm/types/helpers/getWordCount.d.ts +0 -10
- package/dist/esm/types/helpers/handleError.d.ts +0 -18
- package/dist/esm/types/helpers/handleSuccess.d.ts +0 -8
- package/dist/esm/types/helpers/parseUserAgent.d.ts +0 -17
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -24
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -33
- package/dist/esm/types/server/initLogCollection.d.ts +0 -8
- package/dist/esm/types/server/initServer.d.ts +0 -42
- package/src/components/ItemPicker/index.test.tsx +0 -783
- package/src/helpers/addDBEditorEndpoints.ts +0 -128
- package/src/helpers/genRouteHandler.ts +0 -948
- package/src/helpers/getWordCount.ts +0 -26
- package/src/helpers/handleError.ts +0 -65
- package/src/helpers/handleSuccess.ts +0 -18
- package/src/helpers/parseUserAgent.ts +0 -108
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +0 -93
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +0 -164
- package/src/server/initLogCollection.ts +0 -27
- package/src/server/initServer.ts +0 -287
- /package/dist/cjs/types/{client → helpers}/initClient.d.ts +0 -0
- /package/dist/esm/types/{client → helpers}/initClient.d.ts +0 -0
|
@@ -1,948 +0,0 @@
|
|
|
1
|
-
// Import caccl functions
|
|
2
|
-
// TODO: fix dependency cycle
|
|
3
|
-
// eslint-disable-next-line import/no-cycle
|
|
4
|
-
import {
|
|
5
|
-
cacclGetLaunchInfo,
|
|
6
|
-
internalGetLogCollection,
|
|
7
|
-
} from '../server/initServer';
|
|
8
|
-
|
|
9
|
-
// Import shared types
|
|
10
|
-
import ReactKitErrorCode from '../types/ReactKitErrorCode';
|
|
11
|
-
import ParamType from '../types/ParamType';
|
|
12
|
-
|
|
13
|
-
// Import helpers
|
|
14
|
-
import handleError from './handleError';
|
|
15
|
-
import handleSuccess from './handleSuccess';
|
|
16
|
-
import genErrorPage from '../html/genErrorPage';
|
|
17
|
-
import genInfoPage from '../html/genInfoPage';
|
|
18
|
-
import parseUserAgent from './parseUserAgent';
|
|
19
|
-
import getTimeInfoInET from './getTimeInfoInET';
|
|
20
|
-
|
|
21
|
-
// Import shared types
|
|
22
|
-
import LogFunction from '../types/LogFunction';
|
|
23
|
-
import Log from '../types/Log';
|
|
24
|
-
import LogType from '../types/LogType';
|
|
25
|
-
import LogSource from '../types/LogSource';
|
|
26
|
-
import LogTypeSpecificInfo from '../types/Log/LogTypeSpecificInfo';
|
|
27
|
-
import LogMainInfo from '../types/Log/LogMainInfo';
|
|
28
|
-
import LogSourceSpecificInfo from '../types/Log/LogSourceSpecificInfo';
|
|
29
|
-
import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
|
|
30
|
-
import LogAction from '../types/LogAction';
|
|
31
|
-
import LogLevel from '../types/LogLevel';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Generate an express API route handler
|
|
35
|
-
* @author Gabe Abrams
|
|
36
|
-
* @param opts object containing all arguments
|
|
37
|
-
* @param opts.paramTypes map containing the types for each parameter that is
|
|
38
|
-
* included in the request (map: param name => type)
|
|
39
|
-
* @param opts.handler function that processes the request
|
|
40
|
-
* @param [opts.skipSessionCheck] if true, skip the session check (allow users
|
|
41
|
-
* to not be logged in and launched via LTI)
|
|
42
|
-
* @param [opts.allowedHosts] if included, only allow requests from these hosts
|
|
43
|
-
* (start a hostname with a "*" to only check the end of the hostname)
|
|
44
|
-
* you can include just one string instead of an array
|
|
45
|
-
* @param [opts.bannedHosts] if included, do not allow requests from these hosts
|
|
46
|
-
* (start a hostname with a "*" to only check the end of the hostname)
|
|
47
|
-
* you can include just one string instead of an array
|
|
48
|
-
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
49
|
-
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
50
|
-
* where the error message is prefixed with this string. For example,
|
|
51
|
-
* if unhandledErrorMessagePrefix is
|
|
52
|
-
* 'While saving progress, we encountered an error:'
|
|
53
|
-
* and the error is 'progressInfo is not an object',
|
|
54
|
-
* the client will receive an error with the message
|
|
55
|
-
* 'While saving progress, we encountered an error: progressInfo is not an object'
|
|
56
|
-
* @returns express route handler that takes the following arguments:
|
|
57
|
-
* params (map: param name => value),
|
|
58
|
-
* req (express request object),
|
|
59
|
-
* next (express next function),
|
|
60
|
-
* send (a function that sends a string to the client),
|
|
61
|
-
* redirect (takes a url and redirects the user to that url),
|
|
62
|
-
* renderErrorPage (shows a static error page to the user),
|
|
63
|
-
* renderInfoPage (shows a static info page to the user),
|
|
64
|
-
* renderCustomHTML (renders custom html and sends it to the user),
|
|
65
|
-
* and returns the value to send to the client as a JSON API response, or
|
|
66
|
-
* calls next() or redirect(...) or send(...) or renderErrorPage(...).
|
|
67
|
-
* Note: params also has userId, userFirstName,
|
|
68
|
-
* userLastName, userEmail, userAvatarURL, isLearner, isTTM, isAdmin,
|
|
69
|
-
* and any other variables that
|
|
70
|
-
* are directly added to the session, if the user does have a session.
|
|
71
|
-
*/
|
|
72
|
-
const genRouteHandler = (
|
|
73
|
-
opts: {
|
|
74
|
-
paramTypes?: {
|
|
75
|
-
[k: string]: ParamType
|
|
76
|
-
},
|
|
77
|
-
handler: (
|
|
78
|
-
opts: {
|
|
79
|
-
params: {
|
|
80
|
-
[k: string]: any
|
|
81
|
-
},
|
|
82
|
-
req: any,
|
|
83
|
-
next: () => void,
|
|
84
|
-
redirect: (pathOrURL: string) => void,
|
|
85
|
-
send: (text: string, status?: number) => void,
|
|
86
|
-
renderErrorPage: (
|
|
87
|
-
opts?: {
|
|
88
|
-
title?: string,
|
|
89
|
-
description?: string,
|
|
90
|
-
code?: string,
|
|
91
|
-
pageTitle?: string,
|
|
92
|
-
status?: number,
|
|
93
|
-
},
|
|
94
|
-
) => void,
|
|
95
|
-
renderInfoPage: (
|
|
96
|
-
opts: {
|
|
97
|
-
title: string,
|
|
98
|
-
body: string,
|
|
99
|
-
},
|
|
100
|
-
) => void,
|
|
101
|
-
renderCustomHTML: (
|
|
102
|
-
opts: {
|
|
103
|
-
html: string,
|
|
104
|
-
status?: number,
|
|
105
|
-
},
|
|
106
|
-
) => void,
|
|
107
|
-
logServerEvent: LogFunction,
|
|
108
|
-
},
|
|
109
|
-
) => any,
|
|
110
|
-
skipSessionCheck?: boolean,
|
|
111
|
-
allowedHosts?: string[] | string,
|
|
112
|
-
bannedHosts?: string[] | string,
|
|
113
|
-
unhandledErrorMessagePrefix?: string,
|
|
114
|
-
},
|
|
115
|
-
) => {
|
|
116
|
-
// Return a route handler
|
|
117
|
-
return async (req: any, res: any, next: () => void) => {
|
|
118
|
-
// Output params
|
|
119
|
-
const output: { [k in string]: any } = {};
|
|
120
|
-
|
|
121
|
-
/*----------------------------------------*/
|
|
122
|
-
/* ----------- Hostname Check ----------- */
|
|
123
|
-
/*----------------------------------------*/
|
|
124
|
-
|
|
125
|
-
// Get hostnames
|
|
126
|
-
const originURL = String(
|
|
127
|
-
req.get('origin')
|
|
128
|
-
|| req.headers.origin
|
|
129
|
-
|| req.headers.referer,
|
|
130
|
-
);
|
|
131
|
-
const originHostname = (
|
|
132
|
-
originURL
|
|
133
|
-
// Remove protocol
|
|
134
|
-
.replace(/(^\w+:|^)\/\//, '')
|
|
135
|
-
// Remove port
|
|
136
|
-
.replace(/:\d+$/, '')
|
|
137
|
-
);
|
|
138
|
-
const serverHostname = String(req.hostname);
|
|
139
|
-
|
|
140
|
-
// Check allowed
|
|
141
|
-
if (opts.allowedHosts) {
|
|
142
|
-
// Only accept requests from allowed hosts
|
|
143
|
-
const allowedArray = (
|
|
144
|
-
Array.isArray(opts.allowedHosts)
|
|
145
|
-
? opts.allowedHosts
|
|
146
|
-
: [opts.allowedHosts]
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
// Check if server is localhost
|
|
150
|
-
if (serverHostname === 'localhost') {
|
|
151
|
-
// Allow localhost
|
|
152
|
-
allowedArray.push('localhost');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Check if current host is allowed
|
|
156
|
-
const allowed = allowedArray.some((allowedHost) => {
|
|
157
|
-
if (allowedHost.startsWith('*')) {
|
|
158
|
-
// Check end of hostname
|
|
159
|
-
return originHostname.endsWith(allowedHost.substring(1));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Check full hostname
|
|
163
|
-
return originHostname.toLowerCase() === allowedHost.toLowerCase();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// If not allowed, return error
|
|
167
|
-
if (!allowed) {
|
|
168
|
-
return handleError(
|
|
169
|
-
res,
|
|
170
|
-
{
|
|
171
|
-
message: 'You are not allowed to access this endpoint.',
|
|
172
|
-
code: ReactKitErrorCode.HostNotAllowed,
|
|
173
|
-
status: 403,
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Check banned
|
|
180
|
-
if (opts.bannedHosts) {
|
|
181
|
-
// Do not allow requests from banned hosts
|
|
182
|
-
const bannedArray = (
|
|
183
|
-
Array.isArray(opts.bannedHosts)
|
|
184
|
-
? opts.bannedHosts
|
|
185
|
-
: [opts.bannedHosts]
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
// Check if current host is banned
|
|
189
|
-
const banned = bannedArray.some((bannedHost) => {
|
|
190
|
-
if (bannedHost.startsWith('*')) {
|
|
191
|
-
// Check end of hostname
|
|
192
|
-
return originHostname.endsWith(bannedHost.substring(1));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Check full hostname
|
|
196
|
-
return originHostname.toLowerCase() === bannedHost.toLowerCase();
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// If banned, return error
|
|
200
|
-
if (banned) {
|
|
201
|
-
return handleError(
|
|
202
|
-
res,
|
|
203
|
-
{
|
|
204
|
-
message: 'You are not allowed to access this endpoint.',
|
|
205
|
-
code: ReactKitErrorCode.HostBanned,
|
|
206
|
-
status: 403,
|
|
207
|
-
},
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/*----------------------------------------*/
|
|
213
|
-
/* ------------ Parse Params ------------ */
|
|
214
|
-
/*----------------------------------------*/
|
|
215
|
-
|
|
216
|
-
// Process items one by one
|
|
217
|
-
const paramList = Object.entries(opts.paramTypes ?? {});
|
|
218
|
-
for (let i = 0; i < paramList.length; i++) {
|
|
219
|
-
const [name, type] = paramList[i];
|
|
220
|
-
|
|
221
|
-
// Find the value as a string
|
|
222
|
-
const value = (
|
|
223
|
-
req.params[name]
|
|
224
|
-
|| req.query[name]
|
|
225
|
-
|| req.body[name]
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
// Parse
|
|
229
|
-
if (type === ParamType.Boolean || type === ParamType.BooleanOptional) {
|
|
230
|
-
// Boolean
|
|
231
|
-
|
|
232
|
-
// Handle case where value doesn't exist
|
|
233
|
-
if (value === undefined) {
|
|
234
|
-
if (type === ParamType.BooleanOptional) {
|
|
235
|
-
output[name] = undefined;
|
|
236
|
-
} else {
|
|
237
|
-
return handleError(
|
|
238
|
-
res,
|
|
239
|
-
{
|
|
240
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
241
|
-
code: ReactKitErrorCode.MissingParameter,
|
|
242
|
-
status: 422,
|
|
243
|
-
},
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
// Value exists
|
|
248
|
-
|
|
249
|
-
// Simplify value
|
|
250
|
-
const simpleVal = (
|
|
251
|
-
String(value)
|
|
252
|
-
.trim()
|
|
253
|
-
.toLowerCase()
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
// Parse
|
|
257
|
-
output[name] = (
|
|
258
|
-
[
|
|
259
|
-
'true',
|
|
260
|
-
'yes',
|
|
261
|
-
'y',
|
|
262
|
-
'1',
|
|
263
|
-
't',
|
|
264
|
-
].indexOf(simpleVal) >= 0
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
} else if (type === ParamType.Float || type === ParamType.FloatOptional) {
|
|
268
|
-
// Float
|
|
269
|
-
|
|
270
|
-
// Handle case where value doesn't exist
|
|
271
|
-
if (value === undefined) {
|
|
272
|
-
if (type === ParamType.FloatOptional) {
|
|
273
|
-
output[name] = undefined;
|
|
274
|
-
} else {
|
|
275
|
-
return handleError(
|
|
276
|
-
res,
|
|
277
|
-
{
|
|
278
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
279
|
-
code: ReactKitErrorCode.MissingParameter,
|
|
280
|
-
status: 422,
|
|
281
|
-
},
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
} else if (!Number.isNaN(Number.parseFloat(String(value)))) {
|
|
285
|
-
// Value is a number
|
|
286
|
-
output[name] = Number.parseFloat(String(value));
|
|
287
|
-
} else {
|
|
288
|
-
// Issue!
|
|
289
|
-
return handleError(
|
|
290
|
-
res,
|
|
291
|
-
{
|
|
292
|
-
message: `Request data was malformed: ${name} was not a valid float.`,
|
|
293
|
-
code: ReactKitErrorCode.InvalidParameter,
|
|
294
|
-
status: 422,
|
|
295
|
-
},
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
} else if (type === ParamType.Int || type === ParamType.IntOptional) {
|
|
299
|
-
// Int
|
|
300
|
-
|
|
301
|
-
// Handle case where value doesn't exist
|
|
302
|
-
if (value === undefined) {
|
|
303
|
-
if (type === ParamType.IntOptional) {
|
|
304
|
-
output[name] = undefined;
|
|
305
|
-
} else {
|
|
306
|
-
return handleError(
|
|
307
|
-
res,
|
|
308
|
-
{
|
|
309
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
310
|
-
code: ReactKitErrorCode.MissingParameter,
|
|
311
|
-
status: 422,
|
|
312
|
-
},
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
} else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
|
|
316
|
-
// Value is a number
|
|
317
|
-
output[name] = Number.parseInt(String(value), 10);
|
|
318
|
-
} else {
|
|
319
|
-
// Issue!
|
|
320
|
-
return handleError(
|
|
321
|
-
res,
|
|
322
|
-
{
|
|
323
|
-
message: `Request data was malformed: ${name} was not a valid int.`,
|
|
324
|
-
code: ReactKitErrorCode.InvalidParameter,
|
|
325
|
-
status: 422,
|
|
326
|
-
},
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
} else if (type === ParamType.JSON || type === ParamType.JSONOptional) {
|
|
330
|
-
// Stringified JSON
|
|
331
|
-
|
|
332
|
-
// Handle case where value doesn't exist
|
|
333
|
-
if (value === undefined) {
|
|
334
|
-
if (type === ParamType.JSONOptional) {
|
|
335
|
-
output[name] = undefined;
|
|
336
|
-
} else {
|
|
337
|
-
return handleError(
|
|
338
|
-
res,
|
|
339
|
-
{
|
|
340
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
341
|
-
code: ReactKitErrorCode.MissingParameter,
|
|
342
|
-
status: 422,
|
|
343
|
-
},
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
} else {
|
|
347
|
-
// Value exists
|
|
348
|
-
|
|
349
|
-
// Parse
|
|
350
|
-
try {
|
|
351
|
-
output[name] = JSON.parse(String(value));
|
|
352
|
-
} catch (err) {
|
|
353
|
-
return handleError(
|
|
354
|
-
res,
|
|
355
|
-
{
|
|
356
|
-
message: `Request data was malformed: ${name} was not a valid JSON payload.`,
|
|
357
|
-
code: ReactKitErrorCode.InvalidParameter,
|
|
358
|
-
status: 422,
|
|
359
|
-
},
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
} else if (type === ParamType.String || type === ParamType.StringOptional) {
|
|
364
|
-
// String
|
|
365
|
-
|
|
366
|
-
// Handle case where value doesn't exist
|
|
367
|
-
if (value === undefined) {
|
|
368
|
-
if (type === ParamType.StringOptional) {
|
|
369
|
-
output[name] = undefined;
|
|
370
|
-
} else {
|
|
371
|
-
return handleError(
|
|
372
|
-
res,
|
|
373
|
-
{
|
|
374
|
-
message: `Parameter ${name} is required, but it was not included.`,
|
|
375
|
-
code: ReactKitErrorCode.MissingParameter,
|
|
376
|
-
status: 422,
|
|
377
|
-
},
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
} else {
|
|
381
|
-
// Value exists
|
|
382
|
-
|
|
383
|
-
// Leave as is
|
|
384
|
-
output[name] = value;
|
|
385
|
-
}
|
|
386
|
-
} else {
|
|
387
|
-
// No valid data type
|
|
388
|
-
return handleError(
|
|
389
|
-
res,
|
|
390
|
-
{
|
|
391
|
-
message: `An internal error occurred: we could not determine the type of ${name}.`,
|
|
392
|
-
code: ReactKitErrorCode.InvalidParameter,
|
|
393
|
-
status: 422,
|
|
394
|
-
},
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/*----------------------------------------*/
|
|
400
|
-
/* ------------- Launch Info ------------ */
|
|
401
|
-
/*----------------------------------------*/
|
|
402
|
-
|
|
403
|
-
// Get launch info
|
|
404
|
-
const { launched, launchInfo } = cacclGetLaunchInfo(req);
|
|
405
|
-
if (
|
|
406
|
-
// Not launched
|
|
407
|
-
(!launched || !launchInfo)
|
|
408
|
-
// Not skipping the session check
|
|
409
|
-
&& !opts.skipSessionCheck
|
|
410
|
-
) {
|
|
411
|
-
return handleError(
|
|
412
|
-
res,
|
|
413
|
-
{
|
|
414
|
-
message: 'Your session has expired. Please refresh the page and try again.',
|
|
415
|
-
code: ReactKitErrorCode.SessionExpired,
|
|
416
|
-
status: 401,
|
|
417
|
-
},
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Error if user info cannot be found
|
|
422
|
-
if (
|
|
423
|
-
// User information is incomplete
|
|
424
|
-
(
|
|
425
|
-
!launchInfo
|
|
426
|
-
|| !launchInfo.userId
|
|
427
|
-
|| !launchInfo.userFirstName
|
|
428
|
-
|| !launchInfo.userLastName
|
|
429
|
-
|| (
|
|
430
|
-
launchInfo.notInCourse
|
|
431
|
-
&& !launchInfo.isAdmin
|
|
432
|
-
)
|
|
433
|
-
|| (
|
|
434
|
-
!launchInfo.isTTM
|
|
435
|
-
&& !launchInfo.isLearner
|
|
436
|
-
&& !launchInfo.isAdmin
|
|
437
|
-
)
|
|
438
|
-
)
|
|
439
|
-
// Not skipping the session check
|
|
440
|
-
&& !opts.skipSessionCheck
|
|
441
|
-
) {
|
|
442
|
-
return handleError(
|
|
443
|
-
res,
|
|
444
|
-
{
|
|
445
|
-
message: 'Your session was invalid. Please refresh the page and try again.',
|
|
446
|
-
code: ReactKitErrorCode.SessionExpired,
|
|
447
|
-
status: 401,
|
|
448
|
-
},
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Add launch info to output
|
|
453
|
-
output.userId = (
|
|
454
|
-
launchInfo
|
|
455
|
-
? launchInfo.userId
|
|
456
|
-
: (output.userId ?? undefined)
|
|
457
|
-
);
|
|
458
|
-
output.userFirstName = (
|
|
459
|
-
launchInfo
|
|
460
|
-
? launchInfo.userFirstName
|
|
461
|
-
: (output.userFirstName ?? undefined)
|
|
462
|
-
);
|
|
463
|
-
output.userLastName = (
|
|
464
|
-
launchInfo
|
|
465
|
-
? launchInfo.userLastName
|
|
466
|
-
: (output.userLastName ?? undefined)
|
|
467
|
-
);
|
|
468
|
-
output.userEmail = (
|
|
469
|
-
launchInfo
|
|
470
|
-
? launchInfo.userEmail
|
|
471
|
-
: (output.userEmail ?? undefined)
|
|
472
|
-
);
|
|
473
|
-
output.userAvatarURL = (
|
|
474
|
-
launchInfo
|
|
475
|
-
? (
|
|
476
|
-
launchInfo.userImage
|
|
477
|
-
?? 'http://www.gravatar.com/avatar/?d=identicon'
|
|
478
|
-
)
|
|
479
|
-
: (output.userAvatarURL ?? undefined)
|
|
480
|
-
);
|
|
481
|
-
output.isLearner = (
|
|
482
|
-
launchInfo
|
|
483
|
-
? !!launchInfo.isLearner
|
|
484
|
-
: (output.isLearner ?? undefined)
|
|
485
|
-
);
|
|
486
|
-
output.isTTM = (
|
|
487
|
-
launchInfo
|
|
488
|
-
? !!launchInfo.isTTM
|
|
489
|
-
: (output.isTTM ?? undefined)
|
|
490
|
-
);
|
|
491
|
-
output.isAdmin = (
|
|
492
|
-
launchInfo
|
|
493
|
-
? !!launchInfo.isAdmin
|
|
494
|
-
: (output.isAdmin ?? undefined)
|
|
495
|
-
);
|
|
496
|
-
output.courseId = (
|
|
497
|
-
launchInfo
|
|
498
|
-
? (output.courseId ?? launchInfo.courseId)
|
|
499
|
-
: (output.courseId ?? undefined)
|
|
500
|
-
);
|
|
501
|
-
output.courseName = (
|
|
502
|
-
launchInfo
|
|
503
|
-
? launchInfo.contextLabel
|
|
504
|
-
: (output.courseName ?? undefined)
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
// Add other session variables
|
|
508
|
-
Object.keys(req.session).forEach((propName) => {
|
|
509
|
-
// Skip if prop already in output
|
|
510
|
-
if (output[propName] !== undefined) {
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Add to output
|
|
515
|
-
const value = req.session[propName];
|
|
516
|
-
if (
|
|
517
|
-
typeof value === 'string'
|
|
518
|
-
|| typeof value === 'boolean'
|
|
519
|
-
|| typeof value === 'number'
|
|
520
|
-
) {
|
|
521
|
-
output[propName] = value;
|
|
522
|
-
}
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
/*----------------------------------------*/
|
|
526
|
-
/* ----- Require Course Consistency ----- */
|
|
527
|
-
/*----------------------------------------*/
|
|
528
|
-
|
|
529
|
-
// Make sure the user actually launched from the appropriate course
|
|
530
|
-
if (
|
|
531
|
-
output.courseId
|
|
532
|
-
&& launchInfo
|
|
533
|
-
&& launchInfo.courseId
|
|
534
|
-
&& output.courseId !== launchInfo.courseId
|
|
535
|
-
&& !output.isTTM
|
|
536
|
-
&& !output.isAdmin
|
|
537
|
-
) {
|
|
538
|
-
// Course of interest is not the launch course
|
|
539
|
-
return handleError(
|
|
540
|
-
res,
|
|
541
|
-
{
|
|
542
|
-
message: 'You switched sessions by opening this app in another tab. Please refresh the page and try again.',
|
|
543
|
-
code: ReactKitErrorCode.WrongCourse,
|
|
544
|
-
status: 401,
|
|
545
|
-
},
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/*----------------------------------------*/
|
|
550
|
-
/* Require Proper Permissions */
|
|
551
|
-
/*----------------------------------------*/
|
|
552
|
-
|
|
553
|
-
// Add TTM endpoint security
|
|
554
|
-
if (
|
|
555
|
-
// This is a TTM endpoint
|
|
556
|
-
req.path.startsWith('/api/ttm')
|
|
557
|
-
// User is not a TTM
|
|
558
|
-
&& (
|
|
559
|
-
// User is not a TTM
|
|
560
|
-
!output.isTTM
|
|
561
|
-
// User is not an admin
|
|
562
|
-
&& !output.isAdmin
|
|
563
|
-
)
|
|
564
|
-
) {
|
|
565
|
-
// User does not have access
|
|
566
|
-
return handleError(
|
|
567
|
-
res,
|
|
568
|
-
{
|
|
569
|
-
message: 'This action is only allowed if you are a teaching team member for the course. Please go back to Canvas, log in as a teaching team member, and try again.',
|
|
570
|
-
code: ReactKitErrorCode.NotTTM,
|
|
571
|
-
status: 401,
|
|
572
|
-
},
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// Add Admin endpoint security
|
|
577
|
-
if (
|
|
578
|
-
// This is an admin endpoint
|
|
579
|
-
req.path.startsWith('/api/admin')
|
|
580
|
-
// User is not an admin
|
|
581
|
-
&& !output.isAdmin
|
|
582
|
-
) {
|
|
583
|
-
// User does not have access
|
|
584
|
-
return handleError(
|
|
585
|
-
res,
|
|
586
|
-
{
|
|
587
|
-
message: 'This action is only allowed if you are a Canvas admin. Please go back to Canvas, log in as an admin, and try again.',
|
|
588
|
-
code: ReactKitErrorCode.NotAdmin,
|
|
589
|
-
status: 401,
|
|
590
|
-
},
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/*----------------------------------------*/
|
|
595
|
-
/* ------------- Log Handler ------------ */
|
|
596
|
-
/*----------------------------------------*/
|
|
597
|
-
|
|
598
|
-
// Create a log handler function
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* Log an event on the server
|
|
602
|
-
* @author Gabe Abrams
|
|
603
|
-
*/
|
|
604
|
-
const logServerEvent: LogFunction = async (logOpts) => {
|
|
605
|
-
// NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
|
|
606
|
-
// that indicates that this is actually a client event, but we don't
|
|
607
|
-
// include that in the LogFunction type because this is internal and
|
|
608
|
-
// hidden from users
|
|
609
|
-
try {
|
|
610
|
-
// Parse user agent
|
|
611
|
-
const {
|
|
612
|
-
browser,
|
|
613
|
-
device,
|
|
614
|
-
} = parseUserAgent(req.headers['user-agent']);
|
|
615
|
-
|
|
616
|
-
// Get time info in ET
|
|
617
|
-
const {
|
|
618
|
-
timestamp,
|
|
619
|
-
year,
|
|
620
|
-
month,
|
|
621
|
-
day,
|
|
622
|
-
hour,
|
|
623
|
-
minute,
|
|
624
|
-
} = getTimeInfoInET();
|
|
625
|
-
|
|
626
|
-
// Main log info
|
|
627
|
-
const mainLogInfo: LogMainInfo = {
|
|
628
|
-
id: `${launchInfo ? launchInfo.userId : 'unknown'}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
|
|
629
|
-
userFirstName: (launchInfo ? launchInfo.userFirstName: 'unknown'),
|
|
630
|
-
userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'),
|
|
631
|
-
userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'),
|
|
632
|
-
userId: (launchInfo ? launchInfo.userId : 'unknown'),
|
|
633
|
-
isLearner: (launchInfo && !!launchInfo.isLearner),
|
|
634
|
-
isAdmin: (launchInfo && !!launchInfo.isAdmin),
|
|
635
|
-
isTTM: (launchInfo && !!launchInfo.isTTM),
|
|
636
|
-
courseId: (launchInfo ? launchInfo.courseId : 'unknown' ),
|
|
637
|
-
courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'),
|
|
638
|
-
browser,
|
|
639
|
-
device,
|
|
640
|
-
year,
|
|
641
|
-
month,
|
|
642
|
-
day,
|
|
643
|
-
hour,
|
|
644
|
-
minute,
|
|
645
|
-
timestamp,
|
|
646
|
-
context: (
|
|
647
|
-
typeof logOpts.context === 'string'
|
|
648
|
-
? logOpts.context
|
|
649
|
-
: (
|
|
650
|
-
((logOpts.context as any) ?? {})._
|
|
651
|
-
?? LogBuiltInMetadata.Context.Uncategorized
|
|
652
|
-
)
|
|
653
|
-
),
|
|
654
|
-
subcontext: (
|
|
655
|
-
logOpts.subcontext
|
|
656
|
-
?? LogBuiltInMetadata.Context.Uncategorized
|
|
657
|
-
),
|
|
658
|
-
tags: (logOpts.tags ?? []),
|
|
659
|
-
level: (logOpts.level ?? LogLevel.Info),
|
|
660
|
-
metadata: (logOpts.metadata ?? {}),
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
// Type-specific info
|
|
664
|
-
const typeSpecificInfo: LogTypeSpecificInfo = (
|
|
665
|
-
('error' in opts && opts.error)
|
|
666
|
-
? {
|
|
667
|
-
type: LogType.Error,
|
|
668
|
-
errorMessage: (logOpts as any).error.message ?? 'Unknown message',
|
|
669
|
-
errorCode: (logOpts as any).error.code ?? ReactKitErrorCode.NoCode,
|
|
670
|
-
errorStack: (logOpts as any).error.stack ?? 'No stack',
|
|
671
|
-
}
|
|
672
|
-
: {
|
|
673
|
-
type: LogType.Action,
|
|
674
|
-
target: (
|
|
675
|
-
(logOpts as any).target
|
|
676
|
-
?? LogBuiltInMetadata.Target.NoTarget
|
|
677
|
-
),
|
|
678
|
-
action: (
|
|
679
|
-
(logOpts as any).action
|
|
680
|
-
?? LogAction.Unknown
|
|
681
|
-
),
|
|
682
|
-
}
|
|
683
|
-
);
|
|
684
|
-
|
|
685
|
-
// Source-specific info
|
|
686
|
-
const sourceSpecificInfo: LogSourceSpecificInfo = (
|
|
687
|
-
(logOpts as any).overrideAsClientEvent
|
|
688
|
-
? {
|
|
689
|
-
source: LogSource.Client,
|
|
690
|
-
}
|
|
691
|
-
: {
|
|
692
|
-
source: LogSource.Server,
|
|
693
|
-
routePath: req.path,
|
|
694
|
-
routeTemplate: req.route.path,
|
|
695
|
-
}
|
|
696
|
-
);
|
|
697
|
-
|
|
698
|
-
// Build log event
|
|
699
|
-
const log: Log = {
|
|
700
|
-
...mainLogInfo,
|
|
701
|
-
...typeSpecificInfo,
|
|
702
|
-
...sourceSpecificInfo,
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
// Either print to console or save to db
|
|
706
|
-
const logCollection = internalGetLogCollection();
|
|
707
|
-
if (logCollection) {
|
|
708
|
-
// Store to the log collection
|
|
709
|
-
await logCollection.insert(log);
|
|
710
|
-
} else if (log.type === LogType.Error) {
|
|
711
|
-
// Print to console
|
|
712
|
-
// eslint-disable-next-line no-console
|
|
713
|
-
console.error('dce-reactkit error log:', log);
|
|
714
|
-
} else {
|
|
715
|
-
// eslint-disable-next-line no-console
|
|
716
|
-
console.log('dce-reactkit action log:', log);
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Return log entry
|
|
720
|
-
return log;
|
|
721
|
-
} catch (err) {
|
|
722
|
-
// Print because we cannot store the error
|
|
723
|
-
// eslint-disable-next-line no-console
|
|
724
|
-
console.error(
|
|
725
|
-
'Could not log the following:',
|
|
726
|
-
logOpts,
|
|
727
|
-
'due to this error:',
|
|
728
|
-
(err as any ?? {}).message,
|
|
729
|
-
(err as any ?? {}).stack,
|
|
730
|
-
);
|
|
731
|
-
|
|
732
|
-
// Create a dummy log to return
|
|
733
|
-
const dummyMainInfo: LogMainInfo = {
|
|
734
|
-
id: '-1',
|
|
735
|
-
userFirstName: 'Unknown',
|
|
736
|
-
userLastName: 'Unknown',
|
|
737
|
-
userEmail: 'unknown@harvard.edu',
|
|
738
|
-
userId: 1,
|
|
739
|
-
isLearner: false,
|
|
740
|
-
isAdmin: false,
|
|
741
|
-
isTTM: false,
|
|
742
|
-
courseId: 1,
|
|
743
|
-
courseName: 'Unknown',
|
|
744
|
-
browser: {
|
|
745
|
-
name: 'Unknown',
|
|
746
|
-
version: 'Unknown',
|
|
747
|
-
},
|
|
748
|
-
device: {
|
|
749
|
-
isMobile: false,
|
|
750
|
-
os: 'Unknown',
|
|
751
|
-
},
|
|
752
|
-
year: 1,
|
|
753
|
-
month: 1,
|
|
754
|
-
day: 1,
|
|
755
|
-
hour: 1,
|
|
756
|
-
minute: 1,
|
|
757
|
-
timestamp: Date.now(),
|
|
758
|
-
tags: [],
|
|
759
|
-
level: LogLevel.Warn,
|
|
760
|
-
metadata: {},
|
|
761
|
-
context: LogBuiltInMetadata.Context.Uncategorized,
|
|
762
|
-
subcontext: LogBuiltInMetadata.Context.Uncategorized,
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
const dummyTypeSpecificInfo: LogTypeSpecificInfo = {
|
|
766
|
-
type: LogType.Error,
|
|
767
|
-
errorMessage: 'Unknown',
|
|
768
|
-
errorCode: 'Unknown',
|
|
769
|
-
errorStack: 'No Stack',
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
const dummySourceSpecificInfo: LogSourceSpecificInfo = {
|
|
773
|
-
source: LogSource.Server,
|
|
774
|
-
routePath: req.path,
|
|
775
|
-
routeTemplate: req.route.path,
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
const log: Log = {
|
|
779
|
-
...dummyMainInfo,
|
|
780
|
-
...dummyTypeSpecificInfo,
|
|
781
|
-
...dummySourceSpecificInfo,
|
|
782
|
-
};
|
|
783
|
-
|
|
784
|
-
return log;
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
|
|
788
|
-
/*------------------------------------------------------------------------*/
|
|
789
|
-
/* Call handler */
|
|
790
|
-
/*------------------------------------------------------------------------*/
|
|
791
|
-
|
|
792
|
-
// Keep track of whether a response was already sent
|
|
793
|
-
let responseSent = false;
|
|
794
|
-
|
|
795
|
-
/**
|
|
796
|
-
* Redirect the user to another path or url
|
|
797
|
-
* @author Gabe Abrams
|
|
798
|
-
* @param pathOrURL the path or url to redirect to
|
|
799
|
-
*/
|
|
800
|
-
const redirect = (pathOrURL: string) => {
|
|
801
|
-
responseSent = true;
|
|
802
|
-
res.redirect(pathOrURL);
|
|
803
|
-
};
|
|
804
|
-
|
|
805
|
-
/**
|
|
806
|
-
* Send text to the client (with an optional status code)
|
|
807
|
-
* @author Gabe Abrams
|
|
808
|
-
* @param text the text to send to the client
|
|
809
|
-
* @parm [status=200] the http status code to send
|
|
810
|
-
*/
|
|
811
|
-
const send = (text: string, status: number = 200) => {
|
|
812
|
-
responseSent = true;
|
|
813
|
-
res.status(status).send(text);
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* Render an error page
|
|
818
|
-
* @author Gabe Abrams
|
|
819
|
-
* @param renderOpts object containing all arguments
|
|
820
|
-
* @param [renderOpts.title=An Error Occurred] title of the error box
|
|
821
|
-
* @param [renderOpts.description=An unknown server error occurred. Please contact support.]
|
|
822
|
-
* a human-readable description of the error
|
|
823
|
-
* @param [renderOpts.code=ReactKitErrorCode.NoCode] error code to show
|
|
824
|
-
* @param [renderOpts.pageTitle=renderOpts.title] title of the page/tab if it differs from
|
|
825
|
-
* the title of the error
|
|
826
|
-
* @param [renderOpts.status=500] http status code
|
|
827
|
-
*/
|
|
828
|
-
const renderErrorPage = (
|
|
829
|
-
renderOpts: {
|
|
830
|
-
title?: string,
|
|
831
|
-
description?: string,
|
|
832
|
-
code?: string,
|
|
833
|
-
pageTitle?: string,
|
|
834
|
-
status?: number,
|
|
835
|
-
} = {},
|
|
836
|
-
) => {
|
|
837
|
-
const html = genErrorPage(renderOpts);
|
|
838
|
-
send(html, renderOpts.status ?? 500);
|
|
839
|
-
|
|
840
|
-
// Log server-side error if not a session expired error or 404
|
|
841
|
-
if (renderOpts.status && renderOpts.status === 404) {
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
if (renderOpts.title?.toLowerCase().includes('session expired')) {
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
logServerEvent({
|
|
848
|
-
context: LogBuiltInMetadata.Context.ServerRenderedErrorPage,
|
|
849
|
-
error: {
|
|
850
|
-
message: `${renderOpts.title}: ${renderOpts.description}`,
|
|
851
|
-
code: renderOpts.code,
|
|
852
|
-
},
|
|
853
|
-
metadata: {
|
|
854
|
-
title: renderOpts.title,
|
|
855
|
-
description: renderOpts.description,
|
|
856
|
-
code: renderOpts.code,
|
|
857
|
-
pageTitle: renderOpts.pageTitle,
|
|
858
|
-
status: renderOpts.status ?? 500,
|
|
859
|
-
},
|
|
860
|
-
});
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* Render an info page
|
|
865
|
-
* @author Gabe Abrams
|
|
866
|
-
* @param renderOpts object containing all arguments
|
|
867
|
-
* @param renderOpts.title title of the info box
|
|
868
|
-
* @param renderOpts.body a human-readable text body for the info alert
|
|
869
|
-
*/
|
|
870
|
-
const renderInfoPage = (
|
|
871
|
-
renderOpts: {
|
|
872
|
-
title: string,
|
|
873
|
-
body: string,
|
|
874
|
-
},
|
|
875
|
-
) => {
|
|
876
|
-
const html = genInfoPage(renderOpts);
|
|
877
|
-
send(html, 200);
|
|
878
|
-
};
|
|
879
|
-
|
|
880
|
-
/**
|
|
881
|
-
* Render custom HTML
|
|
882
|
-
* @author Gabe Abrams
|
|
883
|
-
* @param htmlOpts object containing all arguments
|
|
884
|
-
* @param htmlOpts.html the HTML to send to the client
|
|
885
|
-
* @param [ejsOpts.status=200] the http status code to send
|
|
886
|
-
*/
|
|
887
|
-
const renderCustomHTML = (
|
|
888
|
-
htmlOpts: {
|
|
889
|
-
html: string,
|
|
890
|
-
status?: number,
|
|
891
|
-
},
|
|
892
|
-
) => {
|
|
893
|
-
send(htmlOpts.html, htmlOpts.status ?? 200);
|
|
894
|
-
};
|
|
895
|
-
|
|
896
|
-
// Call the handler
|
|
897
|
-
try {
|
|
898
|
-
const results = await opts.handler({
|
|
899
|
-
params: output,
|
|
900
|
-
req,
|
|
901
|
-
send,
|
|
902
|
-
next: () => {
|
|
903
|
-
responseSent = true;
|
|
904
|
-
next();
|
|
905
|
-
},
|
|
906
|
-
redirect,
|
|
907
|
-
renderErrorPage,
|
|
908
|
-
renderInfoPage,
|
|
909
|
-
renderCustomHTML,
|
|
910
|
-
logServerEvent,
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
// Send results to client (only if next wasn't called)
|
|
914
|
-
if (!responseSent) {
|
|
915
|
-
return handleSuccess(res, results ?? undefined);
|
|
916
|
-
}
|
|
917
|
-
} catch (err) {
|
|
918
|
-
// Prefix error message if needed
|
|
919
|
-
if (
|
|
920
|
-
opts.unhandledErrorMessagePrefix
|
|
921
|
-
&& err instanceof Error
|
|
922
|
-
&& err.message
|
|
923
|
-
&& err.name !== 'ErrorWithCode'
|
|
924
|
-
) {
|
|
925
|
-
err.message = `${opts.unhandledErrorMessagePrefix.trim()} ${err.message.trim()}`;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// Send error to client (only if next wasn't called)
|
|
929
|
-
if (!responseSent) {
|
|
930
|
-
handleError(res, err);
|
|
931
|
-
|
|
932
|
-
// Log server-side error
|
|
933
|
-
logServerEvent({
|
|
934
|
-
context: LogBuiltInMetadata.Context.ServerEndpointError,
|
|
935
|
-
error: err,
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// Log error that was not responded with
|
|
942
|
-
// eslint-disable-next-line no-console
|
|
943
|
-
console.log('Error occurred but could not be sent to client because a response was already sent:', err);
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
export default genRouteHandler;
|