Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom fetch override #1099

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/expo/src/document-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import * as DocumentPicker from "expo-document-picker";
import type { UseUploadthingProps } from "@uploadthing/react";
import { __useUploadThingInternal } from "@uploadthing/react/native";
import { generatePermittedFileTypes } from "@uploadthing/shared";
import type { ExpandedRouteConfig, ExtendObjectIf } from "@uploadthing/shared";
import type {
ExpandedRouteConfig,
ExtendObjectIf,
FetchEsque,
} from "@uploadthing/shared";
import type { FileRouter } from "uploadthing/server";
import type { inferEndpointInput } from "uploadthing/types";

Expand Down Expand Up @@ -33,6 +37,7 @@ export const GENERATE_useDocumentUploader = <
TRouter extends FileRouter,
>(initOpts: {
url: URL;
fetch: FetchEsque;
}) => {
const useDocumentUploader = <TEndpoint extends keyof TRouter>(
endpoint: TEndpoint,
Expand All @@ -41,6 +46,7 @@ export const GENERATE_useDocumentUploader = <
const { routeConfig, startUpload, isUploading } = __useUploadThingInternal(
initOpts.url,
endpoint,
initOpts.fetch,
opts,
);
const { mimeTypes, multiple } = useMemo(
Expand Down
8 changes: 7 additions & 1 deletion packages/expo/src/image-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import * as ImagePicker from "expo-image-picker";
import type { UseUploadthingProps } from "@uploadthing/react";
import { __useUploadThingInternal } from "@uploadthing/react/native";
import { generatePermittedFileTypes } from "@uploadthing/shared";
import type { ExpandedRouteConfig, ExtendObjectIf } from "@uploadthing/shared";
import type {
ExpandedRouteConfig,
ExtendObjectIf,
FetchEsque,
} from "@uploadthing/shared";
import type { FileRouter } from "uploadthing/server";
import type { inferEndpointInput } from "uploadthing/types";

Expand All @@ -25,6 +29,7 @@ export const GENERATE_useImageUploader = <
TRouter extends FileRouter,
>(initOpts: {
url: URL;
fetch: FetchEsque;
}) => {
const useImageUploader = <TEndpoint extends keyof TRouter>(
endpoint: TEndpoint,
Expand All @@ -33,6 +38,7 @@ export const GENERATE_useImageUploader = <
const { routeConfig, startUpload, isUploading } = __useUploadThingInternal(
initOpts.url,
endpoint,
initOpts.fetch,
opts,
);
const { mediaTypes, multiple } = useMemo(
Expand Down
33 changes: 30 additions & 3 deletions packages/expo/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Constants from "expo-constants";

import { generateReactHelpers } from "@uploadthing/react/native";
import type { FetchEsque } from "@uploadthing/shared";
import { warnIfInvalidPeerDependency } from "@uploadthing/shared";
import { version as uploadthingClientVersion } from "uploadthing/client";
import type { FileRouter } from "uploadthing/types";
Expand All @@ -20,6 +21,25 @@ export interface GenerateTypedHelpersOptions {
* @default (process.env.EXPO_PUBLIC_SERVER_ORIGIN ?? ExpoConstants.debuggerHost) + "/api/uploadthing"
*/
url?: URL | string;
/**
* Provide a custom fetch implementation.
* @default `globalThis.fetch`
* @example
* ```ts
* fetch: (input, init) => {
* if (input.toString().startsWith(MY_SERVER_URL)) {
* // Include cookies in the request to your API
* return fetch(input, {
* ...init,
* credentials: "include",
* });
* }
*
* return fetch(input, init);
* }
* ```
*/
fetch?: FetchEsque | undefined;
}

export const generateReactNativeHelpers = <TRouter extends FileRouter>(
Expand Down Expand Up @@ -48,9 +68,16 @@ export const generateReactNativeHelpers = <TRouter extends FileRouter>(
);
}

const vanillaHelpers = generateReactHelpers<TRouter>({ ...initOpts, url });
const useImageUploader = GENERATE_useImageUploader<TRouter>({ url });
const useDocumentUploader = GENERATE_useDocumentUploader<TRouter>({ url });
const fetch = initOpts?.fetch ?? globalThis.fetch;
const opts = {
...initOpts,
url,
fetch,
};

const vanillaHelpers = generateReactHelpers<TRouter>(opts);
const useImageUploader = GENERATE_useImageUploader<TRouter>(opts);
const useDocumentUploader = GENERATE_useDocumentUploader<TRouter>(opts);

return { ...vanillaHelpers, useImageUploader, useDocumentUploader };
};
1 change: 1 addition & 0 deletions packages/react/src/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export function UploadButton<
const { startUpload, isUploading, routeConfig } = __useUploadThingInternal(
resolveMaybeUrlArg($props.url),
$props.endpoint,
$props.fetch ?? globalThis.fetch,
{
signal: acRef.current.signal,
headers: $props.headers,
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/dropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export function UploadDropzone<
const { startUpload, isUploading, routeConfig } = __useUploadThingInternal(
resolveMaybeUrlArg($props.url),
$props.endpoint,
$props.fetch ?? globalThis.fetch,
{
signal: acRef.current.signal,
headers: $props.headers,
Expand Down
21 changes: 18 additions & 3 deletions packages/react/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,20 @@ export const generateUploadButton = <TRouter extends FileRouter>(
);

const url = resolveMaybeUrlArg(opts?.url);
const fetch = opts?.fetch ?? globalThis.fetch;

const TypedButton = <TEndpoint extends keyof TRouter>(
props: Omit<
UploadButtonProps<TRouter, TEndpoint>,
keyof GenerateTypedHelpersOptions
>,
) => <UploadButton<TRouter, TEndpoint> {...(props as any)} url={url} />;
) => (
<UploadButton<TRouter, TEndpoint>
{...(props as any)}
url={url}
fetch={fetch}
/>
);
return TypedButton;
};

Expand All @@ -54,7 +61,13 @@ export const generateUploadDropzone = <TRouter extends FileRouter>(
UploadDropzoneProps<TRouter, TEndpoint>,
keyof GenerateTypedHelpersOptions
>,
) => <UploadDropzone<TRouter, TEndpoint> {...(props as any)} url={url} />;
) => (
<UploadDropzone<TRouter, TEndpoint>
{...(props as any)}
url={url}
fetch={fetch}
/>
);
return TypedDropzone;
};

Expand All @@ -74,6 +87,8 @@ export const generateUploader = <TRouter extends FileRouter>(
UploadthingComponentProps<TRouter, TEndpoint>,
keyof GenerateTypedHelpersOptions
>,
) => <Uploader<TRouter, TEndpoint> {...(props as any)} url={url} />;
) => (
<Uploader<TRouter, TEndpoint> {...(props as any)} url={url} fetch={fetch} />
);
return TypedUploader;
};
39 changes: 39 additions & 0 deletions packages/react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
ClassListMerger,
ErrorMessage,
ExtendObjectIf,
FetchEsque,
MaybePromise,
UploadThingError,
} from "@uploadthing/shared";
Expand All @@ -26,6 +27,25 @@ export interface GenerateTypedHelpersOptions {
* @default (VERCEL_URL ?? window.location.origin) + "/api/uploadthing"
*/
url?: string | URL;
/**
* Provide a custom fetch implementation.
* @default `globalThis.fetch`
* @example
* ```ts
* fetch: (input, init) => {
* if (input.toString().startsWith(MY_SERVER_URL)) {
* // Include cookies in the request to your API
* return fetch(input, {
* ...init,
* credentials: "include",
* });
* }
*
* return fetch(input, init);
* }
* ```
*/
fetch?: FetchEsque | undefined;
}

export type UseUploadthingProps<
Expand Down Expand Up @@ -122,6 +142,25 @@ export type UploadthingComponentProps<
* @default (VERCEL_URL ?? window.location.origin) + "/api/uploadthing"
*/
url?: string | URL;
/**
* Provide a custom fetch implementation.
* @default `globalThis.fetch`
* @example
* ```ts
* fetch: (input, init) => {
* if (input.toString().startsWith(MY_SERVER_URL)) {
* // Include cookies in the request to your API
* return fetch(input, {
* ...init,
* credentials: "include",
* });
* }
*
* return fetch(input, init);
* }
* ```
*/
fetch?: FetchEsque | undefined;
config?: {
mode?: "auto" | "manual";
appendOnPaste?: boolean;
Expand Down
12 changes: 10 additions & 2 deletions packages/react/src/useUploadThing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useRef, useState } from "react";
import type {
EndpointMetadata,
ExpandedRouteConfig,
FetchEsque,
} from "@uploadthing/shared";
import {
INTERNAL_DO_NOT_USE__fatalClientError,
Expand Down Expand Up @@ -30,14 +31,17 @@ import useFetch from "./utils/useFetch";

declare const globalThis: {
__UPLOADTHING?: EndpointMetadata;
fetch: FetchEsque;
};

const useRouteConfig = (
fetch: FetchEsque,
url: URL,
endpoint: string,
): ExpandedRouteConfig | undefined => {
const maybeServerData = globalThis.__UPLOADTHING;
const { data } = useFetch<EndpointMetadata>(
fetch,
// Don't fetch if we already have the data
maybeServerData ? undefined : url.href,
);
Expand All @@ -55,9 +59,11 @@ export function __useUploadThingInternal<
>(
url: URL,
endpoint: EndpointArg<TRouter, TEndpoint>,
fetch: FetchEsque,
opts?: UseUploadthingProps<TRouter[TEndpoint]>,
) {
const { uploadFiles, routeRegistry } = genUploader<TRouter>({
fetch,
url,
package: "@uploadthing/react",
});
Expand Down Expand Up @@ -134,7 +140,7 @@ export function __useUploadThingInternal<
});

const _endpoint = unwrap(endpoint, routeRegistry);
const routeConfig = useRouteConfig(url, _endpoint as string);
const routeConfig = useRouteConfig(fetch, url, _endpoint as string);

return {
startUpload,
Expand All @@ -152,18 +158,20 @@ export const generateReactHelpers = <TRouter extends FileRouter>(
uploadthingClientVersion,
);

const fetch = initOpts?.fetch ?? globalThis.fetch;
const url = resolveMaybeUrlArg(initOpts?.url);

const clientHelpers = genUploader<TRouter>({
url,
package: "@uploadthing/react",
fetch,
});

function useUploadThing<TEndpoint extends keyof TRouter>(
endpoint: EndpointArg<TRouter, TEndpoint>,
opts?: UseUploadthingProps<TRouter[TEndpoint]>,
) {
return __useUploadThingInternal(url, endpoint, opts);
return __useUploadThingInternal(url, endpoint, fetch, opts);
}

function getRouteConfig(slug: EndpointArg<TRouter, keyof TRouter>) {
Expand Down
7 changes: 6 additions & 1 deletion packages/react/src/utils/useFetch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Ripped from https://usehooks-ts.com/react-hook/use-fetch
import { useEffect, useReducer, useRef } from "react";

import type { FetchEsque } from "@uploadthing/shared";
import { safeParseJSON } from "@uploadthing/shared";

interface State<T> {
Expand All @@ -16,7 +17,11 @@ type Action<T> =
| { type: "fetched"; payload: T }
| { type: "error"; payload: Error };

function useFetch<T = unknown>(url?: string, options?: RequestInit): State<T> {
function useFetch<T = unknown>(
fetch: FetchEsque,
url?: string,
options?: RequestInit,
): State<T> {
const cache = useRef<Cache<T>>({});

// Used to prevent state update if the component is unmounted
Expand Down
10 changes: 6 additions & 4 deletions packages/uploadthing/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Arr from "effect/Array";
import * as Micro from "effect/Micro";

import type { ExpandedRouteConfig } from "@uploadthing/shared";
import type { ExpandedRouteConfig, FetchEsque } from "@uploadthing/shared";
import {
createIdentityProxy,
FetchContext,
Expand Down Expand Up @@ -112,6 +112,7 @@ export const genUploader = <TRouter extends FileRouter>(
url: resolveMaybeUrlArg(initOpts?.url),
headers: opts.headers,
});
const fetchFn: FetchEsque = initOpts.fetch ?? window.fetch;

const presigneds = await Micro.runPromise(
utReporter("upload", {
Expand All @@ -123,7 +124,7 @@ export const genUploader = <TRouter extends FileRouter>(
type: f.type,
lastModified: f.lastModified,
})),
}).pipe(Micro.provideService(FetchContext, window.fetch)),
}).pipe(Micro.provideService(FetchContext, fetchFn)),
);

const totalSize = opts.files.reduce((acc, f) => acc + f.size, 0);
Expand All @@ -141,7 +142,7 @@ export const genUploader = <TRouter extends FileRouter>(
totalProgress: Math.round((totalLoaded / totalSize) * 100),
});
},
}).pipe(Micro.provideService(FetchContext, window.fetch));
}).pipe(Micro.provideService(FetchContext, fetchFn));

for (const [i, p] of presigneds.entries()) {
const file = opts.files[i];
Expand Down Expand Up @@ -254,6 +255,7 @@ export const genUploader = <TRouter extends FileRouter>(
>,
) => {
const endpoint = typeof slug === "function" ? slug(routeRegistry) : slug;
const fetchFn: FetchEsque = initOpts.fetch ?? window.fetch;

return uploadFilesInternal<TRouter, TEndpoint>(endpoint, {
...opts,
Expand All @@ -263,7 +265,7 @@ export const genUploader = <TRouter extends FileRouter>(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
input: (opts as any).input as inferEndpointInput<TRouter[TEndpoint]>,
})
.pipe((effect) =>
.pipe(Micro.provideService(FetchContext, fetchFn), (effect) =>
Micro.runPromiseExit(effect, opts.signal && { signal: opts.signal }),
)
.then((exit) => {
Expand Down
Loading
Loading