ngrx-loading-state
Version:
NgRx Loading State consistently manages loading actions such as API fetches.
190 lines • 23 kB
JavaScript
import { createAction, props } from '@ngrx/store';
import { lodash } from '../lodash';
import { FailureHandlerState } from './loading-state-types';
/**
* See if a new API fetch should be issued.
*
* @param currentState The current loading state.
* @param action The the LoadAction dispatched by the user.
* @returns True if a new API fetch should be issued.
*/
export function shouldIssueFetch(currentState, action) {
if (currentState == null) {
return true;
}
const maxAge = action?.maxAge;
// If action not given, then we err on the side of caution and always do a reload, even if
// an API fetch is already happening.
if (maxAge == null) {
return true;
}
else {
// Check if data not loaded or if too old.
const reload = !currentState.successTimestamp || Date.now() - currentState.successTimestamp >= maxAge;
// Do not issue duplicate loads if a fetch is already in progress
return reload && !currentState.loading;
}
}
/**
* Return the type of error handler that should be used.
*
* @param currentState the current state
* @param action the load action dispatched by the user
* @param issueFetch whether the load action should issue a new API call.
* @returns
*/
export function getFailureHandlerState(currentState, action, issueFetch) {
if (currentState == null) {
// If currentState does not exist, then errorHandlerState can be assumed to be initialised to ErrorHandlerState.INIT
if (action.localError) {
return FailureHandlerState.LOCAL;
}
else {
return FailureHandlerState.GLOBAL;
}
}
// If loading or issuing API fetch, then there is guaranteed to be a success/failure
// action that the global error handler might need to handle.
if (currentState.loading || issueFetch) {
// If any load action sets the localError to true, then it disables the global error hander
// until the success/failure action is handled.
if (action.localError) {
return FailureHandlerState.LOCAL;
}
else if (currentState.failureHandlerState == FailureHandlerState.INIT) {
// If it's in the INIT state, then we use the default global handler because the
// loading action has not requests for localError handler.
return FailureHandlerState.GLOBAL;
}
// else just fall through and return the existing errorHandler unchanged.
}
return currentState.failureHandlerState;
}
/**
* This function make it easier to define the type of prop<T>. Internal use only.
*
* T extends object meets the condition of props function
*
* ref: https://stackoverflow.com/questions/65888508/how-to-use-generic-type-in-ngrx-createaction-props
*
* @param type String type of the action
* @returns An action creator function
*/
export function actionFactory(type) {
// Restricting config type to match createAction requirements
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return createAction(type, props());
}
/**
* Make a clone of any object that implements the LoadingState interface, but copy over only
* those fields that are in the LoadingState interface.
*
* @param src Any object that implements the LoadingState interface
* @returns A copy of src with only fields from LoadingState
*/
export function cloneLoadingState(src) {
// Since the "src" could contain extra fields, we want to make sure we exclude other fields in the
// comparison. Using Required<> to make sure we don't miss any fields.
// Would be better if we can iterate all the fields in the LoadingStateBase interface. But unless we change
// LoadingStateBase to a class, it doesn't seem likely. Below is verbose, but at least it should capture all
// the fields.
if (src == null) {
return src;
}
else {
// Using Required<> to ensure we don't miss any fields from LoadingStateBase.
const ret = lodash.pick(src, [
'isLoadingState',
'loading',
'success',
'issueFetch',
'failureHandlerState',
'successTimestamp',
'error'
]);
return ret;
}
}
/**
* Combine the currentState and the user dispatched LoadAction into newState.
*
* @param action User dispatched LoadAction
* @param currentState Current store state
* @returns A new state if the state should change, null otherwise.
*/
export function getNewLoadState(action, currentState) {
const issueFetch = shouldIssueFetch(currentState, action);
const failureHandlerState = getFailureHandlerState(currentState, action, issueFetch);
const newState = issueFetch
? {
isLoadingState: true,
loading: true,
success: false,
issueFetch,
failureHandlerState,
successTimestamp: currentState?.successTimestamp,
error: undefined
}
: {
isLoadingState: true,
// Deliberately avoiding the use of the spread operator, i.e. no ...currentState
// because we want to be 100% explicit about the states we are setting. Using ...currentState
// makes it difficult to read. Being explicit means we need to specify all fields
// from LoadingState. If we ever add more states to LoadingState the typing will catch any
// missing states. There's also just the loading(), success(), failure() functions
// so not too cumbersome to be explicit.
loading: currentState.loading,
success: currentState.success,
issueFetch,
failureHandlerState,
successTimestamp: currentState.successTimestamp,
error: currentState.error
};
// Note that even if issueFetch is false, the errorHandlerState could still change. So we should
// compare every field from old to new state.
return lodash.isEqual(currentState, newState) ? null : newState;
}
/**
* @returns Always returns a new success state.
*/
export function getNewSuccessState() {
const ret = {
isLoadingState: true,
loading: false,
success: true,
issueFetch: false,
// Each load action will set this again, so here we just set it back to default.
failureHandlerState: FailureHandlerState.INIT,
// Since this time field changes, all SuccessAction will cause a state update.
successTimestamp: Date.now(),
error: undefined
};
return ret;
}
/**
* Returns a new FailureState.
*
* @param action User dispatched FailureAction
* @param currentState Current loading state
* @returns A new loading state.
*/
export function getNewFailureState(action, currentState) {
return {
isLoadingState: true,
loading: false,
success: false,
issueFetch: false,
// Leading this as is for the global failure handler to check.
failureHandlerState: currentState.failureHandlerState,
successTimestamp: currentState.successTimestamp,
error: action.error
};
}
export function combineLoadingStates(loadingStates) {
return {
loading: loadingStates.some((state) => !!state?.loading),
success: loadingStates.every((state) => !!state?.success),
error: loadingStates.find((state) => !!state?.error)?.error
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZGluZy1zdGF0ZS1mdW5jdGlvbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3J4LWxvYWRpbmctc3RhdGUvc3JjL2xpYi9sb2FkaW5nLXN0YXRlL2xvYWRpbmctc3RhdGUtZnVuY3Rpb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBc0IsWUFBWSxFQUFtQixLQUFLLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFdkYsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNuQyxPQUFPLEVBSUwsbUJBQW1CLEVBR3BCLE1BQU0sdUJBQXVCLENBQUM7QUFFL0I7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQixDQUM5QixZQUFvQyxFQUNwQyxNQUE0QjtJQUU1QixJQUFJLFlBQVksSUFBSSxJQUFJLEVBQUU7UUFDeEIsT0FBTyxJQUFJLENBQUM7S0FDYjtJQUVELE1BQU0sTUFBTSxHQUFHLE1BQU0sRUFBRSxNQUFNLENBQUM7SUFFOUIsMEZBQTBGO0lBQzFGLHFDQUFxQztJQUNyQyxJQUFJLE1BQU0sSUFBSSxJQUFJLEVBQUU7UUFDbEIsT0FBTyxJQUFJLENBQUM7S0FDYjtTQUFNO1FBQ0wsMENBQTBDO1FBQzFDLE1BQU0sTUFBTSxHQUNWLENBQUMsWUFBWSxDQUFDLGdCQUFnQixJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxZQUFZLENBQUMsZ0JBQWdCLElBQUksTUFBTSxDQUFDO1FBRXpGLGlFQUFpRTtRQUNqRSxPQUFPLE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUM7S0FDeEM7QUFDSCxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0sVUFBVSxzQkFBc0IsQ0FDcEMsWUFBb0MsRUFDcEMsTUFBNEIsRUFDNUIsVUFBbUI7SUFFbkIsSUFBSSxZQUFZLElBQUksSUFBSSxFQUFFO1FBQ3hCLG9IQUFvSDtRQUNwSCxJQUFJ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dGlvbkNyZWF0b3JQcm9wcywgY3JlYXRlQWN0aW9uLCBOb3RBbGxvd2VkQ2hlY2ssIHByb3BzIH0gZnJvbSAnQG5ncngvc3RvcmUnO1xuaW1wb3J0IHsgQWN0aW9uIH0gZnJvbSAnQG5ncngvc3RvcmUvc3JjL21vZGVscyc7XG5pbXBvcnQgeyBsb2Rhc2ggfSBmcm9tICcuLi9sb2Rhc2gnO1xuaW1wb3J0IHtcbiAgQWN0aW9uRmFjdG9yeVJlc3VsdCxcbiAgQ29tYmluZWRMb2FkaW5nU3RhdGUsXG4gIEZhaWx1cmVBY3Rpb24sXG4gIEZhaWx1cmVIYW5kbGVyU3RhdGUsXG4gIExvYWRBY3Rpb24sXG4gIExvYWRpbmdTdGF0ZVxufSBmcm9tICcuL2xvYWRpbmctc3RhdGUtdHlwZXMnO1xuXG4vKipcbiAqIFNlZSBpZiBhIG5ldyBBUEkgZmV0Y2ggc2hvdWxkIGJlIGlzc3VlZC5cbiAqXG4gKiBAcGFyYW0gY3VycmVudFN0YXRlIFRoZSBjdXJyZW50IGxvYWRpbmcgc3RhdGUuXG4gKiBAcGFyYW0gYWN0aW9uIFRoZSB0aGUgTG9hZEFjdGlvbiBkaXNwYXRjaGVkIGJ5IHRoZSB1c2VyLlxuICogQHJldHVybnMgVHJ1ZSBpZiBhIG5ldyBBUEkgZmV0Y2ggc2hvdWxkIGJlIGlzc3VlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNob3VsZElzc3VlRmV0Y2goXG4gIGN1cnJlbnRTdGF0ZTogUmVhZG9ubHk8TG9hZGluZ1N0YXRlPixcbiAgYWN0aW9uOiBSZWFkb25seTxMb2FkQWN0aW9uPlxuKTogYm9vbGVhbiB7XG4gIGlmIChjdXJyZW50U3RhdGUgPT0gbnVsbCkge1xuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgY29uc3QgbWF4QWdlID0gYWN0aW9uPy5tYXhBZ2U7XG5cbiAgLy8gSWYgYWN0aW9uIG5vdCBnaXZlbiwgdGhlbiB3ZSBlcnIgb24gdGhlIHNpZGUgb2YgY2F1dGlvbiBhbmQgYWx3YXlzIGRvIGEgcmVsb2FkLCBldmVuIGlmXG4gIC8vIGFuIEFQSSBmZXRjaCBpcyBhbHJlYWR5IGhhcHBlbmluZy5cbiAgaWYgKG1heEFnZSA9PSBudWxsKSB7XG4gICAgcmV0dXJuIHRydWU7XG4gIH0gZWxzZSB7XG4gICAgLy8gQ2hlY2sgaWYgZGF0YSBub3QgbG9hZGVkIG9yIGlmIHRvbyBvbGQuXG4gICAgY29uc3QgcmVsb2FkID1cbiAgICAgICFjdXJyZW50U3RhdGUuc3VjY2Vzc1RpbWVzdGFtcCB8fCBEYXRlLm5vdygpIC0gY3VycmVudFN0YXRlLnN1Y2Nlc3NUaW1lc3RhbXAgPj0gbWF4QWdlO1xuXG4gICAgLy8gRG8gbm90IGlzc3VlIGR1cGxpY2F0ZSBsb2FkcyBpZiBhIGZldGNoIGlzIGFscmVhZHkgaW4gcHJvZ3Jlc3NcbiAgICByZXR1cm4gcmVsb2FkICYmICFjdXJyZW50U3RhdGUubG9hZGluZztcbiAgfVxufVxuXG4vKipcbiAqIFJldHVybiB0aGUgdHlwZSBvZiBlcnJvciBoYW5kbGVyIHRoYXQgc2hvdWxkIGJlIHVzZWQuXG4gKlxuICogQHBhcmFtIGN1cnJlbnRTdGF0ZSB0aGUgY3VycmVudCBzdGF0ZVxuICogQHBhcmFtIGFjdGlvbiB0aGUgbG9hZCBhY3Rpb24gZGlzcGF0Y2hlZCBieSB0aGUgdXNlclxuICogQHBhcmFtIGlzc3VlRmV0Y2ggd2hldGhlciB0aGUgbG9hZCBhY3Rpb24gc2hvdWxkIGlzc3VlIGEgbmV3IEFQSSBjYWxsLlxuICogQHJldHVybnNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldEZhaWx1cmVIYW5kbGVyU3RhdGUoXG4gIGN1cnJlbnRTdGF0ZTogUmVhZG9ubHk8TG9hZGluZ1N0YXRlPixcbiAgYWN0aW9uOiBSZWFkb25seTxMb2FkQWN0aW9uPixcbiAgaXNzdWVGZXRjaDogYm9vbGVhblxuKTogRmFpbHVyZUhhbmRsZXJTdGF0ZSB7XG4gIGlmIChjdXJyZW50U3RhdGUgPT0gbnVsbCkge1xuICAgIC8vIElmIGN1cnJlbnRTdGF0ZSBkb2VzIG5vdCBleGlzdCwgdGhlbiBlcnJvckhhbmRsZXJTdGF0ZSBjYW4gYmUgYXNzdW1lZCB0byBiZSBpbml0aWFsaXNlZCB0byBFcnJvckhhbmRsZXJTdGF0ZS5JTklUXG4gICAgaWYgKGFjdGlvbi5sb2NhbEVycm9yKSB7XG4gICAgICByZXR1cm4gRmFpbHVyZUhhbmRsZXJTdGF0ZS5MT0NBTDtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIEZhaWx1cmVIYW5kbGVyU3RhdGUuR0xPQkFMO1xuICAgIH1cbiAgfVxuXG4gIC8vIElmIGxvYWRpbmcgb3IgaXNzdWluZyBBUEkgZmV0Y2gsIHRoZW4gdGhlcmUgaXMgZ3VhcmFudGVlZCB0byBiZSBhIHN1Y2Nlc3MvZmFpbHVyZVxuICAvLyBhY3Rpb24gdGhhdCB0aGUgZ2xvYmFsIGVycm9yIGhhbmRsZXIgbWlnaHQgbmVlZCB0byBoYW5kbGUuXG4gIGlmIChjdXJyZW50U3RhdGUubG9hZGluZyB8fCBpc3N1ZUZldGNoKSB7XG4gICAgLy8gSWYgYW55IGxvYWQgYWN0aW9uIHNldHMgdGhlIGxvY2FsRXJyb3IgdG8gdHJ1ZSwgdGhlbiBpdCBkaXNhYmxlcyB0aGUgZ2xvYmFsIGVycm9yIGhhbmRlclxuICAgIC8vIHVudGlsIHRoZSBzdWNjZXNzL2ZhaWx1cmUgYWN0aW9uIGlzIGhhbmRsZWQuXG4gICAgaWYgKGFjdGlvbi5sb2NhbEVycm9yKSB7XG4gICAgICByZXR1cm4gRmFpbHVyZUhhbmRsZXJTdGF0ZS5MT0NBTDtcbiAgICB9IGVsc2UgaWYgKGN1cnJlbnRTdGF0ZS5mYWlsdXJlSGFuZGxlclN0YXRlID09IEZhaWx1cmVIYW5kbGVyU3RhdGUuSU5JVCkge1xuICAgICAgLy8gSWYgaXQncyBpbiB0aGUgSU5JVCBzdGF0ZSwgdGhlbiB3ZSB1c2UgdGhlIGRlZmF1bHQgZ2xvYmFsIGhhbmRsZXIgYmVjYXVzZSB0aGVcbiAgICAgIC8vIGxvYWRpbmcgYWN0aW9uIGhhcyBub3QgcmVxdWVzdHMgZm9yIGxvY2FsRXJyb3IgaGFuZGxlci5cbiAgICAgIHJldHVybiBGYWlsdXJlSGFuZGxlclN0YXRlLkdMT0JBTDtcbiAgICB9XG4gICAgLy8gZWxzZSBqdXN0IGZhbGwgdGhyb3VnaCBhbmQgcmV0dXJuIHRoZSBleGlzdGluZyBlcnJvckhhbmRsZXIgdW5jaGFuZ2VkLlxuICB9XG5cbiAgcmV0dXJuIGN1cnJlbnRTdGF0ZS5mYWlsdXJlSGFuZGxlclN0YXRlO1xufVxuXG4vKipcbiAqIFRoaXMgZnVuY3Rpb24gbWFrZSBpdCBlYXNpZXIgdG8gZGVmaW5lIHRoZSB0eXBlIG9mIHByb3A8VD4uIEludGVybmFsIHVzZSBvbmx5LlxuICpcbiAqIFQgZXh0ZW5kcyBvYmplY3QgbWVldHMgdGhlIGNvbmRpdGlvbiBvZiBwcm9wcyBmdW5jdGlvblxuICpcbiAqIHJlZjogaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNjU4ODg1MDgvaG93LXRvLXVzZS1nZW5lcmljLXR5cGUtaW4tbmdyeC1jcmVhdGVhY3Rpb24tcHJvcHNcbiAqXG4gKiBAcGFyYW0gdHlwZSBTdHJpbmcgdHlwZSBvZiB0aGUgYWN0aW9uXG4gKiBAcmV0dXJucyBBbiBhY3Rpb24gY3JlYXRvciBmdW5jdGlvblxuICovXG5leHBvcnQgZnVuY3Rpb24gYWN0aW9uRmFjdG9yeTxUIGV4dGVuZHMgb2JqZWN0Pih0eXBlOiBzdHJpbmcpOiBBY3Rpb25GYWN0b3J5UmVzdWx0PFQ+IHtcbiAgLy8gUmVzdHJpY3RpbmcgY29uZmlnIHR5cGUgdG8gbWF0Y2ggY3JlYXRlQWN0aW9uIHJlcXVpcmVtZW50c1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueVxuICByZXR1cm4gY3JlYXRlQWN0aW9uKHR5cGUsIHByb3BzPGFueT4oKSBhcyBBY3Rpb25DcmVhdG9yUHJvcHM8VD4gJiBOb3RBbGxvd2VkQ2hlY2s8VD4pO1xufVxuXG4vKipcbiAqIE1ha2UgYSBjbG9uZSBvZiBhbnkgb2JqZWN0IHRoYXQgaW1wbGVtZW50cyB0aGUgTG9hZGluZ1N0YXRlIGludGVyZmFjZSwgYnV0IGNvcHkgb3ZlciBvbmx5XG4gKiB0aG9zZSBmaWVsZHMgdGhhdCBhcmUgaW4gdGhlIExvYWRpbmdTdGF0ZSBpbnRlcmZhY2UuXG4gKlxuICogQHBhcmFtIHNyYyBBbnkgb2JqZWN0IHRoYXQgaW1wbGVtZW50cyB0aGUgTG9hZGluZ1N0YXRlIGludGVyZmFjZVxuICogQHJldHVybnMgQSBjb3B5IG9mIHNyYyB3aXRoIG9ubHkgZmllbGRzIGZyb20gTG9hZGluZ1N0YXRlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjbG9uZUxvYWRpbmdTdGF0ZShzcmM6IExvYWRpbmdTdGF0ZSk6IExvYWRpbmdTdGF0ZSB7XG4gIC8vIFNpbmNlIHRoZSBcInNyY1wiIGNvdWxkIGNvbnRhaW4gZXh0cmEgZmllbGRzLCB3ZSB3YW50IHRvIG1ha2Ugc3VyZSB3ZSBleGNsdWRlIG90aGVyIGZpZWxkcyBpbiB0aGVcbiAgLy8gY29tcGFyaXNvbi4gVXNpbmcgUmVxdWlyZWQ8PiB0byBtYWtlIHN1cmUgd2UgZG9uJ3QgbWlzcyBhbnkgZmllbGRzLlxuICAvLyBXb3VsZCBiZSBiZXR0ZXIgaWYgd2UgY2FuIGl0ZXJhdGUgYWxsIHRoZSBmaWVsZHMgaW4gdGhlIExvYWRpbmdTdGF0ZUJhc2UgaW50ZXJmYWNlLiBCdXQgdW5sZXNzIHdlIGNoYW5nZVxuICAvLyBMb2FkaW5nU3RhdGVCYXNlIHRvIGEgY2xhc3MsIGl0IGRvZXNuJ3Qgc2VlbSBsaWtlbHkuIEJlbG93IGlzIHZlcmJvc2UsIGJ1dCBhdCBsZWFzdCBpdCBzaG91bGQgY2FwdHVyZSBhbGxcbiAgLy8gdGhlIGZpZWxkcy5cblxuICBpZiAoc3JjID09IG51bGwpIHtcbiAgICByZXR1cm4gc3JjO1xuICB9IGVsc2Uge1xuICAgIC8vIFVzaW5nIFJlcXVpcmVkPD4gdG8gZW5zdXJlIHdlIGRvbid0IG1pc3MgYW55IGZpZWxkcyBmcm9tIExvYWRpbmdTdGF0ZUJhc2UuXG4gICAgY29uc3QgcmV0OiBSZXF1aXJlZDxMb2FkaW5nU3RhdGU+ID0gbG9kYXNoLnBpY2soc3JjIGFzIFJlcXVpcmVkPExvYWRpbmdTdGF0ZT4sIFtcbiAgICAgICdpc0xvYWRpbmdTdGF0ZScsXG4gICAgICAnbG9hZGluZycsXG4gICAgICAnc3VjY2VzcycsXG4gICAgICAnaXNzdWVGZXRjaCcsXG4gICAgICAnZmFpbHVyZUhhbmRsZXJTdGF0ZScsXG4gICAgICAnc3VjY2Vzc1RpbWVzdGFtcCcsXG4gICAgICAnZXJyb3InXG4gICAgXSk7XG5cbiAgICByZXR1cm4gcmV0O1xuICB9XG59XG5cbi8qKlxuICogQ29tYmluZSB0aGUgY3VycmVudFN0YXRlIGFuZCB0aGUgdXNlciBkaXNwYXRjaGVkIExvYWRBY3Rpb24gaW50byBuZXdTdGF0ZS5cbiAqXG4gKiBAcGFyYW0gYWN0aW9uIFVzZXIgZGlzcGF0Y2hlZCBMb2FkQWN0aW9uXG4gKiBAcGFyYW0gY3VycmVudFN0YXRlIEN1cnJlbnQgc3RvcmUgc3RhdGVcbiAqIEByZXR1cm5zIEEgbmV3IHN0YXRlIGlmIHRoZSBzdGF0ZSBzaG91bGQgY2hhbmdlLCBudWxsIG90aGVyd2lzZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldE5ld0xvYWRTdGF0ZShcbiAgYWN0aW9uOiBMb2FkQWN0aW9uICYgQWN0aW9uLFxuICBjdXJyZW50U3RhdGU6IExvYWRpbmdTdGF0ZVxuKTogUmVhZG9ubHk8TG9hZGluZ1N0YXRlPiB8IG51bGwge1xuICBjb25zdCBpc3N1ZUZldGNoID0gc2hvdWxkSXNzdWVGZXRjaChjdXJyZW50U3RhdGUsIGFjdGlvbik7XG5cbiAgY29uc3QgZmFpbHVyZUhhbmRsZXJTdGF0ZSA9IGdldEZhaWx1cmVIYW5kbGVyU3RhdGUoY3VycmVudFN0YXRlLCBhY3Rpb24sIGlzc3VlRmV0Y2gpO1xuXG4gIGNvbnN0IG5ld1N0YXRlOiBMb2FkaW5nU3RhdGUgPSBpc3N1ZUZldGNoXG4gICAgPyB7XG4gICAgICAgIGlzTG9hZGluZ1N0YXRlOiB0cnVlLFxuICAgICAgICBsb2FkaW5nOiB0cnVlLFxuICAgICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgICAgaXNzdWVGZXRjaCxcbiAgICAgICAgZmFpbHVyZUhhbmRsZXJTdGF0ZSxcbiAgICAgICAgc3VjY2Vzc1RpbWVzdGFtcDogY3VycmVudFN0YXRlPy5zdWNjZXNzVGltZXN0YW1wLFxuICAgICAgICBlcnJvcjogdW5kZWZpbmVkXG4gICAgICB9XG4gICAgOiB7XG4gICAgICAgIGlzTG9hZGluZ1N0YXRlOiB0cnVlLFxuICAgICAgICAvLyBEZWxpYmVyYXRlbHkgYXZvaWRpbmcgdGhlIHVzZSBvZiB0aGUgc3ByZWFkIG9wZXJhdG9yLCBpLmUuIG5vIC4uLmN1cnJlbnRTdGF0ZVxuICAgICAgICAvLyBiZWNhdXNlIHdlIHdhbnQgdG8gYmUgMTAwJSBleHBsaWNpdCBhYm91dCB0aGUgc3RhdGVzIHdlIGFyZSBzZXR0aW5nLiBVc2luZyAuLi5jdXJyZW50U3RhdGVcbiAgICAgICAgLy8gbWFrZXMgaXQgZGlmZmljdWx0IHRvIHJlYWQuIEJlaW5nIGV4cGxpY2l0IG1lYW5zIHdlIG5lZWQgdG8gc3BlY2lmeSBhbGwgZmllbGRzXG4gICAgICAgIC8vIGZyb20gTG9hZGluZ1N0YXRlLiBJZiB3ZSBldmVyIGFkZCBtb3JlIHN0YXRlcyB0byBMb2FkaW5nU3RhdGUgdGhlIHR5cGluZyB3aWxsIGNhdGNoIGFueVxuICAgICAgICAvLyBtaXNzaW5nIHN0YXRlcy4gVGhlcmUncyBhbHNvIGp1c3QgdGhlIGxvYWRpbmcoKSwgc3VjY2VzcygpLCBmYWlsdXJlKCkgZnVuY3Rpb25zXG4gICAgICAgIC8vIHNvIG5vdCB0b28gY3VtYmVyc29tZSB0byBiZSBleHBsaWNpdC5cbiAgICAgICAgbG9hZGluZzogY3VycmVudFN0YXRlLmxvYWRpbmcsXG4gICAgICAgIHN1Y2Nlc3M6IGN1cnJlbnRTdGF0ZS5zdWNjZXNzLFxuICAgICAgICBpc3N1ZUZldGNoLFxuICAgICAgICBmYWlsdXJlSGFuZGxlclN0YXRlLFxuICAgICAgICBzdWNjZXNzVGltZXN0YW1wOiBjdXJyZW50U3RhdGUuc3VjY2Vzc1RpbWVzdGFtcCxcbiAgICAgICAgZXJyb3I6IGN1cnJlbnRTdGF0ZS5lcnJvclxuICAgICAgfTtcblxuICAvLyBOb3RlIHRoYXQgZXZlbiBpZiBpc3N1ZUZldGNoIGlzIGZhbHNlLCB0aGUgZXJyb3JIYW5kbGVyU3RhdGUgY291bGQgc3RpbGwgY2hhbmdlLiBTbyB3ZSBzaG91bGRcbiAgLy8gY29tcGFyZSBldmVyeSBmaWVsZCBmcm9tIG9sZCB0byBuZXcgc3RhdGUuXG4gIHJldHVybiBsb2Rhc2guaXNFcXVhbChjdXJyZW50U3RhdGUsIG5ld1N0YXRlKSA/IG51bGwgOiBuZXdTdGF0ZTtcbn1cblxuLyoqXG4gKiBAcmV0dXJucyBBbHdheXMgcmV0dXJucyBhIG5ldyBzdWNjZXNzIHN0YXRlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0TmV3U3VjY2Vzc1N0YXRlKCk6IFJlYWRvbmx5PExvYWRpbmdTdGF0ZT4ge1xuICBjb25zdCByZXQ6IExvYWRpbmdTdGF0ZSA9IHtcbiAgICBpc0xvYWRpbmdTdGF0ZTogdHJ1ZSxcbiAgICBsb2FkaW5nOiBmYWxzZSxcbiAgICBzdWNjZXNzOiB0cnVlLFxuICAgIGlzc3VlRmV0Y2g6IGZhbHNlLFxuICAgIC8vIEVhY2ggbG9hZCBhY3Rpb24gd2lsbCBzZXQgdGhpcyBhZ2Fpbiwgc28gaGVyZSB3ZSBqdXN0IHNldCBpdCBiYWNrIHRvIGRlZmF1bHQuXG4gICAgZmFpbHVyZUhhbmRsZXJTdGF0ZTogRmFpbHVyZUhhbmRsZXJTdGF0ZS5JTklULFxuICAgIC8vIFNpbmNlIHRoaXMgdGltZSBmaWVsZCBjaGFuZ2VzLCBhbGwgU3VjY2Vzc0FjdGlvbiB3aWxsIGNhdXNlIGEgc3RhdGUgdXBkYXRlLlxuICAgIHN1Y2Nlc3NUaW1lc3RhbXA6IERhdGUubm93KCksXG4gICAgZXJyb3I6IHVuZGVmaW5lZFxuICB9O1xuXG4gIHJldHVybiByZXQ7XG59XG5cbi8qKlxuICogUmV0dXJucyBhIG5ldyBGYWlsdXJlU3RhdGUuXG4gKlxuICogQHBhcmFtIGFjdGlvbiBVc2VyIGRpc3BhdGNoZWQgRmFpbHVyZUFjdGlvblxuICogQHBhcmFtIGN1cnJlbnRTdGF0ZSBDdXJyZW50IGxvYWRpbmcgc3RhdGVcbiAqIEByZXR1cm5zIEEgbmV3IGxvYWRpbmcgc3RhdGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXROZXdGYWlsdXJlU3RhdGUoXG4gIGFjdGlvbjogRmFpbHVyZUFjdGlvbiAmIEFjdGlvbixcbiAgY3VycmVudFN0YXRlOiBMb2FkaW5nU3RhdGVcbik6IFJlYWRvbmx5PExvYWRpbmdTdGF0ZT4ge1xuICByZXR1cm4ge1xuICAgIGlzTG9hZGluZ1N0YXRlOiB0cnVlLFxuICAgIGxvYWRpbmc6IGZhbHNlLFxuICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgIGlzc3VlRmV0Y2g6IGZhbHNlLFxuICAgIC8vIExlYWRpbmcgdGhpcyBhcyBpcyBmb3IgdGhlIGdsb2JhbCBmYWlsdXJlIGhhbmRsZXIgdG8gY2hlY2suXG4gICAgZmFpbHVyZUhhbmRsZXJTdGF0ZTogY3VycmVudFN0YXRlLmZhaWx1cmVIYW5kbGVyU3RhdGUsXG4gICAgc3VjY2Vzc1RpbWVzdGFtcDogY3VycmVudFN0YXRlLnN1Y2Nlc3NUaW1lc3RhbXAsXG4gICAgZXJyb3I6IGFjdGlvbi5lcnJvclxuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY29tYmluZUxvYWRpbmdTdGF0ZXMobG9hZGluZ1N0YXRlczogTG9hZGluZ1N0YXRlW10pOiBDb21iaW5lZExvYWRpbmdTdGF0ZSB7XG4gIHJldHVybiB7XG4gICAgbG9hZGluZzogbG9hZGluZ1N0YXRlcy5zb21lKChzdGF0ZSkgPT4gISFzdGF0ZT8ubG9hZGluZyksXG4gICAgc3VjY2VzczogbG9hZGluZ1N0YXRlcy5ldmVyeSgoc3RhdGUpID0+ICEhc3RhdGU/LnN1Y2Nlc3MpLFxuICAgIGVycm9yOiBsb2FkaW5nU3RhdGVzLmZpbmQoKHN0YXRlKSA9PiAhIXN0YXRlPy5lcnJvcik/LmVycm9yXG4gIH07XG59XG4iXX0=