Skip to content

Commit e180ed3

Browse files
chore: initiate traceIds from client (#1210)
1 parent 08ddbfd commit e180ed3

File tree

7 files changed

+83
-13
lines changed

7 files changed

+83
-13
lines changed

packages/uploadthing/src/_internal/client-future.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import type {
1717
FileRouter as AnyFileRouter,
1818
NewPresignedUrl,
1919
} from "../types";
20+
import type { TraceHeaders } from "./random-hex";
21+
import { generateTraceHeaders } from "./random-hex";
2022
import type { UploadPutResult } from "./types";
2123
import { createUTReporter } from "./ut-reporter";
2224

@@ -377,6 +379,7 @@ export interface UploadFileOptions<TRoute extends AnyFileRoute> {
377379
files: AnyFile<TRoute>[];
378380
input: TRoute["$types"]["input"];
379381
onEvent: (event: UploadEvent<TRoute>) => void;
382+
traceHeaders: TraceHeaders;
380383

381384
XHRImpl: new () => XMLHttpRequest;
382385
}
@@ -391,7 +394,7 @@ export function uploadFile<TRoute extends AnyFileRoute>(
391394
url: string,
392395
{ file, files, XHRImpl, ...options }: UploadFileOptions<TRoute>,
393396
): Micro.Micro<UploadedFile<TRoute> | FailedFile<TRoute>, never, FetchContext> {
394-
return fetchEff(url, { method: "HEAD" }).pipe(
397+
return fetchEff(url, { method: "HEAD", headers: options.traceHeaders }).pipe(
395398
Micro.map(({ headers }) =>
396399
Number.parseInt(headers.get("x-ut-range-start") ?? "0"),
397400
),
@@ -411,6 +414,8 @@ export function uploadFile<TRoute extends AnyFileRoute>(
411414
const rangeStart = uploadingFile.sent;
412415
xhr.setRequestHeader("Range", `bytes=${rangeStart}-`);
413416
xhr.setRequestHeader("x-uploadthing-version", version);
417+
xhr.setRequestHeader("b3", options.traceHeaders.b3);
418+
xhr.setRequestHeader("traceparent", options.traceHeaders.traceparent);
414419
xhr.responseType = "json";
415420

416421
xhr.upload.addEventListener("progress", (ev) => {
@@ -525,6 +530,10 @@ export interface RequestPresignedUrlsOptions<
525530
* @example { Authorization: "Bearer 123" }
526531
*/
527532
headers?: HeadersInit | LazyArg<MaybePromise<HeadersInit>> | undefined;
533+
/**
534+
* Custom trace headers to send with the request
535+
*/
536+
traceHeaders: TraceHeaders;
528537
/**
529538
* The uploadthing package that is making this request, used to identify the client in the server logs
530539
* @example "@uploadthing/react"
@@ -551,6 +560,7 @@ export function requestPresignedUrls<
551560
package: options.package,
552561
url: options.url,
553562
headers: options.headers,
563+
traceHeaders: options.traceHeaders,
554564
});
555565

556566
return reportEventToUT("upload", {
@@ -592,14 +602,15 @@ export function uploadFiles<
592602
TEndpoint extends keyof TRouter,
593603
>(endpoint: TEndpoint, options: UploadFilesOptions<TRouter[TEndpoint]>) {
594604
const pendingFiles = options.files.map(makePendingFile);
595-
605+
const traceHeaders = generateTraceHeaders();
596606
return requestPresignedUrls({
597607
endpoint: endpoint,
598608
files: options.files,
599609
url: options.url,
600610
input: options.input,
601611
headers: options.headers,
602612
package: options.package,
613+
traceHeaders,
603614
}).pipe(
604615
Micro.map(Arr.zip(pendingFiles)),
605616
Micro.tap((pairs) => {
@@ -622,6 +633,7 @@ export function uploadFiles<
622633
input: options.input,
623634
onEvent: options.onEvent,
624635
XHRImpl: globalThis.XMLHttpRequest,
636+
traceHeaders,
625637
}),
626638
{ concurrency: 6 },
627639
),
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const randomHexString = (function () {
2+
const characters = "abcdef0123456789";
3+
const charactersLength = characters.length;
4+
return function (length: number) {
5+
let result = "";
6+
for (let i = 0; i < length; i++) {
7+
result += characters.charAt(Math.floor(Math.random() * charactersLength));
8+
}
9+
return result;
10+
};
11+
})();
12+
13+
export type TraceHeaders = {
14+
b3: string;
15+
traceparent: string;
16+
};
17+
18+
export const generateTraceHeaders = (): TraceHeaders => {
19+
const traceId = randomHexString(32);
20+
const spanId = randomHexString(16);
21+
const sampled = "01";
22+
23+
return {
24+
b3: `${traceId}-${spanId}-${sampled}`,
25+
traceparent: `00-${traceId}-${spanId}-${sampled}`,
26+
};
27+
};

packages/uploadthing/src/_internal/upload-browser.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,36 @@ import type {
1414
UploadFilesOptions,
1515
} from "../types";
1616
import { logDeprecationWarning } from "./deprecations";
17+
import type { TraceHeaders } from "./random-hex";
18+
import { generateTraceHeaders } from "./random-hex";
1719
import type { UploadPutResult } from "./types";
1820
import { createUTReporter } from "./ut-reporter";
1921

2022
const uploadWithProgress = (
2123
file: File,
2224
rangeStart: number,
2325
presigned: NewPresignedUrl,
24-
onUploadProgress?:
25-
| ((opts: { loaded: number; delta: number }) => void)
26-
| undefined,
26+
opts: {
27+
traceHeaders: TraceHeaders;
28+
onUploadProgress?:
29+
| ((opts: { loaded: number; delta: number }) => void)
30+
| undefined;
31+
},
2732
) =>
2833
Micro.async<unknown, UploadThingError, FetchContext>((resume) => {
2934
const xhr = new XMLHttpRequest();
3035
xhr.open("PUT", presigned.url, true);
3136
xhr.setRequestHeader("Range", `bytes=${rangeStart}-`);
3237
xhr.setRequestHeader("x-uploadthing-version", version);
38+
xhr.setRequestHeader("b3", opts.traceHeaders.b3);
39+
xhr.setRequestHeader("traceparent", opts.traceHeaders.traceparent);
40+
3341
xhr.responseType = "json";
3442

3543
let previousLoaded = 0;
3644
xhr.upload.addEventListener("progress", ({ loaded }) => {
3745
const delta = loaded - previousLoaded;
38-
onUploadProgress?.({ loaded, delta });
46+
opts.onUploadProgress?.({ loaded, delta });
3947
previousLoaded = loaded;
4048
});
4149
xhr.addEventListener("load", () => {
@@ -108,13 +116,17 @@ export const uploadFile = <
108116
file: File,
109117
presigned: NewPresignedUrl,
110118
opts: {
119+
traceHeaders: TraceHeaders;
111120
onUploadProgress?: (progressEvent: {
112121
loaded: number;
113122
delta: number;
114123
}) => void;
115124
},
116125
) =>
117-
fetchEff(presigned.url, { method: "HEAD" }).pipe(
126+
fetchEff(presigned.url, {
127+
method: "HEAD",
128+
headers: opts.traceHeaders,
129+
}).pipe(
118130
Micro.map(({ headers }) =>
119131
parseInt(headers.get("x-ut-range-start") ?? "0", 10),
120132
),
@@ -125,12 +137,14 @@ export const uploadFile = <
125137
}),
126138
),
127139
Micro.flatMap((start) =>
128-
uploadWithProgress(file, start, presigned, (progressEvent) =>
129-
opts.onUploadProgress?.({
130-
delta: progressEvent.delta,
131-
loaded: progressEvent.loaded + start,
132-
}),
133-
),
140+
uploadWithProgress(file, start, presigned, {
141+
traceHeaders: opts.traceHeaders,
142+
onUploadProgress: (progressEvent) =>
143+
opts.onUploadProgress?.({
144+
delta: progressEvent.delta,
145+
loaded: progressEvent.loaded + start,
146+
}),
147+
}),
134148
),
135149
Micro.map(unsafeCoerce<unknown, UploadPutResult<TServerOutput>>),
136150
Micro.map((uploadResponse) => ({
@@ -171,11 +185,13 @@ export const uploadFilesInternal = <
171185
FetchContext
172186
> => {
173187
// classic service right here
188+
const traceHeaders = generateTraceHeaders();
174189
const reportEventToUT = createUTReporter({
175190
endpoint: String(endpoint),
176191
package: opts.package,
177192
url: opts.url,
178193
headers: opts.headers,
194+
traceHeaders,
179195
});
180196

181197
const totalSize = opts.files.reduce((acc, f) => acc + f.size, 0);
@@ -204,6 +220,7 @@ export const uploadFilesInternal = <
204220
opts.files[i]!,
205221
presigned,
206222
{
223+
traceHeaders,
207224
onUploadProgress: (ev) => {
208225
totalLoaded += ev.delta;
209226
opts.onUploadProgress?.({

packages/uploadthing/src/_internal/ut-reporter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "@uploadthing/shared";
1111

1212
import * as pkgJson from "../../package.json";
13+
import type { TraceHeaders } from "./random-hex";
1314
import type { ActionType } from "./shared-schemas";
1415
import type { UTEvents } from "./types";
1516

@@ -48,6 +49,7 @@ export const createUTReporter =
4849
endpoint: string;
4950
package?: string | undefined;
5051
headers: HeadersInit | (() => MaybePromise<HeadersInit>) | undefined;
52+
traceHeaders: TraceHeaders;
5153
}): UTReporter =>
5254
(type, payload) =>
5355
Micro.gen(function* () {
@@ -66,6 +68,8 @@ export const createUTReporter =
6668
}
6769
headers.set("x-uploadthing-version", pkgJson.version);
6870
headers.set("Content-Type", "application/json");
71+
headers.set("b3", cfg.traceHeaders.b3);
72+
headers.set("traceparent", cfg.traceHeaders.traceparent);
6973

7074
const response = yield* fetchEff(url, {
7175
method: "POST",

packages/uploadthing/src/client-future.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from "./_internal/client-future";
2828
import type { Deferred } from "./_internal/deferred";
2929
import { createDeferred } from "./_internal/deferred";
30+
import { generateTraceHeaders } from "./_internal/random-hex";
3031
import type {
3132
EndpointArg,
3233
FileRouter,
@@ -73,13 +74,15 @@ export const future_genUploader = <TRouter extends FileRouter>(
7374
const endpoint = typeof slug === "function" ? slug(routeRegistry) : slug;
7475
const fetchFn: FetchEsque = initOpts?.fetch ?? window.fetch;
7576

77+
const traceHeaders = generateTraceHeaders();
7678
const pExit = await requestPresignedUrls({
7779
endpoint: String(endpoint),
7880
files: options.files,
7981
url: resolveMaybeUrlArg(initOpts?.url),
8082
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
8183
input: (options as any).input as inferEndpointInput<TRouter[TEndpoint]>,
8284
headers: options.headers,
85+
traceHeaders,
8386
}).pipe(Micro.provideService(FetchContext, fetchFn), (effect) =>
8487
Micro.runPromiseExit(
8588
effect,
@@ -109,6 +112,7 @@ export const future_genUploader = <TRouter extends FileRouter>(
109112
files: pendingFiles,
110113
input: options.input,
111114
onEvent: options.onEvent,
115+
traceHeaders,
112116
XHRImpl: globalThis.XMLHttpRequest,
113117
}).pipe(Micro.provideService(FetchContext, fetchFn));
114118

packages/uploadthing/src/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import * as pkgJson from "../package.json";
1717
import type { Deferred } from "./_internal/deferred";
1818
import { createDeferred } from "./_internal/deferred";
19+
import { generateTraceHeaders } from "./_internal/random-hex";
1920
import { uploadFile, uploadFilesInternal } from "./_internal/upload-browser";
2021
import { createUTReporter } from "./_internal/ut-reporter";
2122
import type {
@@ -110,11 +111,13 @@ export const genUploader = <TRouter extends FileRouter>(
110111

111112
const endpoint = typeof slug === "function" ? slug(routeRegistry) : slug;
112113

114+
const traceHeaders = generateTraceHeaders();
113115
const utReporter = createUTReporter({
114116
endpoint: String(endpoint),
115117
package: initOpts?.package ?? "uploadthing/client",
116118
url: resolveMaybeUrlArg(initOpts?.url),
117119
headers: opts.headers,
120+
traceHeaders,
118121
});
119122
const fetchFn: FetchEsque = initOpts?.fetch ?? window.fetch;
120123

@@ -136,6 +139,7 @@ export const genUploader = <TRouter extends FileRouter>(
136139

137140
const uploadEffect = (file: File, presigned: NewPresignedUrl) =>
138141
uploadFile(file, presigned, {
142+
traceHeaders,
139143
onUploadProgress: (progressEvent) => {
140144
totalLoaded += progressEvent.delta;
141145
opts.onUploadProgress?.({

packages/uploadthing/test/browser/client.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ describe("uploadFiles", () => {
234234
Object.fromEntries(middlewareMock.mock.calls[0]![0]!.req.headers),
235235
).toMatchObject({
236236
authorization: "Bearer my-auth-token",
237+
b3: expect.any(String),
238+
traceparent: expect.any(String),
237239
"x-uploadthing-package": "vitest",
238240
"x-uploadthing-version": expect.stringMatching(/\d+\.\d+\.\d+/),
239241
});

0 commit comments

Comments
 (0)