Skip to content

Commit e7685c5

Browse files
authored
impr: add X-Compatibility-Check header to all responses (@fehmer) (monkeytypegame#6262)
!nuf
1 parent 4ce62db commit e7685c5

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed

backend/src/app.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@ import {
88
badAuthRateLimiterHandler,
99
rootRateLimiter,
1010
} from "./middlewares/rate-limit";
11+
import { compatibilityCheckMiddleware } from "./middlewares/compatibilityCheck";
12+
import { COMPATIBILITY_CHECK_HEADER } from "@monkeytype/contracts";
1113

1214
function buildApp(): express.Application {
1315
const app = express();
1416

1517
app.use(urlencoded({ extended: true }));
1618
app.use(json());
17-
app.use(cors());
19+
app.use(cors({ exposedHeaders: [COMPATIBILITY_CHECK_HEADER] }));
1820
app.use(helmet());
1921

2022
app.set("trust proxy", 1);
2123

24+
app.use(compatibilityCheckMiddleware);
2225
app.use(contextMiddleware);
2326

2427
app.use(badAuthRateLimiterHandler);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {
2+
COMPATIBILITY_CHECK,
3+
COMPATIBILITY_CHECK_HEADER,
4+
} from "@monkeytype/contracts";
5+
import type { Response, NextFunction, Request } from "express";
6+
7+
/**
8+
* Add the COMPATIBILITY_CHECK_HEADER to each response
9+
* @param _req
10+
* @param res
11+
* @param next
12+
*/
13+
export async function compatibilityCheckMiddleware(
14+
_req: Request,
15+
res: Response,
16+
next: NextFunction
17+
): Promise<void> {
18+
res.setHeader(COMPATIBILITY_CHECK_HEADER, COMPATIBILITY_CHECK);
19+
next();
20+
}

frontend/src/ts/ape/adapters/ts-rest-adapter.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import {
77
import { getIdToken } from "firebase/auth";
88
import { envConfig } from "../../constants/env-config";
99
import { getAuthenticatedUser, isAuthenticated } from "../../firebase";
10+
import {
11+
COMPATIBILITY_CHECK,
12+
COMPATIBILITY_CHECK_HEADER,
13+
} from "@monkeytype/contracts";
14+
import * as Notifications from "../../elements/notifications";
15+
16+
let bannerShownThisSession = false;
1017

1118
function timeoutSignal(ms: number): AbortSignal {
1219
const ctrl = new AbortController();
@@ -35,14 +42,35 @@ function buildApi(timeout: number): (args: ApiFetcherArgs) => Promise<{
3542
: AbortSignal.timeout(timeout),
3643
};
3744
const response = await tsRestFetchApi(request);
38-
3945
if (response.status >= 400) {
4046
console.error(`${request.method} ${request.path} failed`, {
4147
status: response.status,
4248
...(response.body as object),
4349
});
4450
}
4551

52+
const compatibilityCheckHeader = response.headers.get(
53+
COMPATIBILITY_CHECK_HEADER
54+
);
55+
if (compatibilityCheckHeader !== null && !bannerShownThisSession) {
56+
const backendCheck = parseInt(compatibilityCheckHeader);
57+
if (backendCheck !== COMPATIBILITY_CHECK) {
58+
const message =
59+
backendCheck > COMPATIBILITY_CHECK
60+
? `Looks like the client and server versions are mismatched (backend is newer). Please <a onClick="location.reload(true)">refresh</a> the page.`
61+
: `Looks like our monkeys didn't deploy the new server version correctly. If this message persists contact support.`;
62+
Notifications.addBanner(
63+
message,
64+
1,
65+
undefined,
66+
false,
67+
undefined,
68+
true
69+
);
70+
bannerShownThisSession = true;
71+
}
72+
}
73+
4674
return response;
4775
} catch (e: Error | unknown) {
4876
let message = "Unknown error";

packages/contracts/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,10 @@ export const contract = c.router({
3030
quotes: quotesContract,
3131
webhooks: webhooksContract,
3232
});
33+
34+
/**
35+
* Whenever there is a breaking change with old frontend clients increase this number.
36+
* This will inform the frontend to refresh.
37+
*/
38+
export const COMPATIBILITY_CHECK = 0;
39+
export const COMPATIBILITY_CHECK_HEADER = "X-Compatibility-Check";

0 commit comments

Comments
 (0)