dce-reactkit 3.9.2 → 3.9.4-beta-prompt.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/dist/cjs/index.js +184 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AppWrapper.d.ts +24 -0
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +8 -0
- package/dist/cjs/types/index.d.ts +2 -2
- package/dist/cjs/types/types/ModalType.d.ts +1 -0
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +2 -0
- package/dist/esm/index.js +184 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AppWrapper.d.ts +24 -0
- package/dist/esm/types/helpers/genRouteHandler.d.ts +8 -0
- package/dist/esm/types/index.d.ts +2 -2
- package/dist/esm/types/types/ModalType.d.ts +1 -0
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +2 -0
- package/dist/index.d.ts +36 -1
- package/package.json +1 -1
- package/src/components/AppWrapper.tsx +216 -0
- package/src/components/Modal/index.tsx +3 -0
- package/src/helpers/genRouteHandler.ts +99 -0
- package/src/index.ts +2 -0
- package/src/types/ModalType.tsx +1 -0
- package/src/types/ReactKitErrorCode.tsx +3 -1
|
@@ -44,6 +44,30 @@ export declare const confirm: (title: string, text: string, opts?: {
|
|
|
44
44
|
cancelButtonText?: string;
|
|
45
45
|
cancelButtonVariant?: Variant;
|
|
46
46
|
}) => Promise<boolean>;
|
|
47
|
+
/**
|
|
48
|
+
* Show a prompt modal asking the user for input
|
|
49
|
+
* @author Yuen Ler Chow
|
|
50
|
+
* @param title the title text to display at the top of the prompt
|
|
51
|
+
* @param text the text to display in the prompt
|
|
52
|
+
* @param [opts={}] additional options for the prompt dialog
|
|
53
|
+
* @param [opts.placeholder] the placeholder text for the input field
|
|
54
|
+
* @param [opts.defaultText] the default text for the input field
|
|
55
|
+
* @param [opts.confirmButtonText=Okay] the text of the confirm button
|
|
56
|
+
* @param [opts.confirmButtonVariant=Variant.Dark] the variant of the confirm button
|
|
57
|
+
* @param [opts.cancelButtonText=Cancel] the text of the cancel button
|
|
58
|
+
* @param [opts.cancelButtonVariant=Variant.Secondary] the variant of the cancel button
|
|
59
|
+
* @returns Promise that resolves with the input string or null if canceled
|
|
60
|
+
*/
|
|
61
|
+
export declare const prompt: (title: string, text: string, currentInputFieldText: string, opts?: {
|
|
62
|
+
placeholder?: string | undefined;
|
|
63
|
+
defaultText?: string | undefined;
|
|
64
|
+
confirmButtonText?: string | undefined;
|
|
65
|
+
confirmButtonVariant?: Variant | undefined;
|
|
66
|
+
cancelButtonText?: string | undefined;
|
|
67
|
+
cancelButtonVariant?: Variant | undefined;
|
|
68
|
+
minNumChars?: number | undefined;
|
|
69
|
+
findValidationError?: ((text: string) => string | undefined) | undefined;
|
|
70
|
+
} | undefined) => Promise<string | null>;
|
|
47
71
|
/**
|
|
48
72
|
* Show a fatal error message
|
|
49
73
|
* @author Gabe Abrams
|
|
@@ -9,6 +9,12 @@ import LogFunction from '../types/LogFunction';
|
|
|
9
9
|
* @param opts.handler function that processes the request
|
|
10
10
|
* @param [opts.skipSessionCheck] if true, skip the session check (allow users
|
|
11
11
|
* to not be logged in and launched via LTI)
|
|
12
|
+
* @param [opts.allowedHosts] if included, only allow requests from these hosts
|
|
13
|
+
* (start a hostname with a "*" to only check the end of the hostname)
|
|
14
|
+
* you can include just one string instead of an array
|
|
15
|
+
* @param [opts.bannedHosts] if included, do not allow requests from these hosts
|
|
16
|
+
* (start a hostname with a "*" to only check the end of the hostname)
|
|
17
|
+
* you can include just one string instead of an array
|
|
12
18
|
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
13
19
|
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
14
20
|
* where the error message is prefixed with this string. For example,
|
|
@@ -63,6 +69,8 @@ declare const genRouteHandler: (opts: {
|
|
|
63
69
|
logServerEvent: LogFunction;
|
|
64
70
|
}) => any;
|
|
65
71
|
skipSessionCheck?: boolean | undefined;
|
|
72
|
+
allowedHosts?: string | string[] | undefined;
|
|
73
|
+
bannedHosts?: string | string[] | undefined;
|
|
66
74
|
unhandledErrorMessagePrefix?: string | undefined;
|
|
67
75
|
}) => (req: any, res: any, next: () => void) => Promise<undefined>;
|
|
68
76
|
export default genRouteHandler;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import AppWrapper, { alert, confirm, showFatalError, addFatalErrorHandler, leaveToURL } from './components/AppWrapper';
|
|
1
|
+
import AppWrapper, { alert, prompt, confirm, showFatalError, addFatalErrorHandler, leaveToURL } from './components/AppWrapper';
|
|
2
2
|
import LoadingSpinner from './components/LoadingSpinner';
|
|
3
3
|
import ErrorBox from './components/ErrorBox';
|
|
4
4
|
import Modal from './components/Modal';
|
|
@@ -99,4 +99,4 @@ import PickableItem from './components/ItemPicker/types/PickableItem';
|
|
|
99
99
|
import DBEntry from './components/DBEntryManagerPanel/types/DBEntry';
|
|
100
100
|
import DBEntryField from './components/DBEntryManagerPanel/types/DBEntryField';
|
|
101
101
|
import DBEntryFieldType from './components/DBEntryManagerPanel/types/DBEntryFieldType';
|
|
102
|
-
export { AppWrapper, LoadingSpinner, ErrorBox, Modal, TabBox, RadioButton, CheckboxButton, ButtonInputGroup, SimpleDateChooser, Drawer, PopSuccessMark, PopFailureMark, PopPendingMark, CopiableBox, ItemPicker, LogReviewer, IntelliTable, CSVDownloadButton, DBEntryManagerPanel, Tooltip, ToggleSwitch, AutoscrollToBottomContainer, MultiSwitch, Dropdown, alert, confirm, showFatalError, ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, DynamicWord, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, stubServerEndpoint, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, canReviewLogs, isMobileOrTablet, extractProp, compareArraysByProp, genCommaList, validateEmail, validatePhoneNumber, validateString, getLocalTimeInfo, idify, makeLinksClickable, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, initClient, visitServerEndpoint, logClientEvent, addFatalErrorHandler, leaveToURL, combineClassNames, useForceRender, setClientEventMetadataPopulator, initServer, genRouteHandler, handleError, handleSuccess, initLogCollection, addDBEditorEndpoints, visitEndpointOnAnotherServer, ModalButtonType, ModalSize, ModalType, ReactKitErrorCode, Variant, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, IntelliTableColumn, DropdownItemType, PickableItem, DBEntry, DBEntryField, DBEntryFieldType, ParamType, };
|
|
102
|
+
export { AppWrapper, LoadingSpinner, ErrorBox, Modal, TabBox, RadioButton, CheckboxButton, ButtonInputGroup, SimpleDateChooser, Drawer, PopSuccessMark, PopFailureMark, PopPendingMark, CopiableBox, ItemPicker, LogReviewer, IntelliTable, CSVDownloadButton, DBEntryManagerPanel, Tooltip, ToggleSwitch, AutoscrollToBottomContainer, MultiSwitch, Dropdown, alert, prompt, confirm, showFatalError, ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, DynamicWord, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, stubServerEndpoint, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, canReviewLogs, isMobileOrTablet, extractProp, compareArraysByProp, genCommaList, validateEmail, validatePhoneNumber, validateString, getLocalTimeInfo, idify, makeLinksClickable, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, initClient, visitServerEndpoint, logClientEvent, addFatalErrorHandler, leaveToURL, combineClassNames, useForceRender, setClientEventMetadataPopulator, initServer, genRouteHandler, handleError, handleSuccess, initLogCollection, addDBEditorEndpoints, visitEndpointOnAnotherServer, ModalButtonType, ModalSize, ModalType, ReactKitErrorCode, Variant, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, IntelliTableColumn, DropdownItemType, PickableItem, DBEntry, DBEntryField, DBEntryFieldType, ParamType, };
|
|
@@ -8,6 +8,8 @@ declare enum ReactKitErrorCode {
|
|
|
8
8
|
SessionExpired = "DRK3",
|
|
9
9
|
MissingParameter = "DRK4",
|
|
10
10
|
InvalidParameter = "DRK5",
|
|
11
|
+
HostNotAllowed = "DRK17",
|
|
12
|
+
HostBanned = "DRK18",
|
|
11
13
|
WrongCourse = "DRK6",
|
|
12
14
|
NoCACCLSendRequestFunction = "DRK7",
|
|
13
15
|
NoCACCLGetLaunchInfoFunction = "DRK8",
|
package/dist/index.d.ts
CHANGED
|
@@ -63,6 +63,30 @@ declare const confirm: (title: string, text: string, opts?: {
|
|
|
63
63
|
cancelButtonText?: string;
|
|
64
64
|
cancelButtonVariant?: Variant;
|
|
65
65
|
}) => Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Show a prompt modal asking the user for input
|
|
68
|
+
* @author Yuen Ler Chow
|
|
69
|
+
* @param title the title text to display at the top of the prompt
|
|
70
|
+
* @param text the text to display in the prompt
|
|
71
|
+
* @param [opts={}] additional options for the prompt dialog
|
|
72
|
+
* @param [opts.placeholder] the placeholder text for the input field
|
|
73
|
+
* @param [opts.defaultText] the default text for the input field
|
|
74
|
+
* @param [opts.confirmButtonText=Okay] the text of the confirm button
|
|
75
|
+
* @param [opts.confirmButtonVariant=Variant.Dark] the variant of the confirm button
|
|
76
|
+
* @param [opts.cancelButtonText=Cancel] the text of the cancel button
|
|
77
|
+
* @param [opts.cancelButtonVariant=Variant.Secondary] the variant of the cancel button
|
|
78
|
+
* @returns Promise that resolves with the input string or null if canceled
|
|
79
|
+
*/
|
|
80
|
+
declare const prompt: (title: string, text: string, currentInputFieldText: string, opts?: {
|
|
81
|
+
placeholder?: string | undefined;
|
|
82
|
+
defaultText?: string | undefined;
|
|
83
|
+
confirmButtonText?: string | undefined;
|
|
84
|
+
confirmButtonVariant?: Variant | undefined;
|
|
85
|
+
cancelButtonText?: string | undefined;
|
|
86
|
+
cancelButtonVariant?: Variant | undefined;
|
|
87
|
+
minNumChars?: number | undefined;
|
|
88
|
+
findValidationError?: ((text: string) => string | undefined) | undefined;
|
|
89
|
+
} | undefined) => Promise<string | null>;
|
|
66
90
|
/**
|
|
67
91
|
* Show a fatal error message
|
|
68
92
|
* @author Gabe Abrams
|
|
@@ -131,6 +155,7 @@ declare enum ModalSize {
|
|
|
131
155
|
*/
|
|
132
156
|
declare enum ModalType {
|
|
133
157
|
Okay = "okay",
|
|
158
|
+
Cancel = "cancel",
|
|
134
159
|
OkayCancel = "okay-cancel",
|
|
135
160
|
YesNo = "yes-no",
|
|
136
161
|
YesNoCancel = "yes-no-cancel",
|
|
@@ -1044,6 +1069,12 @@ type LogFunction = (opts: ({
|
|
|
1044
1069
|
* @param opts.handler function that processes the request
|
|
1045
1070
|
* @param [opts.skipSessionCheck] if true, skip the session check (allow users
|
|
1046
1071
|
* to not be logged in and launched via LTI)
|
|
1072
|
+
* @param [opts.allowedHosts] if included, only allow requests from these hosts
|
|
1073
|
+
* (start a hostname with a "*" to only check the end of the hostname)
|
|
1074
|
+
* you can include just one string instead of an array
|
|
1075
|
+
* @param [opts.bannedHosts] if included, do not allow requests from these hosts
|
|
1076
|
+
* (start a hostname with a "*" to only check the end of the hostname)
|
|
1077
|
+
* you can include just one string instead of an array
|
|
1047
1078
|
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
1048
1079
|
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
1049
1080
|
* where the error message is prefixed with this string. For example,
|
|
@@ -1098,6 +1129,8 @@ declare const genRouteHandler: (opts: {
|
|
|
1098
1129
|
logServerEvent: LogFunction;
|
|
1099
1130
|
}) => any;
|
|
1100
1131
|
skipSessionCheck?: boolean | undefined;
|
|
1132
|
+
allowedHosts?: string | string[] | undefined;
|
|
1133
|
+
bannedHosts?: string | string[] | undefined;
|
|
1101
1134
|
unhandledErrorMessagePrefix?: string | undefined;
|
|
1102
1135
|
}) => (req: any, res: any, next: () => void) => Promise<undefined>;
|
|
1103
1136
|
|
|
@@ -1637,6 +1670,8 @@ declare enum ReactKitErrorCode {
|
|
|
1637
1670
|
SessionExpired = "DRK3",
|
|
1638
1671
|
MissingParameter = "DRK4",
|
|
1639
1672
|
InvalidParameter = "DRK5",
|
|
1673
|
+
HostNotAllowed = "DRK17",
|
|
1674
|
+
HostBanned = "DRK18",
|
|
1640
1675
|
WrongCourse = "DRK6",
|
|
1641
1676
|
NoCACCLSendRequestFunction = "DRK7",
|
|
1642
1677
|
NoCACCLGetLaunchInfoFunction = "DRK8",
|
|
@@ -1680,4 +1715,4 @@ declare const LogBuiltInMetadata: {
|
|
|
1680
1715
|
};
|
|
1681
1716
|
};
|
|
1682
1717
|
|
|
1683
|
-
export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntry, DBEntryField, DBEntryFieldType, DBEntryManagerPanel, DayOfWeek, Drawer, Dropdown, DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, IntelliTableColumn, ItemPicker, LoadingSpinner, Log, LogAction, LogBuiltInMetadata, LogFunction, LogMetadataType, LogReviewer, LogSource, LogType, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, MultiSwitch, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, ToggleSwitch, Tooltip, Variant, abbreviate, addDBEditorEndpoints, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, genRouteHandler, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, idify, initClient, initLogCollection, initServer, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitEndpointOnAnotherServer, visitServerEndpoint, waitMs };
|
|
1718
|
+
export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntry, DBEntryField, DBEntryFieldType, DBEntryManagerPanel, DayOfWeek, Drawer, Dropdown, DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, IntelliTableColumn, ItemPicker, LoadingSpinner, Log, LogAction, LogBuiltInMetadata, LogFunction, LogMetadataType, LogReviewer, LogSource, LogType, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, MultiSwitch, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, ToggleSwitch, Tooltip, Variant, abbreviate, addDBEditorEndpoints, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, genRouteHandler, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, idify, initClient, initLogCollection, initServer, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitEndpointOnAnotherServer, visitServerEndpoint, waitMs };
|
package/package.json
CHANGED
|
@@ -240,6 +240,124 @@ export const confirm = async (
|
|
|
240
240
|
});
|
|
241
241
|
};
|
|
242
242
|
|
|
243
|
+
/*----------------------------------------*/
|
|
244
|
+
/* --------------- Prompt -------------- */
|
|
245
|
+
/*----------------------------------------*/
|
|
246
|
+
|
|
247
|
+
// Stored copies of setters
|
|
248
|
+
let setPromptInfo: (
|
|
249
|
+
info:
|
|
250
|
+
| undefined
|
|
251
|
+
| {
|
|
252
|
+
title: string;
|
|
253
|
+
text: string;
|
|
254
|
+
currentInputFieldText: string,
|
|
255
|
+
opts: {
|
|
256
|
+
placeholder?: string,
|
|
257
|
+
defaultText?: string,
|
|
258
|
+
confirmButtonText?: string,
|
|
259
|
+
confirmButtonVariant?: Variant,
|
|
260
|
+
cancelButtonText?: string,
|
|
261
|
+
cancelButtonVariant?: Variant,
|
|
262
|
+
minNumChars?: number,
|
|
263
|
+
findValidationError?: (text: string) => string | undefined,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
) => void;
|
|
267
|
+
|
|
268
|
+
// Function to call when prompt is closed
|
|
269
|
+
let onPromptClosed: (result: string | null) => void;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Show a prompt modal asking the user for input
|
|
273
|
+
* @author Yuen Ler Chow
|
|
274
|
+
* @param title the title text to display at the top of the prompt
|
|
275
|
+
* @param text the text to display in the prompt
|
|
276
|
+
* @param [opts={}] additional options for the prompt dialog
|
|
277
|
+
* @param [opts.placeholder] the placeholder text for the input field
|
|
278
|
+
* @param [opts.defaultText] the default text for the input field
|
|
279
|
+
* @param [opts.confirmButtonText=Okay] the text of the confirm button
|
|
280
|
+
* @param [opts.confirmButtonVariant=Variant.Dark] the variant of the confirm button
|
|
281
|
+
* @param [opts.cancelButtonText=Cancel] the text of the cancel button
|
|
282
|
+
* @param [opts.cancelButtonVariant=Variant.Secondary] the variant of the cancel button
|
|
283
|
+
* @returns Promise that resolves with the input string or null if canceled
|
|
284
|
+
*/
|
|
285
|
+
export const prompt = async (
|
|
286
|
+
title: string,
|
|
287
|
+
text: string,
|
|
288
|
+
currentInputFieldText: string,
|
|
289
|
+
opts?: {
|
|
290
|
+
placeholder?: string,
|
|
291
|
+
defaultText?: string,
|
|
292
|
+
confirmButtonText?: string,
|
|
293
|
+
confirmButtonVariant?: Variant,
|
|
294
|
+
cancelButtonText?: string,
|
|
295
|
+
cancelButtonVariant?: Variant,
|
|
296
|
+
minNumChars?: number,
|
|
297
|
+
findValidationError?: (text: string) => string | undefined,
|
|
298
|
+
},
|
|
299
|
+
): Promise<string | null> => {
|
|
300
|
+
// Wait for helper to exist
|
|
301
|
+
await waitForHelper(() => {
|
|
302
|
+
return !!setPromptInfo;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Fallback if prompt is not available
|
|
306
|
+
if (!setPromptInfo) {
|
|
307
|
+
const resultPassesValidation = false;
|
|
308
|
+
while (!resultPassesValidation) {
|
|
309
|
+
// eslint-disable-next-line no-alert
|
|
310
|
+
const result = window.prompt(
|
|
311
|
+
`${title}\n\n${text}`,
|
|
312
|
+
opts?.defaultText ?? '',
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
if (result === null) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Validate min num chars
|
|
320
|
+
const minNumCharsValidationError = (
|
|
321
|
+
(opts?.minNumChars && result.length < opts.minNumChars)
|
|
322
|
+
? `Please enter at least ${opts.minNumChars} characters.`
|
|
323
|
+
: undefined
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// Run custom validation
|
|
327
|
+
const customValidationError = (
|
|
328
|
+
opts?.findValidationError
|
|
329
|
+
&& opts.findValidationError(result)
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Show validation issue
|
|
333
|
+
if (minNumCharsValidationError || customValidationError) {
|
|
334
|
+
alert(
|
|
335
|
+
'Invalid Input',
|
|
336
|
+
`${minNumCharsValidationError ?? ''} ${customValidationError ?? ''}`,
|
|
337
|
+
);
|
|
338
|
+
} else {
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Return promise that resolves with result of prompt
|
|
345
|
+
return new Promise((resolve) => {
|
|
346
|
+
// Setup handler
|
|
347
|
+
onPromptClosed = (result: string | null) => {
|
|
348
|
+
resolve(result);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Show the prompt
|
|
352
|
+
setPromptInfo({
|
|
353
|
+
title,
|
|
354
|
+
text,
|
|
355
|
+
currentInputFieldText,
|
|
356
|
+
opts: (opts ?? {}),
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
|
|
243
361
|
/*----------------------------------------*/
|
|
244
362
|
/* ------------- Fatal Error ------------ */
|
|
245
363
|
/*----------------------------------------*/
|
|
@@ -465,6 +583,27 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
|
|
|
465
583
|
>(undefined);
|
|
466
584
|
setConfirmInfo = setConfirmInfoInner;
|
|
467
585
|
|
|
586
|
+
// Prompt
|
|
587
|
+
const [promptInfo, setPromptInfoInner] = useState<
|
|
588
|
+
| undefined
|
|
589
|
+
| {
|
|
590
|
+
title: string,
|
|
591
|
+
text: string,
|
|
592
|
+
currentInputFieldText: string,
|
|
593
|
+
opts: {
|
|
594
|
+
placeholder?: string,
|
|
595
|
+
defaultText?: string,
|
|
596
|
+
confirmButtonText?: string,
|
|
597
|
+
confirmButtonVariant?: Variant,
|
|
598
|
+
cancelButtonText?: string,
|
|
599
|
+
cancelButtonVariant?: Variant,
|
|
600
|
+
minNumChars?: number,
|
|
601
|
+
findValidationError?: (text: string) => string | undefined,
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
>(undefined);
|
|
605
|
+
setPromptInfo = setPromptInfoInner;
|
|
606
|
+
|
|
468
607
|
// Session expired
|
|
469
608
|
const [
|
|
470
609
|
sessionHasExpired,
|
|
@@ -531,6 +670,83 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
|
|
|
531
670
|
);
|
|
532
671
|
}
|
|
533
672
|
|
|
673
|
+
/* ------------- Prompt ------------ */
|
|
674
|
+
|
|
675
|
+
if (promptInfo) {
|
|
676
|
+
// Run min char validation
|
|
677
|
+
const minNumCharsValidationError = (
|
|
678
|
+
(
|
|
679
|
+
promptInfo.opts.minNumChars
|
|
680
|
+
&& promptInfo.currentInputFieldText.length < promptInfo.opts.minNumChars
|
|
681
|
+
)
|
|
682
|
+
? `Please enter at least ${promptInfo.opts.minNumChars} characters.`
|
|
683
|
+
: undefined
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
// Run custom validation
|
|
687
|
+
const customValidationError = (
|
|
688
|
+
promptInfo.opts.findValidationError
|
|
689
|
+
&& promptInfo.opts.findValidationError(promptInfo.currentInputFieldText)
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
modal = (
|
|
693
|
+
<ModalForWrapper
|
|
694
|
+
key={`prompt-${promptInfo.title}-${promptInfo.text}`}
|
|
695
|
+
title={promptInfo.title}
|
|
696
|
+
// Don't show ok button if there is a validation error
|
|
697
|
+
type={(
|
|
698
|
+
(customValidationError || minNumCharsValidationError)
|
|
699
|
+
? ModalType.Cancel
|
|
700
|
+
: ModalType.OkayCancel
|
|
701
|
+
)}
|
|
702
|
+
okayLabel={promptInfo.opts.confirmButtonText}
|
|
703
|
+
okayVariant={promptInfo.opts.confirmButtonVariant}
|
|
704
|
+
cancelLabel={promptInfo.opts.cancelButtonText}
|
|
705
|
+
cancelVariant={promptInfo.opts.cancelButtonVariant}
|
|
706
|
+
onClose={(buttonType) => {
|
|
707
|
+
const result = (
|
|
708
|
+
buttonType === ModalButtonType.Okay
|
|
709
|
+
? promptInfo.currentInputFieldText
|
|
710
|
+
: null
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
setPromptInfo(undefined);
|
|
714
|
+
|
|
715
|
+
if (onPromptClosed) {
|
|
716
|
+
onPromptClosed(result);
|
|
717
|
+
}
|
|
718
|
+
}}
|
|
719
|
+
onTopOfOtherModals
|
|
720
|
+
dontAllowBackdropExit
|
|
721
|
+
>
|
|
722
|
+
<div className="d-flex flex-column align-items-center">
|
|
723
|
+
<p>{promptInfo.text}</p>
|
|
724
|
+
<input
|
|
725
|
+
type="text"
|
|
726
|
+
placeholder={promptInfo.opts.placeholder}
|
|
727
|
+
value={promptInfo.currentInputFieldText}
|
|
728
|
+
onChange={(e) => {
|
|
729
|
+
return setPromptInfo({
|
|
730
|
+
...promptInfo,
|
|
731
|
+
currentInputFieldText: e.target.value,
|
|
732
|
+
});
|
|
733
|
+
}}
|
|
734
|
+
/>
|
|
735
|
+
{minNumCharsValidationError && (
|
|
736
|
+
<div className="text-danger bg-danger bg-opacity-25 p-2 m-1 rounded">
|
|
737
|
+
{minNumCharsValidationError}
|
|
738
|
+
</div>
|
|
739
|
+
)}
|
|
740
|
+
{customValidationError && (
|
|
741
|
+
<div className="text-danger bg-danger bg-opacity-25 p-2 m-1 rounded">
|
|
742
|
+
{customValidationError}
|
|
743
|
+
</div>
|
|
744
|
+
)}
|
|
745
|
+
</div>
|
|
746
|
+
</ModalForWrapper>
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
534
750
|
/* ------ Custom Modal Portals ------ */
|
|
535
751
|
|
|
536
752
|
// Custom modal portals
|
|
@@ -39,6 +39,12 @@ import LogLevel from '../types/LogLevel';
|
|
|
39
39
|
* @param opts.handler function that processes the request
|
|
40
40
|
* @param [opts.skipSessionCheck] if true, skip the session check (allow users
|
|
41
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
|
|
42
48
|
* @param [opts.unhandledErrorMessagePrefix] if included, when an error that
|
|
43
49
|
* is not of type ErrorWithCode is thrown, the client will receive an error
|
|
44
50
|
* where the error message is prefixed with this string. For example,
|
|
@@ -102,6 +108,8 @@ const genRouteHandler = (
|
|
|
102
108
|
},
|
|
103
109
|
) => any,
|
|
104
110
|
skipSessionCheck?: boolean,
|
|
111
|
+
allowedHosts?: string[] | string,
|
|
112
|
+
bannedHosts?: string[] | string,
|
|
105
113
|
unhandledErrorMessagePrefix?: string,
|
|
106
114
|
},
|
|
107
115
|
) => {
|
|
@@ -110,6 +118,97 @@ const genRouteHandler = (
|
|
|
110
118
|
// Output params
|
|
111
119
|
const output: { [k in string]: any } = {};
|
|
112
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
|
+
|
|
113
212
|
/*----------------------------------------*/
|
|
114
213
|
/* ------------ Parse Params ------------ */
|
|
115
214
|
/*----------------------------------------*/
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Import components
|
|
2
2
|
import AppWrapper, {
|
|
3
3
|
alert,
|
|
4
|
+
prompt,
|
|
4
5
|
confirm,
|
|
5
6
|
showFatalError,
|
|
6
7
|
addFatalErrorHandler,
|
|
@@ -148,6 +149,7 @@ export {
|
|
|
148
149
|
Dropdown,
|
|
149
150
|
// Global functions
|
|
150
151
|
alert,
|
|
152
|
+
prompt,
|
|
151
153
|
confirm,
|
|
152
154
|
showFatalError,
|
|
153
155
|
// Errors
|
package/src/types/ModalType.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Highest error code =
|
|
1
|
+
// Highest error code = DRK18
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* List of error codes built into the react kit
|
|
@@ -10,6 +10,8 @@ enum ReactKitErrorCode {
|
|
|
10
10
|
SessionExpired = 'DRK3',
|
|
11
11
|
MissingParameter = 'DRK4',
|
|
12
12
|
InvalidParameter = 'DRK5',
|
|
13
|
+
HostNotAllowed = 'DRK17',
|
|
14
|
+
HostBanned = 'DRK18',
|
|
13
15
|
WrongCourse = 'DRK6',
|
|
14
16
|
NoCACCLSendRequestFunction = 'DRK7',
|
|
15
17
|
NoCACCLGetLaunchInfoFunction = 'DRK8',
|