mirror of
https://github.com/fluxerapp/fluxer.git
synced 2026-09-02 21:04:06 +03:00
feat(api): serve well-known discovery with etag and 304 (#2349)
This commit is contained in:
@@ -36,6 +36,20 @@ export function configureMiddleware(routes: HonoApp, options: MiddlewarePipeline
|
||||
const resolvedHeader = resolveClientIpHeaderName(clientIpHeaderName);
|
||||
routes.use('/webhooks/:webhook_id/:token', cors({origins: '*'}));
|
||||
routes.use('/webhooks/:webhook_id/:token/messages/:message_id', cors({origins: '*'}));
|
||||
routes.use(
|
||||
'/.well-known/fluxer',
|
||||
cors({
|
||||
origins: '*',
|
||||
methods: ['GET', 'HEAD', 'OPTIONS'],
|
||||
allowedHeaders: [
|
||||
HttpHeaders.ACCEPT,
|
||||
HttpHeaders.CONTENT_TYPE,
|
||||
HttpHeaders.IF_MODIFIED_SINCE,
|
||||
HttpHeaders.IF_NONE_MATCH,
|
||||
],
|
||||
exposedHeaders: [HttpHeaders.ETAG, HttpHeaders.LAST_MODIFIED],
|
||||
}),
|
||||
);
|
||||
applyMiddlewareStack(routes, {
|
||||
requestId: {},
|
||||
cors: {origins: corsOrigins, exposedHeaders: [HttpHeaders.X_FLUXER_VERSION]},
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createHash} from 'node:crypto';
|
||||
import {entityTagMatches} from '../utils/EntityTag';
|
||||
|
||||
export interface DiscoveryValidators {
|
||||
etag: string;
|
||||
lastModified: Date;
|
||||
}
|
||||
|
||||
export function nextDiscoveryValidators(document: unknown, previous: DiscoveryValidators | null): DiscoveryValidators {
|
||||
const etag = `"${createHash('sha256').update(JSON.stringify(document)).digest('hex')}"`;
|
||||
if (previous !== null && previous.etag === etag) {
|
||||
return previous;
|
||||
}
|
||||
return {etag, lastModified: new Date(Math.floor(Date.now() / 1000) * 1000)};
|
||||
}
|
||||
|
||||
export function isDiscoveryNotModified(
|
||||
validators: DiscoveryValidators,
|
||||
ifNoneMatch: string | undefined,
|
||||
ifModifiedSince: string | undefined,
|
||||
): boolean {
|
||||
if (ifNoneMatch !== undefined) {
|
||||
return entityTagMatches(ifNoneMatch, validators.etag);
|
||||
}
|
||||
if (ifModifiedSince === undefined) {
|
||||
return false;
|
||||
}
|
||||
const since = Date.parse(ifModifiedSince);
|
||||
return !Number.isNaN(since) && validators.lastModified.getTime() <= since;
|
||||
}
|
||||
@@ -13,8 +13,11 @@ import {RateLimitMiddleware} from '../middleware/RateLimitMiddleware';
|
||||
import {OpenAPI} from '../middleware/ResponseTypeMiddleware';
|
||||
import {RateLimitConfigs} from '../RateLimitConfig';
|
||||
import type {HonoEnv} from '../types/HonoEnv';
|
||||
import {type DiscoveryValidators, isDiscoveryNotModified, nextDiscoveryValidators} from './DiscoveryValidators';
|
||||
import type {InstanceCaptchaEffectiveConfig} from './InstanceConfigRepository';
|
||||
|
||||
let discoveryValidators: DiscoveryValidators | null = null;
|
||||
|
||||
function buildDiscoveryStaticInput(
|
||||
gifService: GifService | undefined,
|
||||
appPublic: InstanceAppPublic,
|
||||
@@ -131,6 +134,18 @@ export function InstanceController(app: Hono<HonoEnv>) {
|
||||
limits,
|
||||
},
|
||||
);
|
||||
discoveryValidators = nextDiscoveryValidators(response, discoveryValidators);
|
||||
ctx.header('ETag', discoveryValidators.etag);
|
||||
ctx.header('Last-Modified', discoveryValidators.lastModified.toUTCString());
|
||||
if (
|
||||
isDiscoveryNotModified(
|
||||
discoveryValidators,
|
||||
ctx.req.header('If-None-Match'),
|
||||
ctx.req.header('If-Modified-Since'),
|
||||
)
|
||||
) {
|
||||
return ctx.body(null, 304);
|
||||
}
|
||||
return ctx.json(response);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
function stripWeakness(entityTag: string): string {
|
||||
return entityTag.startsWith('W/') ? entityTag.slice(2) : entityTag;
|
||||
}
|
||||
|
||||
export function entityTagMatches(ifNoneMatch: string, etag: string): boolean {
|
||||
const target = stripWeakness(etag.trim());
|
||||
return ifNoneMatch
|
||||
.split(',')
|
||||
.map((candidate) => candidate.trim())
|
||||
.some((candidate) => candidate === '*' || stripWeakness(candidate) === target);
|
||||
}
|
||||
Reference in New Issue
Block a user