From 720b9755f43a2084abf80c449daa3d8e3cf5cc4d Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Tue, 3 Dec 2024 01:22:45 +0100 Subject: [PATCH 1/3] handle callback failed --- packages/uploadthing/src/internal/handler.ts | 47 +++++++++++++++++--- playground/middleware.ts | 22 +++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 playground/middleware.ts diff --git a/packages/uploadthing/src/internal/handler.ts b/packages/uploadthing/src/internal/handler.ts index 727fdbaaa5..108f2399d6 100644 --- a/packages/uploadthing/src/internal/handler.ts +++ b/packages/uploadthing/src/internal/handler.ts @@ -8,6 +8,7 @@ import { HttpServerRequest, HttpServerResponse, } from "@effect/platform"; +import type { ResponseError } from "@effect/platform/HttpClientError"; import * as Config from "effect/Config"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; @@ -601,6 +602,40 @@ const handleUploadAction = (opts: { Effect.flatMap(httpClient.execute), ); + const handleDevStreamError = (err: ResponseError, chunk: string) => + Effect.gen(function* () { + const { + file: { key }, + } = yield* Schema.decodeUnknown( + Schema.parseJson(Schema.Struct({ file: UploadedFileData })), + )(chunk); + + yield* Effect.logError( + "Failed to forward callback request from dev stream", + ).pipe(Effect.annotateLogs({ fileKey: key, error: err.message })); + + const httpResponse = yield* HttpClientRequest.post( + "/callback-result", + ).pipe( + HttpClientRequest.prependUrl(ingestUrl), + HttpClientRequest.setHeaders({ + "x-uploadthing-api-key": Redacted.value(apiKey), + "x-uploadthing-version": pkgJson.version, + "x-uploadthing-be-adapter": beAdapter, + "x-uploadthing-fe-package": fePackage, + }), + HttpClientRequest.bodyJson({ + fileKey: key, + error: `Failed to forward callback request from dev stream: ${err.message}`, + }), + Effect.flatMap(httpClient.execute), + ); + + yield* logHttpClientResponse("Reported callback error to UploadThing")( + httpResponse, + ); + }); + // Send metadata to UT server (non blocking as a daemon) // In dev, keep the stream open and simulate the callback requests as // files complete uploading @@ -624,14 +659,14 @@ const handleUploadAction = (opts: { HttpBody.text(chunk.payload, "application/json"), ), httpClient.execute, - Effect.tapBoth({ - onSuccess: logHttpClientResponse( + Effect.tap( + logHttpClientResponse( "Successfully forwarded callback request from dev stream", ), - onFailure: logHttpClientError( - "Failed to forward callback request from dev stream", - ), - }), + ), + Effect.catchTag("ResponseError", (err) => + handleDevStreamError(err, chunk.payload), + ), Effect.annotateLogs(chunk), Effect.asVoid, Effect.ignoreLogged, diff --git a/playground/middleware.ts b/playground/middleware.ts new file mode 100644 index 0000000000..d777adaa93 --- /dev/null +++ b/playground/middleware.ts @@ -0,0 +1,22 @@ +import { + MiddlewareConfig, + NextResponse, + type NextMiddleware, +} from "next/server"; + +import { getSession } from "./lib/data"; + +export default (async (req) => { + if (req.nextUrl.pathname !== "/") { + const sesh = await getSession(); + if (!sesh) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + } + + return NextResponse.next(); +}) satisfies NextMiddleware; + +export const config = { + matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"], +} satisfies MiddlewareConfig; From 2ee0d0ca86550b59c6398248540cfd4ddc8b5379 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Fri, 13 Dec 2024 15:02:01 +0100 Subject: [PATCH 2/3] handle it in the browser --- .../src/internal/upload.browser.ts | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/uploadthing/src/internal/upload.browser.ts b/packages/uploadthing/src/internal/upload.browser.ts index 1d9d53f083..600dad60d7 100644 --- a/packages/uploadthing/src/internal/upload.browser.ts +++ b/packages/uploadthing/src/internal/upload.browser.ts @@ -1,5 +1,6 @@ import { unsafeCoerce } from "effect/Function"; import * as Micro from "effect/Micro"; +import { hasProperty, isRecord } from "effect/Predicate"; import type { FetchError } from "@uploadthing/shared"; import { FetchContext, fetchEff, UploadThingError } from "@uploadthing/shared"; @@ -37,13 +38,27 @@ const uploadWithProgress = ( previousLoaded = loaded; }); xhr.addEventListener("load", () => { - resume( - xhr.status >= 200 && xhr.status < 300 - ? Micro.succeed(xhr.response) - : Micro.die( - `XHR failed ${xhr.status} ${xhr.statusText} - ${JSON.stringify(xhr.response)}`, - ), - ); + if (xhr.status >= 200 && xhr.status < 300 && isRecord(xhr.response)) { + if (hasProperty(xhr.response, "error")) { + resume( + new UploadThingError({ + code: "UPLOAD_FAILED", + message: String(xhr.response.error), + data: xhr.response as never, + }), + ); + } else { + resume(Micro.succeed(xhr.response)); + } + } else { + resume( + new UploadThingError({ + code: "UPLOAD_FAILED", + message: `XHR failed ${xhr.status} ${xhr.statusText}`, + data: xhr.response as never, + }), + ); + } }); // Is there a case when the client would throw and From 32f3f6acac9b54505d4b9a1f5bd8a66445c5776b Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sat, 4 Jan 2025 03:26:37 +0100 Subject: [PATCH 3/3] cool --- packages/uploadthing/src/_internal/handler.ts | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/packages/uploadthing/src/_internal/handler.ts b/packages/uploadthing/src/_internal/handler.ts index 1315f1b796..cc591dcada 100644 --- a/packages/uploadthing/src/_internal/handler.ts +++ b/packages/uploadthing/src/_internal/handler.ts @@ -602,39 +602,41 @@ const handleUploadAction = (opts: { Effect.flatMap(httpClient.execute), ); - const handleDevStreamError = (err: ResponseError, chunk: string) => - Effect.gen(function* () { - const { - file: { key }, - } = yield* Schema.decodeUnknown( - Schema.parseJson(Schema.Struct({ file: UploadedFileData })), - )(chunk); - - yield* Effect.logError( - "Failed to forward callback request from dev stream", - ).pipe(Effect.annotateLogs({ fileKey: key, error: err.message })); - - const httpResponse = yield* HttpClientRequest.post( - "/callback-result", - ).pipe( - HttpClientRequest.prependUrl(ingestUrl), - HttpClientRequest.setHeaders({ - "x-uploadthing-api-key": Redacted.value(apiKey), - "x-uploadthing-version": pkgJson.version, - "x-uploadthing-be-adapter": beAdapter, - "x-uploadthing-fe-package": fePackage, - }), - HttpClientRequest.bodyJson({ - fileKey: key, - error: `Failed to forward callback request from dev stream: ${err.message}`, - }), - Effect.flatMap(httpClient.execute), - ); + const handleDevStreamError = Effect.fn("handleDevStreamError")(function* ( + err: ResponseError, + chunk: string, + ) { + const schema = Schema.parseJson( + Schema.Struct({ file: UploadedFileData }), + ); + const parsedChunk = yield* Schema.decodeUnknown(schema)(chunk); + const key = parsedChunk.file.key; - yield* logHttpClientResponse("Reported callback error to UploadThing")( - httpResponse, - ); - }); + yield* Effect.logError( + "Failed to forward callback request from dev stream", + ).pipe(Effect.annotateLogs({ fileKey: key, error: err.message })); + + const httpResponse = yield* HttpClientRequest.post( + "/callback-result", + ).pipe( + HttpClientRequest.prependUrl(ingestUrl), + HttpClientRequest.setHeaders({ + "x-uploadthing-api-key": Redacted.value(apiKey), + "x-uploadthing-version": pkgJson.version, + "x-uploadthing-be-adapter": beAdapter, + "x-uploadthing-fe-package": fePackage, + }), + HttpClientRequest.bodyJson({ + fileKey: key, + error: `Failed to forward callback request from dev stream: ${err.message}`, + }), + Effect.flatMap(httpClient.execute), + ); + + yield* logHttpClientResponse("Reported callback error to UploadThing")( + httpResponse, + ); + }); // Send metadata to UT server (non blocking as a daemon) // In dev, keep the stream open and simulate the callback requests as