From 48caa86784645b84633468ac75e111bf64fcb6fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 08:13:20 +0000 Subject: [PATCH 1/2] Initial plan From 1de503b5c8eae272274f4265c4a00f3df4ea8bbe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 08:20:37 +0000 Subject: [PATCH 2/2] Add Brotli compression support for /octokit/release endpoint Co-authored-by: OpportunityLiu <13471233+OpportunityLiu@users.noreply.github.com> --- src/server/octokit/octokit.controller.ts | 61 ++++++++++++++++++------ 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/server/octokit/octokit.controller.ts b/src/server/octokit/octokit.controller.ts index e5384d75..b8f27b7c 100644 --- a/src/server/octokit/octokit.controller.ts +++ b/src/server/octokit/octokit.controller.ts @@ -1,7 +1,12 @@ -import { Controller, HttpCode, HttpStatus, Get, Header } from '@nestjs/common'; +import { Controller, HttpStatus, Get, Req, Res } from '@nestjs/common'; import { InjectableBase } from '../injectable-base.js'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { OctokitService } from './octokit.service.js'; +import type { FastifyRequest, FastifyReply } from 'fastify'; +import { brotliCompress } from 'node:zlib'; +import { promisify } from 'node:util'; + +const brotliCompressAsync = promisify(brotliCompress); @ApiTags('Octokit') @Controller('octokit') @@ -10,23 +15,49 @@ export class OctokitController extends InjectableBase { super(); } - private releaseCache: unknown = null; - @Get('release') - @ApiOperation({ summary: '获取最新版本', description: '代理 GitHub API' }) - @HttpCode(HttpStatus.OK) - @Header('Cache-Control', 'public, max-age=10, s-maxage=10') - async release(): Promise { - if (this.releaseCache) { - return this.releaseCache; - } + private releaseFetchPromise: Promise<{ json: string; br: Buffer }> | null = null; + private releaseCacheTimer: ReturnType | null = null; + + private async fetchRelease(): Promise<{ json: string; br: Buffer }> { const response = await this.octokit.forApp.repos.getLatestRelease({ owner: this.octokit.owner, repo: this.octokit.repo, }); - this.releaseCache = response.data; - setTimeout(() => { - this.releaseCache = null; - }, 10_000); - return this.releaseCache; + const json = JSON.stringify(response.data); + const br = await brotliCompressAsync(Buffer.from(json)); + return { json, br }; + } + + @Get('release') + @ApiOperation({ summary: '获取最新版本', description: '代理 GitHub API' }) + async release(@Req() req: FastifyRequest, @Res() res: FastifyReply): Promise { + if (!this.releaseFetchPromise) { + const promise = this.fetchRelease(); + this.releaseFetchPromise = promise; + void promise.then(() => { + if (this.releaseFetchPromise !== promise) return; + if (this.releaseCacheTimer) clearTimeout(this.releaseCacheTimer); + this.releaseCacheTimer = setTimeout(() => { + if (this.releaseFetchPromise === promise) this.releaseFetchPromise = null; + this.releaseCacheTimer = null; + }, 10_000); + }); + void promise.catch(() => { + if (this.releaseFetchPromise === promise) this.releaseFetchPromise = null; + }); + } + + const { json, br } = await this.releaseFetchPromise; + const acceptEncoding = req.headers['accept-encoding'] ?? ''; + const reply = res + .status(HttpStatus.OK) + .header('Cache-Control', 'public, max-age=10, s-maxage=10') + .header('Content-Type', 'application/json; charset=utf-8') + .header('Vary', 'Accept-Encoding'); + if (acceptEncoding.includes('br')) { + void reply.header('Content-Encoding', 'br').send(br); + } else { + void reply.send(json); + } } }