From 93536a021ee444743a523f54f3d53975087a88c1 Mon Sep 17 00:00:00 2001 From: Shigma Date: Fri, 12 Apr 2024 06:06:46 +0800 Subject: [PATCH] feat(temp): add server-temp --- packages/temp/.npmignore | 2 + packages/temp/package.json | 34 ++++++++++++ packages/temp/src/index.ts | 104 ++++++++++++++++++++++++++++++++++++ packages/temp/tsconfig.json | 12 +++++ 4 files changed, 152 insertions(+) create mode 100644 packages/temp/.npmignore create mode 100644 packages/temp/package.json create mode 100644 packages/temp/src/index.ts create mode 100644 packages/temp/tsconfig.json diff --git a/packages/temp/.npmignore b/packages/temp/.npmignore new file mode 100644 index 0000000..7e5fcbc --- /dev/null +++ b/packages/temp/.npmignore @@ -0,0 +1,2 @@ +.DS_Store +tsconfig.tsbuildinfo diff --git a/packages/temp/package.json b/packages/temp/package.json new file mode 100644 index 0000000..0b458ba --- /dev/null +++ b/packages/temp/package.json @@ -0,0 +1,34 @@ +{ + "name": "@cordisjs/server-temp", + "description": "Temp server plugin for cordis", + "version": "0.3.0", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib", + "src" + ], + "author": "Shigma ", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/cordisjs/server.git", + "directory": "packages/temp" + }, + "bugs": { + "url": "https://github.com/cordisjs/server/issues" + }, + "homepage": "https://github.com/cordisjs/server/tree/main/packages/temp", + "keywords": [ + "cordis", + "router", + "http", + "temporary", + "server", + "service", + "plugin" + ], + "devDependencies": { + "undios": "^0.3.2" + } +} diff --git a/packages/temp/src/index.ts b/packages/temp/src/index.ts new file mode 100644 index 0000000..a16c5ac --- /dev/null +++ b/packages/temp/src/index.ts @@ -0,0 +1,104 @@ +import { Context, Schema, Service } from 'cordis' +import { Dict, sanitize, Time } from 'cosmokit' +import {} from '@cordisjs/server' +import {} from 'undios' +import { createReadStream } from 'fs' +import { fileURLToPath } from 'url' +import { mkdir, rm, writeFile } from 'fs/promises' +import { Readable } from 'stream' + +declare module 'cordis' { + interface Context { + 'server.temp': TempServer + } +} + +export interface Entry { + path: string + url: string + dispose?: () => void +} + +class TempServer extends Service { + static [Service.provide] = 'server.temp' + static inject = ['server', 'http'] + + public path: string + public selfUrl!: string + public baseDir!: string + public entries: Dict = Object.create(null) + + constructor(protected ctx: Context, public config: TempServer.Config) { + super(ctx, 'server.temp', true) + const logger = ctx.logger('temp') + + this.path = sanitize(config.path) + this.selfUrl = config.selfUrl || ctx.server.config.selfUrl! + if (!this.selfUrl) { + logger.warn('missing selfUrl configuration') + } + + ctx.server.get(this.path + '/:name', async (koa) => { + logger.debug(koa.params.name) + const entry = this.entries[koa.params.name] + if (!entry) return koa.status = 404 + koa.body = createReadStream(entry.path) + }) + + ctx.on('ready', () => this.start()) + ctx.on('dispose', () => this.stop()) + } + + async start() { + this.baseDir = this.ctx.baseDir + '/temp/' + Math.random().toString(36).slice(2) + '/' + await mkdir(this.baseDir, { recursive: true }) + this.ctx.server.temp = this + } + + async stop() { + await rm(this.baseDir, { recursive: true }) + } + + async create(data: string | Buffer | ReadableStream): Promise { + const name = Math.random().toString(36).slice(2) + const url = this.selfUrl + this.path + '/' + name + let path: string + if (typeof data === 'string') { + if (new URL(data).protocol === 'file:') { + path = fileURLToPath(data) + } else { + const stream = await this.ctx.http.get(data, { responseType: 'stream' }) + path = this.baseDir + name + await writeFile(path, Readable.fromWeb(stream)) + } + } else { + path = this.baseDir + name + await writeFile(path, data instanceof ReadableStream ? Readable.fromWeb(data as any) : data) + } + return this[Context.origin].effect(() => { + const timer = setTimeout(() => dispose(), this.config.maxAge) + const dispose = async () => { + clearTimeout(timer) + delete this.entries[name] + if (path.startsWith(this.baseDir)) await rm(path) + } + return this.entries[name] = { path, url, dispose } + }) + } +} + +namespace TempServer { + export interface Config { + path: string + selfUrl?: string + maxAge?: number + } + + export const Config: Schema = Schema.object({ + path: Schema.string().default('/temp'), + selfUrl: Schema.string().role('link').description('此服务暴露在公网的地址。缺省时将使用全局配置。'), + maxAge: Schema.number().default(Time.minute * 5).description('临时文件的默认最大存活时间。'), + }) +} + +export default TempServer diff --git a/packages/temp/tsconfig.json b/packages/temp/tsconfig.json new file mode 100644 index 0000000..6f11f32 --- /dev/null +++ b/packages/temp/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "strict": true, + "noImplicitAny": false, + }, + "include": [ + "src", + ], +}