better code: typing
Massimo Melina committed
Dec 14, 2023 at 16:57 UTC
fbef1a9771d749ee33c684b8c7f1fb51420945c8
11 files changed
+71
-27
src/api.auth.ts
+4
-4
@@ -43,8 +43,8 @@ export const loginSrp1: ApiHandler = async ({ username }, ctx) => {
43
if (!ctx.session)
44
return new ApiError(HTTP_SERVER_ERROR)
45
if (!account || !accountCanLogin(account)) { // TODO simulate fake account to prevent knowing valid usernames
46
- ctx.state.logExtra = { u: username }
47
- ctx.state.dont_log = false // log even if log_api is false
46
+ ctx.logExtra({ u: username })
47
+ ctx.state.dontLog = false // log even if log_api is false
48
return new ApiError(HTTP_UNAUTHORIZED)
49
}
50
try {
@@ -80,8 +80,8 @@ export const loginSrp2: ApiHandler = async ({ pubKey, proof }, ctx) => {
80
}
81
}
82
catch(e) {
83
- ctx.state.dont_log = false // log even if log_api is false
84
- ctx.state.logExtra = { u: username }
83
+ ctx.logExtra({ u: username })
84
+ ctx.state.dontLog = false // log even if log_api is false
85
return new ApiError(HTTP_UNAUTHORIZED, String(e))
86
}
87
finally {
src/apiMiddleware.ts
+1
-1
@@ -23,7 +23,7 @@ const logApi = defineConfig('log_api', true)
23
export function apiMiddleware(apis: ApiHandlers) : Koa.Middleware {
24
return async (ctx) => {
25
if (!logApi.get())
26
- ctx.state.dont_log = true
26
+ ctx.state.dontLog = true
27
const isPost = ctx.params
28
const params = isPost ? ctx.params || {} : ctx.query
29
const apiName = ctx.path
src/cross.ts
+4
-4
@@ -129,6 +129,10 @@ export function enforceFinal(sub:string, s:string, evenEmpty=false) {
129
return !evenEmpty && !s || s.endsWith(sub) ? s : s+sub
130
}
131
132
+export function removeStarting(sub: string, s: string) {
133
+ return s.startsWith(sub) ? s.slice(sub.length) : s
134
+}
135
+
136
export function splitAt(sub: string | number, all: string): [string, string] {
137
if (typeof sub === 'number')
138
return [all.slice(0, sub), all.slice(sub + 1)]
@@ -231,10 +235,6 @@ export function findDefined<I, O>(a: I[] | Record<string, I>, cb:(v:I, k: string
235
}
236
}
237
234
-export function removeStarting(sub: string, s: string) {
235
- return s.startsWith(sub) ? s.slice(sub.length) : s
236
-}
237
-
238
export function newObj<S extends (object | undefined | null),VR=any>(
239
src: S,
240
returnNewValue: (value:Truthy<S[keyof S]>, key: Exclude<keyof S, symbol>, setK:(newK?: string)=>true, depth: number) => any,
src/listen.ts
+1
-2
@@ -85,8 +85,7 @@ const considerHttps = debounceAsync(async () => {
85
defaultBaseUrl.port = getCurrentPort(httpSrv) ?? 0
86
let port = httpsPortCfg.get()
87
try {
88
- while (!app)
89
- await wait(100)
88
+ await waitFor(() => app)
89
httpsSrv = Object.assign(
90
https.createServer(port === PORT_DISABLED ? {} : { ...commonOptions, key: httpsOptions.private_key, cert: httpsOptions.cert }, app.callback()),
91
{ name: 'https' }
src/log.ts
+27
-8
@@ -9,9 +9,10 @@ import { stat } from 'fs/promises'
9
import _ from 'lodash'
10
import { createFileWithPath, prepareFolder } from './util-files'
11
import { getCurrentUsername } from './auth'
12
-import { DAY, makeNetMatcher, tryJson } from './misc'
12
+import { DAY, makeNetMatcher, tryJson, Dict, Falsy } from './misc'
13
import events from './events'
14
import { getConnection } from './connections'
15
+import { app } from './index'
16
17
class Logger {
18
stream?: Writable
@@ -73,7 +74,7 @@ export const logMw: Koa.Middleware = async (ctx, next) => {
74
// don't await, as we don't want to hold the middlewares chain
75
ctx.state.completed = Promise.race([ once(ctx.res, 'finish'), once(ctx.res, 'close') ])
76
ctx.state.completed.then(() => {
76
- if (ctx.state.dont_log) return
77
+ if (ctx.state.dontLog) return
78
if (dontLogNet.compiled()(ctx.ip)) return
79
const isError = ctx.status >= 400
80
const logger = isError && accessErrorLog || accessLogger
@@ -105,14 +106,14 @@ export const logMw: Koa.Middleware = async (ctx, next) => {
106
const user = getCurrentUsername(ctx)
107
const length = ctx.state.length ?? ctx.length
108
const uri = ctx.originalUrl
108
- let extra = ctx.state.includesLastByte && ctx.vfsNode && ctx.res.finished && { dl: 1 }
109
- || ctx.state.uploadPath && { ul: ctx.state.uploadPath, size: ctx.state.uploadSize }
109
+ ctx.logExtra(ctx.state.includesLastByte && ctx.vfsNode && ctx.res.finished && { dl: 1 }
110
+ || ctx.state.uploadPath && { ul: ctx.state.uploadPath, size: ctx.state.uploadSize })
111
const conn = getConnection(ctx)
112
if (conn?.country)
112
- Object.assign(extra ||= {}, { country: conn.country })
113
- extra = extra ? Object.assign(extra, ctx.state.logExtra) : ctx.state.logExtra
113
+ ctx.logExtra({ country: conn.country })
114
if (logUA.get())
115
- extra = Object.assign({ ua: ctx.get('user-agent') }, extra)
115
+ ctx.logExtra({ ua: ctx.get('user-agent') })
116
+ const extra = ctx.state.logExtra
117
events.emit(logger.name, Object.assign(_.pick(ctx, ['ip', 'method','status']), { length, user, ts: now, uri, extra }))
118
debounce(() => // once in a while we check if the file is still good (not deleted, etc), or we'll reopen it
119
stat(logger.path).catch(() => logger.reopen())) // async = smoother but we may lose some entries
@@ -125,11 +126,29 @@ export const logMw: Koa.Middleware = async (ctx, next) => {
126
ctx.req.httpVersion,
127
ctx.status,
128
length?.toString() ?? '-',
128
- extra ? JSON.stringify(JSON.stringify(extra)) : '',
129
+ _.isEmpty(extra) ? '' : JSON.stringify(JSON.stringify(extra)),
130
))
131
})
132
}
133
134
+declare module "koa" {
135
+ interface BaseContext {
136
+ logExtra(o: Falsy | Dict<any>): void
137
+ }
138
+ interface DefaultState {
139
+ dontLog?: boolean // don't log this request
140
+ logExtra?: object
141
+ completed?: Promise<unknown>
142
+ }
143
+}
144
+
145
+events.on('app', () => { // wait for app to be set
146
+ app.context.logExtra = function(o) { // no => as we need 'this'
147
+ if (o)
148
+ Object.assign((this as any).state.logExtra ||= {}, o)
149
+ }
150
+})
151
+
152
function doubleDigit(n: number) {
153
return n > 9 ? n : '0'+n
154
}
src/middlewares.ts
+5
-5
@@ -192,7 +192,7 @@ export const someSecurity: Koa.Middleware = async (ctx, next) => {
192
// we have some dev-proxies to ignore
193
&& !(DEV && [process.env.FRONTEND_PROXY, process.env.ADMIN_PROXY].includes(ctx.get('X-Forwarded-port')))) {
194
proxyDetected = ctx
195
- ctx.state.when = new Date()
195
+ ctx.state.whenProxyDetected = new Date()
196
}
197
}
198
catch {
@@ -205,7 +205,7 @@ export const someSecurity: Koa.Middleware = async (ctx, next) => {
205
206
// limited to http proxies
207
export function getProxyDetected() {
208
- if (proxyDetected?.state.when < Date.now() - DAY)
208
+ if (proxyDetected?.state.whenProxyDetected < Date.now() - DAY)
209
proxyDetected = undefined
210
return !ignoreProxies.get() && proxyDetected
211
&& { from: proxyDetected.ip, for: proxyDetected.get('X-Forwarded-For') }
@@ -251,11 +251,11 @@ declare module "koa" {
251
params: Record<string, any>
252
}
253
interface DefaultState {
254
- account?: Account
254
+ account?: Account // user logged in
255
revProxyPath: string
256
connection: Connection
257
- serveApp?: boolean
258
- browsing?: string
257
+ serveApp?: boolean // please, serve the frontend app
258
+ browsing?: string // for admin/monitoring
259
}
260
}
261
export const paramsDecoder: Koa.Middleware = async (ctx, next) => {
src/serveFile.ts
+6
@@ -119,3 +119,9 @@ export function getRange(ctx: Koa.Context, totalSize: number) {
119
ctx.response.length = end - start + 1
120
return { start, end }
121
}
122
+
123
+declare module "koa" {
124
+ interface DefaultState {
125
+ includesLastByte?: boolean
126
+ }
127
+}
\ No newline at end of file
src/serveGuiFiles.ts
+2
-2
@@ -29,7 +29,7 @@ function serveStatic(uri: string): Koa.Middleware {
29
subscribe(customHtmlState, () => cache = {}) // reset cache at every change
30
return async (ctx) => {
31
if (!logGui.get())
32
- ctx.state.dont_log = true
32
+ ctx.state.dontLog = true
33
if(ctx.method === 'OPTIONS') {
34
ctx.status = HTTP_NO_CONTENT
35
ctx.set({ Allow: 'OPTIONS, GET' })
@@ -149,7 +149,7 @@ function serveProxied(port: string | undefined, uri: string) { // used for devel
149
}) )
150
return function (ctx, next) {
151
if (!logGui.get())
152
- ctx.state.dont_log = true
152
+ ctx.state.dontLog = true
153
return proxy(ctx, next)
154
} as Koa.Middleware
155
}
src/throttler.ts
+8
-1
@@ -39,7 +39,7 @@ export const throttler: Koa.Middleware = async (ctx, next) => {
39
group.updateLimit(v))
40
return { group, count:0, destroy: unsub }
41
})
42
- const conn = ctx.state.connection as Connection | undefined
42
+ const conn = ctx.state.connection
43
if (!conn) throw 'assert throttler connection'
44
45
const ts = conn[SymThrStr] = new ThrottledStream(ipGroup.group, conn[SymThrStr])
@@ -85,6 +85,13 @@ export const throttler: Koa.Middleware = async (ctx, next) => {
85
ctx.state.length = ts.getBytesSent() - offset)
86
}
87
88
+declare module "koa" {
89
+ interface DefaultState {
90
+ length?: number
91
+ originalStream?: Parameters<Koa.Middleware>[0]['body']
92
+ }
93
+}
94
+
95
export function roundSpeed(n: number) {
96
return _.round(n, 1) || _.round(n, 3) // further precision if necessary
97
}
src/upload.ts
+7
@@ -159,3 +159,10 @@ export function uploadWriter(base: VfsNode, path: string, ctx: Koa.Context) {
159
notifyClient(ctx, 'upload.status', { [path]: ctx.status }) // allow browsers to detect failure while still sending body
160
}
161
}
162
+
163
+declare module "koa" {
164
+ interface DefaultState {
165
+ uploadSize?: number
166
+ uploadPath?: string
167
+ }
168
+}
\ No newline at end of file
src/zip.ts
+6
@@ -78,3 +78,9 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
78
}
79
80
const zipSeconds = defineConfig('zip_calculate_size_for_seconds', 1)
81
+
82
+declare module "koa" {
83
+ interface DefaultState {
84
+ archive?: string
85
+ }
86
+}
\ No newline at end of file