Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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