mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-24 06:58:10 -05:00
NR
This commit is contained in:
parent
a3757199aa
commit
a1dba004ad
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -22,3 +22,5 @@ dump
|
|||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
|
||||
newrelic_agent.log
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
import { PassThrough } from "stream";
|
||||
|
||||
import {
|
||||
type ActionFunctionArgs,
|
||||
type LoaderFunctionArgs,
|
||||
createReadableStreamFromReadable,
|
||||
type EntryContext,
|
||||
} from "@remix-run/node";
|
||||
import { RemixServer } from "@remix-run/react";
|
||||
import { isbot } from "isbot";
|
||||
import { renderToPipeableStream } from "react-dom/server";
|
||||
import cron from "node-cron";
|
||||
import { updatePatreonData } from "./modules/patreon";
|
||||
import { i18Instance } from "./modules/i18n";
|
||||
import { renderToPipeableStream } from "react-dom/server";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { getUser } from "./features/auth/core";
|
||||
import { i18Instance } from "./modules/i18n";
|
||||
import { updatePatreonData } from "./modules/patreon";
|
||||
import { noticeError, setTransactionName } from "./utils/newrelic.server";
|
||||
|
||||
const ABORT_DELAY = 5000;
|
||||
|
||||
|
|
@ -21,6 +25,14 @@ const handleRequest = (
|
|||
remixContext: EntryContext,
|
||||
) => {
|
||||
const userAgent = request.headers.get("user-agent");
|
||||
|
||||
const lastMatch =
|
||||
remixContext.staticHandlerContext.matches[
|
||||
remixContext.staticHandlerContext.matches.length - 1
|
||||
];
|
||||
|
||||
if (lastMatch) setTransactionName(`ssr/${lastMatch.route.id}`);
|
||||
|
||||
return userAgent && isbot(userAgent)
|
||||
? handleBotRequest(
|
||||
request,
|
||||
|
|
@ -37,6 +49,16 @@ const handleRequest = (
|
|||
};
|
||||
export default handleRequest;
|
||||
|
||||
export function handleDataRequest(
|
||||
response: Response,
|
||||
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
|
||||
) {
|
||||
const name = new URL(request.url).searchParams.get("_data");
|
||||
if (name) setTransactionName(name);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
const handleBotRequest = (
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
|
|
@ -125,6 +147,23 @@ const handleBrowserRequest = (
|
|||
});
|
||||
});
|
||||
|
||||
export async function handleError(
|
||||
error: unknown,
|
||||
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
|
||||
) {
|
||||
const user = await getUser(request);
|
||||
if (!request.signal.aborted) {
|
||||
if (error instanceof Error) {
|
||||
noticeError(error, {
|
||||
"enduser.id": user?.id,
|
||||
// TODO: FetchError: Invalid response body while trying to fetch http://localhost:5800/admin?_data=features%2Fadmin%2Froutes%2Fadmin: This stream has already been locked for exclusive reading by another reader
|
||||
// formData: JSON.stringify(formDataToObject(await request.formData())),
|
||||
});
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// example from https://github.com/BenMcH/remix-rss/blob/main/app/entry.server.tsx
|
||||
declare global {
|
||||
var appStartSignal: undefined | true;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import { CUSTOMIZED_CSS_VARS_NAME } from "./constants";
|
|||
import NProgress from "nprogress";
|
||||
import nProgressStyles from "nprogress/nprogress.css";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { browserTimingHeader } from "./utils/newrelic.server";
|
||||
|
||||
export const shouldRevalidate: ShouldRevalidateFunction = ({ nextUrl }) => {
|
||||
// // reload on language change so the selected language gets set into the cookie
|
||||
|
|
@ -97,6 +98,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|||
publisherId: process.env["PLAYWIRE_PUBLISHER_ID"],
|
||||
websiteId: process.env["PLAYWIRE_WEBSITE_ID"],
|
||||
loginDisabled: process.env["LOGIN_DISABLED"] === "true",
|
||||
browserTimingHeader: browserTimingHeader(),
|
||||
user: user
|
||||
? {
|
||||
discordName: user.discordName,
|
||||
|
|
@ -163,6 +165,12 @@ function Document({
|
|||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/brackets-viewer@1.5.1/dist/brackets-viewer.min.js"
|
||||
/>
|
||||
{data?.browserTimingHeader ? (
|
||||
<script
|
||||
type="text/javascript"
|
||||
dangerouslySetInnerHTML={{ __html: data?.browserTimingHeader }}
|
||||
/>
|
||||
) : null}
|
||||
<PWALinks />
|
||||
<Fonts />
|
||||
</head>
|
||||
|
|
|
|||
29
app/utils/newrelic.server.ts
Normal file
29
app/utils/newrelic.server.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
const isEnabled =
|
||||
process.env["NEW_RELIC_APP_NAME"] && process.env["NEW_RELIC_LICENSE_KEY"];
|
||||
|
||||
const newrelic = isEnabled ? require("newrelic") : {};
|
||||
|
||||
export const browserTimingHeader = () =>
|
||||
isEnabled
|
||||
? newrelic.getBrowserTimingHeader({
|
||||
hasToRemoveScriptWrapper: true,
|
||||
})
|
||||
: null;
|
||||
|
||||
export const noticeError = (
|
||||
error: Error,
|
||||
attributes?: {
|
||||
"enduser.id"?: number;
|
||||
formData?: string;
|
||||
searchParams?: string;
|
||||
params?: string;
|
||||
},
|
||||
) =>
|
||||
isEnabled &&
|
||||
newrelic.noticeError(error, {
|
||||
...attributes,
|
||||
"tags.commit": process.env["RENDER_GIT_COMMIT"],
|
||||
});
|
||||
|
||||
export const setTransactionName = (name: string) =>
|
||||
isEnabled && newrelic.setTransactionName(name);
|
||||
|
|
@ -3,6 +3,7 @@ import type { Params, UIMatch } from "@remix-run/react";
|
|||
import type navItems from "~/components/layout/nav-items.json";
|
||||
import { json } from "@remix-run/node";
|
||||
import type { Namespace, TFunction } from "i18next";
|
||||
import { noticeError } from "./newrelic.server";
|
||||
|
||||
export function notFoundIfFalsy<T>(value: T | null | undefined): T {
|
||||
if (!value) throw new Response(null, { status: 404 });
|
||||
|
|
@ -18,7 +19,10 @@ export function notFoundIfNullLike<T>(value: T | null | undefined): T {
|
|||
}
|
||||
|
||||
export function badRequestIfFalsy<T>(value: T | null | undefined): T {
|
||||
if (!value) throw new Response(null, { status: 400 });
|
||||
if (!value) {
|
||||
noticeError(new Error("Value is falsy"));
|
||||
throw new Response(null, { status: 400 });
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
|
@ -30,11 +34,14 @@ export function parseSearchParams<T extends z.ZodTypeAny>({
|
|||
request: Request;
|
||||
schema: T;
|
||||
}): z.infer<T> {
|
||||
const url = new URL(request.url);
|
||||
const searchParams = Object.fromEntries(url.searchParams);
|
||||
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
return schema.parse(Object.fromEntries(url.searchParams));
|
||||
return schema.parse(searchParams);
|
||||
} catch (e) {
|
||||
if (e instanceof z.ZodError) {
|
||||
noticeError(e, { searchParams: JSON.stringify(searchParams) });
|
||||
console.error(e);
|
||||
throw new Response(JSON.stringify(e), { status: 400 });
|
||||
}
|
||||
|
|
@ -64,14 +71,16 @@ export async function parseRequestFormData<T extends z.ZodTypeAny>({
|
|||
schema: T;
|
||||
parseAsync?: boolean;
|
||||
}): Promise<z.infer<T>> {
|
||||
const formDataObj = formDataToObject(await request.formData());
|
||||
try {
|
||||
const parsed = parseAsync
|
||||
? await schema.parseAsync(formDataToObject(await request.formData()))
|
||||
: schema.parse(formDataToObject(await request.formData()));
|
||||
? await schema.parseAsync(formDataObj)
|
||||
: schema.parse(formDataObj);
|
||||
|
||||
return parsed;
|
||||
} catch (e) {
|
||||
if (e instanceof z.ZodError) {
|
||||
noticeError(e, { formData: JSON.stringify(formDataObj) });
|
||||
console.error(e);
|
||||
throw new Response(JSON.stringify(e), { status: 400 });
|
||||
}
|
||||
|
|
@ -88,10 +97,12 @@ export function parseFormData<T extends z.ZodTypeAny>({
|
|||
formData: FormData;
|
||||
schema: T;
|
||||
}): z.infer<T> {
|
||||
const formDataObj = formDataToObject(formData);
|
||||
try {
|
||||
return schema.parse(formDataToObject(formData));
|
||||
return schema.parse(formDataObj);
|
||||
} catch (e) {
|
||||
if (e instanceof z.ZodError) {
|
||||
noticeError(e, { formData: JSON.stringify(formDataObj) });
|
||||
console.error(e);
|
||||
throw new Response(JSON.stringify(e), { status: 400 });
|
||||
}
|
||||
|
|
@ -112,6 +123,7 @@ export function parseParams<T extends z.ZodTypeAny>({
|
|||
return schema.parse(params);
|
||||
} catch (e) {
|
||||
if (e instanceof z.ZodError) {
|
||||
noticeError(e, { params: JSON.stringify(params) });
|
||||
console.error(e);
|
||||
throw new Response(JSON.stringify(e), { status: 400 });
|
||||
}
|
||||
|
|
@ -173,6 +185,7 @@ export function validate(
|
|||
): asserts condition {
|
||||
if (condition) return;
|
||||
|
||||
noticeError(new Error(`Validation error: ${message}`));
|
||||
throw new Response(
|
||||
message ? JSON.stringify({ validationError: message }) : undefined,
|
||||
{
|
||||
|
|
|
|||
2076
package-lock.json
generated
2076
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -10,6 +10,7 @@
|
|||
"dev:prod": "cross-env DB_PATH=db-prod.sqlite3 remix dev",
|
||||
"dev:ci": "cp .env.example .env && npm run migrate up && npm run dev",
|
||||
"start": "npm run migrate up && remix-serve ./build/index.js",
|
||||
"start:nr": "npm run migrate up && NODE_OPTIONS='-r dotenv/config -r newrelic' remix-serve ./build/index.js",
|
||||
"migrate": "ley",
|
||||
"migrate:reset": "node scripts/delete-db-files.mjs && npm run migrate up",
|
||||
"add-badge": "node --experimental-specifier-resolution=node -r @swc-node/register -r tsconfig-paths/register scripts/add-badge.ts",
|
||||
|
|
@ -86,6 +87,7 @@
|
|||
"lru-cache": "10.2.0",
|
||||
"markdown-to-jsx": "^7.4.1",
|
||||
"nanoid": "~5.0.5",
|
||||
"newrelic": "^11.10.3",
|
||||
"node-cron": "3.0.3",
|
||||
"nprogress": "^0.2.0",
|
||||
"openskill": "^3.1.0",
|
||||
|
|
@ -113,6 +115,7 @@
|
|||
"@swc-node/register": "^1.8.0",
|
||||
"@types/better-sqlite3": "7.6.9",
|
||||
"@types/i18next-fs-backend": "^1.1.5",
|
||||
"@types/newrelic": "^9.14.3",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/prettier": "3.0.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user