Grabbed dndbeyond's source code
``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
import axios from "axios";
|
||||
import { merge } from "lodash";
|
||||
|
||||
import {
|
||||
ApiAdapterDataException,
|
||||
ApiAdapterException,
|
||||
ApiAdapterUrlException,
|
||||
ApiException,
|
||||
AuthException,
|
||||
AuthMissingException,
|
||||
Constants,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { appEnvActions } from "../../actions";
|
||||
import { appInfoActions } from "../../actions/appInfo";
|
||||
import AppErrorTypeEnum from "../../constants/AppErrorTypeEnum";
|
||||
import * as HttpStatusCodes from "../../constants/HttpStatusCodes";
|
||||
import { appEnvSelectors } from "../../selectors";
|
||||
import { StateStoreUtils } from "../../stores";
|
||||
import { AppNotificationUtils, ErrorCustomTags, ErrorUtils } from "../../utils";
|
||||
import { COALESCED_ERROR_CODES } from "./constants";
|
||||
|
||||
let hasDispatchedAuthException: boolean = false;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
export function processApiUrl(url: string): string {
|
||||
let match = url.match(/^(.*\/character\/v\d+\/character)\/\d+$/);
|
||||
if (match) {
|
||||
return [match[1], "{id}"].join("/");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param error
|
||||
* @param extraContextData
|
||||
*/
|
||||
export function getApiAdapterUrlExceptionContextData(
|
||||
error: ApiAdapterUrlException,
|
||||
extraContextData: Record<string, any>
|
||||
): Record<string, any> {
|
||||
let errorContextData: Record<string, any> = error.contextData ?? {};
|
||||
let errorMessageContextData: Record<string, any> = {
|
||||
errorMessage: error.message,
|
||||
errorUrl: error.url,
|
||||
errorMethod: error.method,
|
||||
};
|
||||
|
||||
return merge({}, errorContextData, errorMessageContextData, extraContextData);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
export function getApiAdapterUrlExceptionCustomTags(
|
||||
error: ApiAdapterUrlException
|
||||
): ErrorCustomTags {
|
||||
return {
|
||||
serverErrorId: error.contextData?.serverErrorData?.errorCode ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param error
|
||||
* @param messagePrefix
|
||||
*/
|
||||
export function getApiAdapterUrlExceptionMessage(
|
||||
error: ApiAdapterUrlException,
|
||||
messagePrefix: string
|
||||
): string {
|
||||
if (error.errorCode === null) {
|
||||
return "Unknown Client Request Failure";
|
||||
}
|
||||
|
||||
if (COALESCED_ERROR_CODES.includes(error.errorCode)) {
|
||||
return [messagePrefix, error.errorCode].join(" - ");
|
||||
}
|
||||
|
||||
return [
|
||||
messagePrefix,
|
||||
error.errorCode,
|
||||
error.method ?? "UNKNOWN Method",
|
||||
error.url ? processApiUrl(error.url) : "UNKNOWN URL",
|
||||
].join(" - ");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
export function getApiAdapterUrlExceptionAppErrorType(
|
||||
error: ApiAdapterUrlException
|
||||
): AppErrorTypeEnum {
|
||||
if (error.errorCode === HttpStatusCodes.FORBIDDEN) {
|
||||
return AppErrorTypeEnum.ACCESS_DENIED;
|
||||
}
|
||||
if (error.errorCode === HttpStatusCodes.NOT_FOUND) {
|
||||
return AppErrorTypeEnum.NOT_FOUND;
|
||||
}
|
||||
|
||||
return AppErrorTypeEnum.API_FAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param error
|
||||
* @param contextData
|
||||
*/
|
||||
export function logError(
|
||||
error: Error,
|
||||
contextData: Record<string, any> = {}
|
||||
): null {
|
||||
// This function returns null because the Rules Engine config expects a log method
|
||||
// that returns string | null. See packages/rules-engine/src/config/typings.ts
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.log("***************");
|
||||
console.log("The following error happened:");
|
||||
console.log(error);
|
||||
console.log("***************");
|
||||
}
|
||||
|
||||
if (axios.isCancel(error)) {
|
||||
// only do something if not canceled, cancellation should be fine
|
||||
return null;
|
||||
}
|
||||
|
||||
let appErrorType: AppErrorTypeEnum = AppErrorTypeEnum.GENERIC;
|
||||
|
||||
let message: string | null = null;
|
||||
let shouldLogError: boolean = true;
|
||||
let customTags: ErrorCustomTags = {};
|
||||
|
||||
if (error instanceof ApiException) {
|
||||
appErrorType = getApiAdapterUrlExceptionAppErrorType(error);
|
||||
contextData = getApiAdapterUrlExceptionContextData(error, contextData);
|
||||
customTags = getApiAdapterUrlExceptionCustomTags(error);
|
||||
message = getApiAdapterUrlExceptionMessage(error, "API");
|
||||
} else if (error instanceof ApiAdapterDataException) {
|
||||
appErrorType = getApiAdapterUrlExceptionAppErrorType(error);
|
||||
contextData = getApiAdapterUrlExceptionContextData(error, contextData);
|
||||
customTags = getApiAdapterUrlExceptionCustomTags(error);
|
||||
message = getApiAdapterUrlExceptionMessage(error, "API Data");
|
||||
} else if (error instanceof AuthException) {
|
||||
appErrorType = AppErrorTypeEnum.AUTH_FAIL;
|
||||
message = "Auth Failure";
|
||||
|
||||
// We could emit multiple auth exceptions when we make parallel requests
|
||||
// AuthException isn't recoverable, so it should be fine to only log the first one
|
||||
if (hasDispatchedAuthException) {
|
||||
shouldLogError = false;
|
||||
} else {
|
||||
hasDispatchedAuthException = true;
|
||||
}
|
||||
} else if (error instanceof AuthMissingException) {
|
||||
appErrorType = AppErrorTypeEnum.AUTH_MISSING;
|
||||
shouldLogError = false;
|
||||
}
|
||||
|
||||
if (shouldLogError) {
|
||||
if (message === null) {
|
||||
ErrorUtils.dispatchException(error, null, customTags);
|
||||
} else {
|
||||
ErrorUtils.dispatchError(
|
||||
message,
|
||||
contextData,
|
||||
customTags,
|
||||
Constants.LogMessageType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const store = StateStoreUtils.getAppStore();
|
||||
if (store !== null) {
|
||||
const diceUrl = appEnvSelectors.getDiceFeatureConfiguration(
|
||||
store.getState()
|
||||
).apiEndpoint;
|
||||
if (error instanceof ApiException && error?.url?.includes(diceUrl)) {
|
||||
store.dispatch(
|
||||
appEnvActions.dataSet({
|
||||
diceEnabled: false,
|
||||
})
|
||||
);
|
||||
|
||||
AppNotificationUtils.dispatchError(
|
||||
"Dice Service Error",
|
||||
"The dice service is currently experiencing issues. We have disabled it temporarily as a result."
|
||||
);
|
||||
} else {
|
||||
store.dispatch(appInfoActions.errorSet(appErrorType));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param message
|
||||
* @param type
|
||||
* @param contextData
|
||||
*/
|
||||
export function logMessage(
|
||||
message: string,
|
||||
type: Constants.LogMessageType,
|
||||
contextData: Record<string, any> = {}
|
||||
): null {
|
||||
ErrorUtils.dispatchError(message, contextData, null, type);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
export function handleAdhocApiError(error: Error): void {
|
||||
// ApiAdapterException errors are handled by the AppApiAdapter
|
||||
if (!(error instanceof ApiAdapterException)) {
|
||||
logError(error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user