diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index bffb357..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "next/core-web-vitals"
-}
diff --git a/.github/renovate.json b/.github/renovate.json
index 650bd20..d6c85b7 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -2,4 +2,4 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"labels": ["dependencies"]
-}
\ No newline at end of file
+}
diff --git a/.github/workflows/next-build.yml b/.github/workflows/build.yml
similarity index 77%
rename from .github/workflows/next-build.yml
rename to .github/workflows/build.yml
index f751104..411b320 100644
--- a/.github/workflows/next-build.yml
+++ b/.github/workflows/build.yml
@@ -11,11 +11,11 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '#skip-lint')"
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version: "16"
+ node-version: "20"
cache: yarn
- name: Install dependencies
run: yarn install
@@ -26,11 +26,11 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '#skip-build')"
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version: "16"
+ node-version: "20"
cache: yarn
- name: Install dependencies
run: yarn install
diff --git a/.gitignore b/.gitignore
index 88b6f0d..11fe0e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,7 @@ yarn-error.log*
# typescript
*.tsbuildinfo
+
+.dev.vars
+.wrangler
+.react-router
\ No newline at end of file
diff --git a/README.md b/README.md
index b076dc4..e83f9fc 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,79 @@
-# DropBox-Index
+# Welcome to React Router!
-Index your dropbox files publically. Built with NextJS & Tailwind
+A modern, production-ready template for building full-stack React applications using React Router.
-[](https://github.com/ArnabXD/Dropbox-Index/actions/workflows/next-build.yml)
-[](https://lgtm.com/projects/g/ArnabXD/Dropbox-Index/alerts/)
-[](https://lgtm.com/projects/g/ArnabXD/Dropbox-Index/context:javascript)
+## Features
-## ENV Vars
+- 🚀 Server-side rendering
+- ⚡️ Hot Module Replacement (HMR)
+- 📦 Asset bundling and optimization
+- 🔄 Data loading and mutations
+- 🔒 TypeScript by default
+- 🎉 TailwindCSS for styling
+- 📖 [React Router docs](https://reactrouter.com/)
-- `APP_ID` : Dropbox App Client ID
-- `APP_SECRET` : Dropbox App Client Secret
-- `REFRESH_TOKEN` : Dropbox refresh token
-- `NEXT_PUBLIC_TITLE` : Website title and Navbar heading
+## Getting Started
-## Deploy
+### Installation
-[](https://render.com/deploy)
+Install the dependencies:
-[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FArnabXD%2FDropbox-Index&env=APP_ID,APP_SECRET,REFRESH_TOKEN,NEXT_PUBLIC_TITLE)
\ No newline at end of file
+```bash
+npm install
+```
+
+### Development
+
+Start the development server with HMR:
+
+```bash
+npm run dev
+```
+
+Your application will be available at `http://localhost:5173`.
+
+## Previewing the Production Build
+
+Preview the production build locally:
+
+```bash
+npm run preview
+```
+
+## Building for Production
+
+Create a production build:
+
+```bash
+npm run build
+```
+
+## Deployment
+
+Deployment is done using the Wrangler CLI.
+
+To build and deploy directly to production:
+
+```sh
+npm run deploy
+```
+
+To deploy a preview URL:
+
+```sh
+npx wrangler versions upload
+```
+
+You can then promote a version to production after verification or roll it out progressively.
+
+```sh
+npx wrangler versions deploy
+```
+
+## Styling
+
+This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.
+
+---
+
+Built with ❤️ using React Router.
diff --git a/app/app.css b/app/app.css
new file mode 100644
index 0000000..076da4f
--- /dev/null
+++ b/app/app.css
@@ -0,0 +1,11 @@
+@import "tailwindcss";
+
+html,
+body {
+ @apply bg-white dark:bg-slate-950;
+ @apply text-slate-950 dark:text-white;
+
+ @media (prefers-color-scheme: dark) {
+ color-scheme: dark;
+ }
+}
diff --git a/app/entry.server.tsx b/app/entry.server.tsx
new file mode 100644
index 0000000..9db4060
--- /dev/null
+++ b/app/entry.server.tsx
@@ -0,0 +1,44 @@
+import type { AppLoadContext, EntryContext } from "react-router";
+import { ServerRouter } from "react-router";
+import { isbot } from "isbot";
+import { renderToReadableStream } from "react-dom/server";
+
+export default async function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ routerContext: EntryContext,
+ _loadContext: AppLoadContext,
+) {
+ let shellRendered = false;
+ let status = responseStatusCode;
+ const userAgent = request.headers.get("user-agent");
+
+ const body = await renderToReadableStream(
+ ,
+ {
+ onError(error: unknown) {
+ status = 500;
+ // Log streaming rendering errors from inside the shell. Don't log
+ // errors encountered during initial shell rendering since they'll
+ // reject and get logged in handleDocumentRequest.
+ if (shellRendered) {
+ console.error(error);
+ }
+ },
+ },
+ );
+ shellRendered = true;
+
+ // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
+ if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
+ await body.allReady;
+ }
+
+ responseHeaders.set("Content-Type", "text/html");
+ return new Response(body, {
+ headers: responseHeaders,
+ status,
+ });
+}
diff --git a/app/root.tsx b/app/root.tsx
new file mode 100644
index 0000000..9fc6636
--- /dev/null
+++ b/app/root.tsx
@@ -0,0 +1,75 @@
+import {
+ isRouteErrorResponse,
+ Links,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from "react-router";
+
+import type { Route } from "./+types/root";
+import "./app.css";
+
+export const links: Route.LinksFunction = () => [
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
+ {
+ rel: "preconnect",
+ href: "https://fonts.gstatic.com",
+ crossOrigin: "anonymous",
+ },
+ {
+ rel: "stylesheet",
+ href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
+ },
+];
+
+export function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
+
+export default function App() {
+ return ;
+}
+
+export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
+ let message = "Oops!";
+ let details = "An unexpected error occurred.";
+ let stack: string | undefined;
+
+ if (isRouteErrorResponse(error)) {
+ message = error.status === 404 ? "404" : "Error";
+ details =
+ error.status === 404
+ ? "The requested page could not be found."
+ : error.statusText || details;
+ } else if (import.meta.env.DEV && error && error instanceof Error) {
+ details = error.message;
+ stack = error.stack;
+ }
+
+ return (
+
+ {message}
+ {details}
+ {stack && (
+
+ {stack}
+
+ )}
+
+ );
+}
diff --git a/app/routes.ts b/app/routes.ts
new file mode 100644
index 0000000..b3b0903
--- /dev/null
+++ b/app/routes.ts
@@ -0,0 +1,7 @@
+import { type RouteConfig, route, index } from "@react-router/dev/routes";
+
+export default [
+ index("routes/home.tsx"),
+ route("/setup", "routes/setup.tsx"),
+ route("/authorize", "routes/authorize.tsx"),
+] satisfies RouteConfig;
diff --git a/app/routes/authorize.tsx b/app/routes/authorize.tsx
new file mode 100644
index 0000000..dc2aca1
--- /dev/null
+++ b/app/routes/authorize.tsx
@@ -0,0 +1,59 @@
+import { redirect } from "react-router";
+
+import type { Route } from "./+types/authorize";
+import { DropboxService } from "~/services/dropbox";
+
+export const meta = ({ data }: Route.MetaArgs) => {
+ return [
+ { title: `${data.title} | ${data.error}` },
+ { name: "description", content: data.error_description },
+ ];
+};
+
+export const loader = async ({ context, request }: Route.LoaderArgs) => {
+ const dropbox = new DropboxService(
+ context.cloudflare.env.DROPBOX_APP_KEY,
+ context.cloudflare.env.DROPBOX_APP_SECRET,
+ context.cloudflare.env.TOKEN,
+ );
+
+ const code = new URL(request.url).searchParams.get("code");
+ if (!code) {
+ return {
+ error: "Missing code",
+ error_description: "The authorization code is missing.",
+ title: context.cloudflare.env.VITE_SITE_TITLE,
+ };
+ }
+
+ const token = await dropbox.getToken(code, request.url);
+
+ if ("access_token" in token) {
+ return redirect("/home");
+ }
+
+ return {
+ error: token.error,
+ error_description: token.error_description,
+ title: context.cloudflare.env.VITE_SITE_TITLE,
+ };
+};
+
+export default function Authorize({ loaderData }: Route.ComponentProps) {
+ if (!loaderData.error) {
+ return (
+
+
Success
+
Authorization successful!
+
+ );
+ }
+
+ return (
+
+
Error
+
{loaderData.error}
+
{loaderData.error_description}
+
+ );
+}
diff --git a/app/routes/home.tsx b/app/routes/home.tsx
new file mode 100644
index 0000000..cf121ac
--- /dev/null
+++ b/app/routes/home.tsx
@@ -0,0 +1,18 @@
+import type { Route } from "./+types/home";
+
+export function meta({ data }: Route.MetaArgs) {
+ return [
+ { title: data.title },
+ { name: "description", content: "Welcome to React Router!" },
+ ];
+}
+
+export function loader({ context }: Route.LoaderArgs) {
+ return {
+ title: context.cloudflare.env.VITE_SITE_TITLE,
+ };
+}
+
+export default function Home({ loaderData }: Route.ComponentProps) {
+ return Hi
;
+}
diff --git a/app/routes/setup.tsx b/app/routes/setup.tsx
new file mode 100644
index 0000000..6f511f2
--- /dev/null
+++ b/app/routes/setup.tsx
@@ -0,0 +1,48 @@
+import { DropboxService } from "~/services/dropbox";
+import type { Route } from "./+types/setup";
+import { redirect } from "react-router";
+
+export const loader = async ({ context, request }: Route.LoaderArgs) => {
+ const dropbox = new DropboxService(
+ context.cloudflare.env.DROPBOX_APP_KEY,
+ context.cloudflare.env.DROPBOX_APP_SECRET,
+ context.cloudflare.env.TOKEN
+ );
+
+ const refreshToken = await dropbox.getRefreshToken();
+ if (refreshToken) {
+ return {
+ setupComplete: true,
+ };
+ }
+
+ const authorizationUrl = dropbox.getAuthUrl(
+ new URL(request.url).host + "/authorize"
+ );
+
+ redirect(authorizationUrl, 301);
+};
+
+export const meta = ({ data }: Route.MetaArgs) => {
+ return [
+ { title: "Setup Dropbox Integration" },
+ { name: "description", content: "Setup your Dropbox integration" },
+ ];
+};
+
+export default function Setup({ loaderData }: Route.ComponentProps) {
+ if (loaderData?.setupComplete) {
+ return (
+
+
Setup Complete
+
+ You have successfully set up the Dropbox integration. You can now
+ proceed to use the application.
+
+
Home
+
+ );
+ }
+
+ return null;
+}
diff --git a/app/services/dropbox.ts b/app/services/dropbox.ts
new file mode 100644
index 0000000..947ef08
--- /dev/null
+++ b/app/services/dropbox.ts
@@ -0,0 +1,171 @@
+import ky from "ky";
+import type { KyInstance, KyRequest } from "ky";
+
+interface DropboxAuthResponse {
+ access_token: string;
+ expires_in: number;
+ refresh_token: string;
+ scope: string;
+ uid: string;
+ account_id: string;
+}
+
+interface DropboxError {
+ error: string;
+ error_description: string;
+}
+
+export class DropboxService {
+ private API_KEY: string;
+ private API_SECRET: string;
+ private TOKEN_DB: KVNamespace;
+ private client: KyInstance;
+
+ constructor(API_KEY: string, API_SECRET: string, TOKEN_DB: KVNamespace) {
+ this.API_KEY = API_KEY;
+ this.API_SECRET = API_SECRET;
+ this.TOKEN_DB = TOKEN_DB;
+
+ this.client = ky.create({
+ prefixUrl: "https://api.dropboxapi.com/2",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ hooks: {
+ beforeRequest: [
+ async (request) => {
+ const token = await this.getAccessToken();
+ request.headers.set("Authorization", `Bearer ${token}`);
+ },
+ ],
+ afterResponse: [
+ async (request, _options, response) => {
+ if (response.status === 401) {
+ const originalRequest = request.clone() as KyRequest;
+
+ const newToken = await this.refreshToken();
+ if ("access_token" in newToken) {
+ request.headers.set(
+ "Authorization",
+ `Bearer ${newToken.access_token}`
+ );
+ }
+
+ return await ky(originalRequest);
+ }
+ },
+ ],
+ },
+ });
+ }
+
+ getAuthUrl = (redirectUri: string) => {
+ const authorizationUrl = new URL(
+ "https://www.dropbox.com/oauth2/authorize"
+ );
+
+ authorizationUrl.searchParams.set("client_id", this.API_KEY);
+ authorizationUrl.searchParams.set("response_type", "code");
+ authorizationUrl.searchParams.set("token_access_type", "offline");
+ authorizationUrl.searchParams.set("redirect_uri", redirectUri);
+
+ return authorizationUrl.toString();
+ };
+
+ getToken = async (code: string, redirectUri: string) => {
+ const body = new FormData();
+
+ const redirect = new URL(redirectUri);
+
+ body.append("code", code);
+ body.append("grant_type", "authorization_code");
+ body.append("client_id", this.API_KEY);
+ body.append("client_secret", this.API_SECRET);
+ body.append("redirect_uri", redirect.origin + redirect.pathname);
+
+ const response = await ky
+ .post("https://api.dropboxapi.com/oauth2/token", {
+ body,
+ throwHttpErrors: false,
+ })
+ .json();
+
+ if (!response || "error_description" in response) {
+ console.debug(response);
+ return response;
+ }
+
+ await this.TOKEN_DB.put("REFRESH_TOKEN", response.refresh_token);
+ await this.TOKEN_DB.put("ACCESS_TOKEN", response.access_token);
+
+ return response;
+ };
+
+ getAccessToken = async () => {
+ const token = await this.TOKEN_DB.get("ACCESS_TOKEN");
+ if (!token) {
+ throw new Error("No access token found", {
+ cause: {
+ status: 401,
+ message: "No access token found",
+ },
+ });
+ }
+
+ return token;
+ };
+
+ getRefreshToken = async () => {
+ const token = await this.TOKEN_DB.get("REFRESH_TOKEN");
+ if (!token) {
+ throw new Error("No refresh token found");
+ }
+
+ return token;
+ };
+
+ refreshToken = async () => {
+ const refreshToken = await this.getRefreshToken();
+
+ const body = new FormData();
+
+ body.append("grant_type", "refresh_token");
+ body.append("client_id", this.API_KEY);
+ body.append("client_secret", this.API_SECRET);
+ body.append("refresh_token", refreshToken);
+
+ const response = await ky
+ .post("https://api.dropboxapi.com/oauth2/token", {
+ body,
+ throwHttpErrors: false,
+ })
+ .json();
+
+ if (!response || "error_description" in response) {
+ console.debug(response);
+ return response;
+ }
+
+ await this.TOKEN_DB.put("REFRESH_TOKEN", response.refresh_token);
+ await this.TOKEN_DB.put("ACCESS_TOKEN", response.access_token);
+
+ return response;
+ };
+
+ listFolders = async (path = "") => {
+ const response = await this.client
+ .post("files/list_folder", {
+ json: {
+ path,
+ recursive: false,
+ include_media_info: false,
+ include_deleted: false,
+ include_has_explicit_shared_members: false,
+ include_mounted_folders: true,
+ },
+ })
+ .json();
+
+ return response;
+ };
+}
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..ba510b7
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
+ "vcs": {
+ "enabled": false,
+ "clientKind": "git",
+ "useIgnoreFile": false
+ },
+ "files": {
+ "ignoreUnknown": false,
+ "ignore": ["node_modules", "dist", "build"]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "indentWidth": 2,
+ "ignore": ["node_modules", "dist", "build", "worker-configuration.d.ts"]
+ },
+ "organizeImports": {
+ "enabled": true
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true
+ },
+ "ignore": [
+ "node_modules",
+ "dist",
+ ".react-router",
+ "worker-configuration.d.ts"
+ ]
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "double"
+ }
+ }
+}
diff --git a/components/Breadcrumb.tsx b/components/Breadcrumb.tsx
deleted file mode 100644
index 072eaf1..0000000
--- a/components/Breadcrumb.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { useEffect, useState } from "react";
-import { HomeIcon } from "@heroicons/react/solid";
-import Link from "next/link";
-import { IconButton } from "./Icons";
-
-export interface BCPaths {
- display: string;
- link?: string;
-}
-
-interface BCProps {
- path?: string;
-}
-
-export const Breadcrumb = (props: BCProps) => {
- const [path, setPath] = useState([]);
-
- useEffect(() => {
- if (!props.path) {
- return setPath([]);
- }
- if (!props.path.includes("/")) {
- return setPath([{ display: props.path }]);
- }
- const paths = props.path.split("/").map((value, index) => {
- return {
- display: value,
- link:
- index !== props.path!.split("/").length - 1
- ? "/" +
- props
- .path!.split("/")
- .slice(0, index + 1)
- .join("/")
- : undefined,
- };
- });
- setPath(paths);
- }, [props.path]);
-
- return (
-
-
-
-
-
-
- {path.length ? (
- path.map((p) => (
-
- {" / "}
- {p.link ? (
-
- {p.display}
-
- ) : (
- {p.display}
- )}
-
- ))
- ) : (
- <>>
- )}
-
- );
-};
diff --git a/components/Icons.tsx b/components/Icons.tsx
deleted file mode 100644
index d0bb4db..0000000
--- a/components/Icons.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, { ReactChild } from "react";
-
-const Loader = () => (
-
-);
-
-export const Loading = React.memo(Loader);
-
-interface IconButtonI {
- children: ReactChild;
- onClick?: () => any;
-}
-
-export const IconButton = ({ children, onClick }: IconButtonI) => {
- return (
-
- {children}
-
- );
-};
diff --git a/components/List.tsx b/components/List.tsx
deleted file mode 100644
index 5f4f3a4..0000000
--- a/components/List.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import React, { useState, useEffect } from "react";
-import {
- DownloadIcon,
- DocumentIcon,
- FolderIcon,
- ClipboardIcon,
-} from "@heroicons/react/solid";
-import { DropboxResponse, files } from "dropbox";
-import { useClipboard } from "@chakra-ui/hooks";
-import { Toaster, toast } from "react-hot-toast";
-import { useRouter } from "next/router";
-
-import { IconButton } from "./Icons";
-import ky from "ky";
-import prettyBytes from "pretty-bytes";
-
-interface ListProps {
- entries: (files.FileMetadataReference | files.FolderMetadataReference)[];
- path?: string;
-}
-
-export const List = (props: ListProps) => {
- const [link, setLink] = useState("");
- const { onCopy } = useClipboard(link);
-
- const router = useRouter();
-
- useEffect(() => {
- if (link) {
- onCopy();
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [link]);
-
- const getDownloadLink = async (path: string) =>
- await ky
- .get("/api/download" + path)
- .json<
- DropboxResponse<
- files.GetTemporaryLinkResult | files.GetTemporaryLinkError
- >
- >();
-
- const copyToClipboard = async (
- data: files.FileMetadataReference | files.FolderMetadataReference
- ) => {
- if (data[".tag"] === "folder") {
- setLink(window.location.origin + data.path_display);
- } else {
- let resp = await getDownloadLink(data.path_display!);
- if ("link" in resp.result) {
- setLink(resp.result.link);
- } else {
- throw new Error("Failed to generate download link");
- }
- }
- };
-
- return (
-
-
-
Name
-
- Info
-
-
- Actions
-
-
-
- {props.entries.map((file) => {
- return (
-
-
{
- if (file[".tag"] === "folder") {
- router.push(file.path_display || "/");
- } else {
- getDownloadLink(file.path_display!).then((resp) => {
- if ("link" in resp.result) {
- router.push(resp.result.link);
- } else {
- toast.error("Failed to Download Generate Link");
- }
- });
- }
- }}
- >
- {file[".tag"] === "file" ? (
-
- ) : (
-
- )}
-
{file.name}
-
-
- {file[".tag"] === "file" && prettyBytes(file.size)}
-
-
-
- toast.promise(
- copyToClipboard(file),
- {
- success: "Link copied successfully",
- error: "Failed to generate link",
- loading: "Generating link",
- },
- {
- style: {
- backgroundColor: "#1e293b",
- borderWidth: 2,
- borderColor: "#475569",
- color: "white",
- },
- }
- )
- }
- >
-
-
- {file[".tag"] === "file" && (
- {
- getDownloadLink(file.path_display!).then((resp) => {
- if ("link" in resp.result) {
- router.push(resp.result.link);
- } else {
- toast.error("Failed to Download Generate Link");
- }
- });
- }}
- >
-
-
- )}
-
-
- );
- })}
-
- );
-};
diff --git a/components/Nav.tsx b/components/Nav.tsx
deleted file mode 100644
index d9c471f..0000000
--- a/components/Nav.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from "react";
-import Link from "next/link";
-import { useDisclosure } from "@chakra-ui/hooks";
-import { SearchIcon } from "@heroicons/react/solid";
-import Head from "next/head";
-
-export const Nav = () => {
- const title = process.env.NEXT_PUBLIC_TITLE ?? "D-Index";
- const { isOpen, onToggle } = useDisclosure();
- return (
- <>
-
- {title}
-
-
- >
- );
-};
diff --git a/components/index.ts b/components/index.ts
deleted file mode 100644
index 6747adc..0000000
--- a/components/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export { Breadcrumb } from "./Breadcrumb";
-export { Loading, IconButton } from "./Icons";
-export { List } from "./List";
-export { Nav } from "./Nav";
diff --git a/lib/dropbox.ts b/lib/dropbox.ts
deleted file mode 100644
index 6691961..0000000
--- a/lib/dropbox.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Dropbox } from "dropbox";
-
-export const dropbox = new Dropbox({
- clientId: process.env.APP_ID,
- clientSecret: process.env.APP_SECRET,
- refreshToken: process.env.REFRESH_TOKEN,
-});
diff --git a/next-env.d.ts b/next-env.d.ts
deleted file mode 100644
index 4f11a03..0000000
--- a/next-env.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-///
-///
-
-// NOTE: This file should not be edited
-// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/next.config.js b/next.config.js
deleted file mode 100644
index 8b61df4..0000000
--- a/next.config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import('next').NextConfig} */
-module.exports = {
- reactStrictMode: true,
-}
diff --git a/package.json b/package.json
index 3f7e515..373a041 100644
--- a/package.json
+++ b/package.json
@@ -1,32 +1,37 @@
{
- "name": "dropbox-index",
+ "name": "dropbox-index-rr",
"private": true,
+ "type": "module",
"scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "next lint"
+ "build": "react-router build",
+ "cf-typegen": "wrangler types",
+ "deploy": "yarn run build && wrangler deploy",
+ "dev": "react-router dev",
+ "preview": "yarn run build && vite preview",
+ "typecheck": "npm run cf-typegen && react-router typegen && tsc -b",
+ "lint": "biome lint",
+ "format": "biome format --write ."
},
"dependencies": {
- "@chakra-ui/hooks": "2.0.4",
- "@heroicons/react": "1.0.6",
- "dropbox": "10.32.0",
- "ky": "0.31.1",
- "next": "12.2.2",
- "pretty-bytes": "6.0.0",
- "react": "18.2.0",
- "react-dom": "18.2.0",
- "react-hot-toast": "2.3.0",
- "react-query": "3.39.2"
+ "isbot": "^5.1.17",
+ "ky": "^1.8.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-router": "^7.5.0"
},
"devDependencies": {
- "@types/node": "16.11.43",
- "@types/react": "17.0.45",
- "autoprefixer": "10.4.8",
- "eslint": "8.22.0",
- "eslint-config-next": "12.2.2",
- "postcss": "8.4.14",
- "tailwindcss": "3.1.7",
- "typescript": "4.7.4"
- }
+ "@biomejs/biome": "1.9.4",
+ "@cloudflare/vite-plugin": "^1.0.0",
+ "@react-router/dev": "^7.5.0",
+ "@tailwindcss/vite": "^4.0.0",
+ "@types/node": "^20",
+ "@types/react": "^19.0.1",
+ "@types/react-dom": "^19.0.1",
+ "tailwindcss": "^4.0.0",
+ "typescript": "^5.7.2",
+ "vite": "^6.2.1",
+ "vite-tsconfig-paths": "^5.1.4",
+ "wrangler": "^4.10.0"
+ },
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
diff --git a/pages/[...path].tsx b/pages/[...path].tsx
deleted file mode 100644
index decaa30..0000000
--- a/pages/[...path].tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useEffect, useState } from "react";
-import type { NextPage } from "next";
-import ky from "ky";
-import { useQuery } from "react-query";
-import { DropboxResponse, files } from "dropbox";
-import { useRouter } from "next/router";
-import { Nav, Loading, List, Breadcrumb } from "../components";
-
-const Home: NextPage = () => {
- const router = useRouter();
- const [path, setPath] = useState("");
-
- useEffect(() => {
- if (router.query.path) {
- if (Array.isArray(router.query.path)) {
- setPath(router.query.path.join("/"));
- } else {
- setPath(router.query.path);
- }
- }
- }, [router.query]);
-
- const { data, isLoading, isFetching, refetch } = useQuery(
- "path-" + path,
- async () => {
- const resp = await ky
- .get("/api/explore/" + path)
- .json<
- DropboxResponse
- >();
- if (resp.status === 200) {
- const resp2 = resp as DropboxResponse;
- return resp2.result.entries.filter(
- (file) => file[".tag"] === "file" || file[".tag"] === "folder"
- ) as (files.FileMetadataReference | files.FolderMetadataReference)[];
- } else {
- throw new Error("Something Went Wrong");
- }
- },
- { enabled: false }
- );
-
- useEffect(() => {
- if (path) {
- console.log(path);
- refetch();
- }
- }, [path, refetch]);
-
- return (
- <>
-
-
- {(isLoading || isFetching) && }
- {!(isLoading || isFetching) && data &&
}
- >
- );
-};
-
-export default Home;
diff --git a/pages/_app.tsx b/pages/_app.tsx
deleted file mode 100644
index 046ab94..0000000
--- a/pages/_app.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import "../styles/globals.css";
-import type { AppProps } from "next/app";
-import Head from "next/head";
-import { QueryClient, QueryClientProvider } from "react-query";
-
-const queryClient = new QueryClient();
-
-function MyApp({ Component, pageProps }: AppProps) {
- return (
- <>
-
-
-
-
-
-
-
- >
- );
-}
-
-export default MyApp;
diff --git a/pages/api/auth/index.ts b/pages/api/auth/index.ts
deleted file mode 100644
index fad85be..0000000
--- a/pages/api/auth/index.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from "next";
-import { DropboxAuth } from "dropbox";
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- try {
- const APP_ID = process.env.APP_ID ?? "";
- const APP_SECRET = process.env.APP_SECRET ?? "";
-
- const dbx = new DropboxAuth({
- clientId: APP_ID,
- clientSecret: APP_SECRET,
- });
-
- const redirectUri = req.headers.host?.includes("localhost")
- ? `http://${req.headers.host}/api/auth/token`
- : `https://${req.headers.host}/api/auth/token`;
- const authUri = await dbx.getAuthenticationUrl(
- redirectUri,
- undefined,
- "code",
- "offline",
- undefined,
- "none",
- false
- );
-
- // @ts-ignore
- res.redirect(authUri);
- } catch (error) {
- res.json({ error });
- }
-};
-
-export default handler;
diff --git a/pages/api/auth/token.ts b/pages/api/auth/token.ts
deleted file mode 100644
index 1c00592..0000000
--- a/pages/api/auth/token.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from "next";
-import { DropboxAuth } from "dropbox";
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- try {
- const APP_ID = process.env.APP_ID ?? "";
- const APP_SECRET = process.env.APP_SECRET ?? "";
-
- const dbx = new DropboxAuth({
- clientId: APP_ID,
- clientSecret: APP_SECRET,
- });
-
- const redirectUri = req.headers.host?.includes("localhost")
- ? `http://${req.headers.host}/api/auth/token`
- : `https://${req.headers.host}/api/auth/token`;
- const code = req.query.code as string;
-
- const refreshToken = await dbx.getAccessTokenFromCode(redirectUri, code);
- // @ts-ignore
- res.json(refreshToken);
- } catch (error) {
- res.json({ error });
- }
-};
-
-export default handler;
diff --git a/pages/api/download/[...path].ts b/pages/api/download/[...path].ts
deleted file mode 100644
index 3d3516e..0000000
--- a/pages/api/download/[...path].ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from "next";
-import type { DropboxResponse, files } from "dropbox";
-import { dropbox } from "../../../lib/dropbox";
-
-type FolderResponse = DropboxResponse<
- files.GetTemporaryLinkResult | files.GetTemporaryLinkError
->;
-
-const handler = async (
- req: NextApiRequest,
- res: NextApiResponse
-) => {
- try {
- const path = Array.isArray(req.query.path)
- ? req.query.path
- : [req.query.path];
- const resp = await dropbox.filesGetTemporaryLink({
- path: "/" + path.join("/"),
- });
- res.json(resp);
- } catch (err) {
- res.json(err as DropboxResponse);
- }
-};
-
-export default handler;
diff --git a/pages/api/explore/[...path].ts b/pages/api/explore/[...path].ts
deleted file mode 100644
index fb480f8..0000000
--- a/pages/api/explore/[...path].ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from "next";
-import type { DropboxResponse, files } from "dropbox";
-import { dropbox } from "../../../lib/dropbox";
-
-type FolderResponse = DropboxResponse<
- files.ListFolderResult | files.ListFolderError
->;
-
-const handler = async (
- req: NextApiRequest,
- res: NextApiResponse
-) => {
- try {
- const path = Array.isArray(req.query.path)
- ? req.query.path
- : [req.query.path];
- const resp = await dropbox.filesListFolder({
- path: "/" + path.join("/"),
- limit: 1000,
- });
- res.json(resp);
- } catch (err) {
- console.log(err);
- res.json(err as DropboxResponse);
- }
-};
-
-export default handler;
diff --git a/pages/api/explore/index.ts b/pages/api/explore/index.ts
deleted file mode 100644
index 5e7e579..0000000
--- a/pages/api/explore/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from "next";
-import type { DropboxResponse, files } from "dropbox";
-import { dropbox } from "../../../lib/dropbox";
-
-export type FolderResponse = DropboxResponse<
- files.ListFolderResult | files.ListFolderError
->;
-
-const handler = async (
- req: NextApiRequest,
- res: NextApiResponse
-) => {
- try {
- const resp = await dropbox.filesListFolder({
- path: "",
- limit: 1000,
- include_media_info: true,
- });
- res.json(resp);
- } catch (err) {
- res.json(err as DropboxResponse);
- }
-};
-
-export default handler;
diff --git a/pages/api/search.ts b/pages/api/search.ts
deleted file mode 100644
index 8164f9a..0000000
--- a/pages/api/search.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from "next";
-import type { DropboxResponse, files } from "dropbox";
-import { dropbox } from "../../lib/dropbox";
-
-type FolderResponse = DropboxResponse;
-
-const handler = async (
- req: NextApiRequest,
- res: NextApiResponse
-) => {
- try {
- const query = req.query.query as string;
- const resp = await dropbox.filesSearchV2({
- query,
- });
- res.json(resp);
- } catch (err) {
- res.json(err as DropboxResponse);
- }
-};
-
-export default handler;
diff --git a/pages/index.tsx b/pages/index.tsx
deleted file mode 100644
index ad2d7b4..0000000
--- a/pages/index.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { NextPage } from "next";
-import { Nav, Loading, List, Breadcrumb } from "../components";
-import ky from "ky";
-import { useQuery } from "react-query";
-import { DropboxResponse, files } from "dropbox";
-
-const Home: NextPage = () => {
- const { data, isLoading } = useQuery("home", async () => {
- const resp = await ky
- .get("/api/explore")
- .json>();
- if (resp.status === 200) {
- const resp2 = resp as DropboxResponse;
- return resp2.result.entries.filter(
- (file) => file[".tag"] === "file" || file[".tag"] === "folder"
- ) as (files.FileMetadataReference | files.FolderMetadataReference)[];
- } else {
- throw new Error("Something Went Wrong");
- }
- });
-
- return (
- <>
-
-
- {isLoading && }
- {data &&
}
- >
- );
-};
-
-export default Home;
diff --git a/postcss.config.js b/postcss.config.js
deleted file mode 100644
index 33ad091..0000000
--- a/postcss.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-module.exports = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-}
diff --git a/public/favicon.ico b/public/favicon.ico
index 1cc011e..5dbdfcd 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/react-router.config.ts b/react-router.config.ts
new file mode 100644
index 0000000..fea33dc
--- /dev/null
+++ b/react-router.config.ts
@@ -0,0 +1,8 @@
+import type { Config } from "@react-router/dev/config";
+
+export default {
+ ssr: true,
+ future: {
+ unstable_viteEnvironmentApi: true,
+ },
+} satisfies Config;
diff --git a/render.yaml b/render.yaml
deleted file mode 100644
index 63e964e..0000000
--- a/render.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-services:
- - type: web
- name: dropbox-index
- env: node
- plan: free
- buildCommand: yarn; yarn build
- startCommand: yarn start
- envVars:
- - key: APP_ID
- value: ''
- - key: APP_SECRET
- value: ''
- - key: REFRESH_TOKEN
- value: ''
- - key: NEXT_PUBLIC_TITLE
- value: PublicBox
diff --git a/styles/globals.css b/styles/globals.css
deleted file mode 100644
index 6575cca..0000000
--- a/styles/globals.css
+++ /dev/null
@@ -1,75 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-body {
- @apply bg-slate-900;
- @apply text-slate-100;
-}
-
-.text-shadow {
- text-shadow: 0px 0px 1px #94a3b8;
-}
-
-.loadingScreen {
- min-height: calc(100vh - 120px);
- @apply flex;
- @apply justify-center;
- @apply items-center;
-}
-
-.lds-ellipsis {
- display: inline-block;
- position: relative;
- width: 80px;
- height: 80px;
-}
-.lds-ellipsis div {
- position: absolute;
- top: 33px;
- width: 13px;
- height: 13px;
- border-radius: 50%;
- background: #fff;
- animation-timing-function: cubic-bezier(0, 1, 1, 0);
-}
-.lds-ellipsis div:nth-child(1) {
- left: 8px;
- animation: lds-ellipsis1 0.6s infinite;
-}
-.lds-ellipsis div:nth-child(2) {
- left: 8px;
- animation: lds-ellipsis2 0.6s infinite;
-}
-.lds-ellipsis div:nth-child(3) {
- left: 32px;
- animation: lds-ellipsis2 0.6s infinite;
-}
-.lds-ellipsis div:nth-child(4) {
- left: 56px;
- animation: lds-ellipsis3 0.6s infinite;
-}
-@keyframes lds-ellipsis1 {
- 0% {
- transform: scale(0);
- }
- 100% {
- transform: scale(1);
- }
-}
-@keyframes lds-ellipsis3 {
- 0% {
- transform: scale(1);
- }
- 100% {
- transform: scale(0);
- }
-}
-@keyframes lds-ellipsis2 {
- 0% {
- transform: translate(0, 0);
- }
- 100% {
- transform: translate(24px, 0);
- }
-}
diff --git a/tailwind.config.js b/tailwind.config.js
deleted file mode 100644
index 3b742c5..0000000
--- a/tailwind.config.js
+++ /dev/null
@@ -1,10 +0,0 @@
-module.exports = {
- content: [
- "./pages/**/*.{js,ts,jsx,tsx}",
- "./components/**/*.{js,ts,jsx,tsx}",
- ],
- theme: {
- extend: {},
- },
- plugins: [],
-};
diff --git a/tsconfig.cloudflare.json b/tsconfig.cloudflare.json
new file mode 100644
index 0000000..ab5dbda
--- /dev/null
+++ b/tsconfig.cloudflare.json
@@ -0,0 +1,28 @@
+{
+ "extends": "./tsconfig.json",
+ "include": [
+ ".react-router/types/**/*",
+ "app/**/*",
+ "app/**/.server/**/*",
+ "app/**/.client/**/*",
+ "workers/**/*",
+ "worker-configuration.d.ts"
+ ],
+ "compilerOptions": {
+ "composite": true,
+ "strict": true,
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "types": ["./worker-configuration.d.ts", "vite/client"],
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "baseUrl": ".",
+ "rootDirs": [".", "./.react-router/types"],
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+ "esModuleInterop": true,
+ "resolveJsonModule": true
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index 99710e8..4455fd9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,20 +1,15 @@
{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.node.json" },
+ { "path": "./tsconfig.cloudflare.json" }
+ ],
"compilerOptions": {
- "target": "es5",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
+ "checkJs": true,
+ "verbatimModuleSyntax": true,
"skipLibCheck": true,
"strict": true,
- "forceConsistentCasingInFileNames": true,
"noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "preserve",
- "incremental": true
- },
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"]
+ "types": ["./worker-configuration.d.ts"]
+ }
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..8e3f1d3
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,13 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["tailwind.config.ts", "vite.config.ts"],
+ "compilerOptions": {
+ "composite": true,
+ "strict": true,
+ "types": ["node"],
+ "lib": ["ES2022"],
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "bundler"
+ }
+}
diff --git a/vercel.json b/vercel.json
deleted file mode 100644
index cace29b..0000000
--- a/vercel.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "github": {
- "silent": true
- }
-}
\ No newline at end of file
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..4904355
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,14 @@
+import { reactRouter } from "@react-router/dev/vite";
+import { cloudflare } from "@cloudflare/vite-plugin";
+import tailwindcss from "@tailwindcss/vite";
+import { defineConfig } from "vite";
+import tsconfigPaths from "vite-tsconfig-paths";
+
+export default defineConfig({
+ plugins: [
+ cloudflare({ viteEnvironment: { name: "ssr" } }),
+ tailwindcss(),
+ reactRouter(),
+ tsconfigPaths(),
+ ],
+});
diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts
new file mode 100644
index 0000000..9885808
--- /dev/null
+++ b/worker-configuration.d.ts
@@ -0,0 +1,5705 @@
+// Generated by Wrangler by running `wrangler types` (hash: 2b317eb24d4b37b5e3d5d1b569b23576)
+// Runtime types generated with workerd@1.20250409.0 2025-04-04
+declare namespace Cloudflare {
+ interface Env {
+ TOKEN: KVNamespace;
+ VITE_SITE_TITLE: string;
+ DROPBOX_APP_KEY: string;
+ DROPBOX_APP_SECRET: string;
+ }
+}
+interface Env extends Cloudflare.Env {}
+
+// Begin runtime types
+/*! *****************************************************************************
+Copyright (c) Cloudflare. All rights reserved.
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at http://www.apache.org/licenses/LICENSE-2.0
+THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
+WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+MERCHANTABLITY OR NON-INFRINGEMENT.
+See the Apache Version 2.0 License for specific language governing permissions
+and limitations under the License.
+***************************************************************************** */
+/* eslint-disable */
+// noinspection JSUnusedGlobalSymbols
+declare var onmessage: never;
+/**
+ * An abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException)
+ */
+declare class DOMException extends Error {
+ constructor(message?: string, name?: string);
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message) */
+ readonly message: string;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name) */
+ readonly name: string;
+ /**
+ * @deprecated
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code)
+ */
+ readonly code: number;
+ static readonly INDEX_SIZE_ERR: number;
+ static readonly DOMSTRING_SIZE_ERR: number;
+ static readonly HIERARCHY_REQUEST_ERR: number;
+ static readonly WRONG_DOCUMENT_ERR: number;
+ static readonly INVALID_CHARACTER_ERR: number;
+ static readonly NO_DATA_ALLOWED_ERR: number;
+ static readonly NO_MODIFICATION_ALLOWED_ERR: number;
+ static readonly NOT_FOUND_ERR: number;
+ static readonly NOT_SUPPORTED_ERR: number;
+ static readonly INUSE_ATTRIBUTE_ERR: number;
+ static readonly INVALID_STATE_ERR: number;
+ static readonly SYNTAX_ERR: number;
+ static readonly INVALID_MODIFICATION_ERR: number;
+ static readonly NAMESPACE_ERR: number;
+ static readonly INVALID_ACCESS_ERR: number;
+ static readonly VALIDATION_ERR: number;
+ static readonly TYPE_MISMATCH_ERR: number;
+ static readonly SECURITY_ERR: number;
+ static readonly NETWORK_ERR: number;
+ static readonly ABORT_ERR: number;
+ static readonly URL_MISMATCH_ERR: number;
+ static readonly QUOTA_EXCEEDED_ERR: number;
+ static readonly TIMEOUT_ERR: number;
+ static readonly INVALID_NODE_TYPE_ERR: number;
+ static readonly DATA_CLONE_ERR: number;
+ get stack(): any;
+ set stack(value: any);
+}
+type WorkerGlobalScopeEventMap = {
+ fetch: FetchEvent;
+ scheduled: ScheduledEvent;
+ queue: QueueEvent;
+ unhandledrejection: PromiseRejectionEvent;
+ rejectionhandled: PromiseRejectionEvent;
+};
+declare abstract class WorkerGlobalScope extends EventTarget {
+ EventTarget: typeof EventTarget;
+}
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) */
+interface Console {
+ "assert"(condition?: boolean, ...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static) */
+ clear(): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) */
+ count(label?: string): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countreset_static) */
+ countReset(label?: string): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) */
+ debug(...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static) */
+ dir(item?: any, options?: any): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static) */
+ dirxml(...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static) */
+ error(...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static) */
+ group(...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupcollapsed_static) */
+ groupCollapsed(...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupend_static) */
+ groupEnd(): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) */
+ info(...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) */
+ log(...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static) */
+ table(tabularData?: any, properties?: string[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static) */
+ time(label?: string): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeend_static) */
+ timeEnd(label?: string): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timelog_static) */
+ timeLog(label?: string, ...data: any[]): void;
+ timeStamp(label?: string): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static) */
+ trace(...data: any[]): void;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static) */
+ warn(...data: any[]): void;
+}
+declare const console: Console;
+type BufferSource = ArrayBufferView | ArrayBuffer;
+type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array;
+declare namespace WebAssembly {
+ class CompileError extends Error {
+ constructor(message?: string);
+ }
+ class RuntimeError extends Error {
+ constructor(message?: string);
+ }
+ type ValueType = "anyfunc" | "externref" | "f32" | "f64" | "i32" | "i64" | "v128";
+ interface GlobalDescriptor {
+ value: ValueType;
+ mutable?: boolean;
+ }
+ class Global {
+ constructor(descriptor: GlobalDescriptor, value?: any);
+ value: any;
+ valueOf(): any;
+ }
+ type ImportValue = ExportValue | number;
+ type ModuleImports = Record;
+ type Imports = Record;
+ type ExportValue = Function | Global | Memory | Table;
+ type Exports = Record;
+ class Instance {
+ constructor(module: Module, imports?: Imports);
+ readonly exports: Exports;
+ }
+ interface MemoryDescriptor {
+ initial: number;
+ maximum?: number;
+ shared?: boolean;
+ }
+ class Memory {
+ constructor(descriptor: MemoryDescriptor);
+ readonly buffer: ArrayBuffer;
+ grow(delta: number): number;
+ }
+ type ImportExportKind = "function" | "global" | "memory" | "table";
+ interface ModuleExportDescriptor {
+ kind: ImportExportKind;
+ name: string;
+ }
+ interface ModuleImportDescriptor {
+ kind: ImportExportKind;
+ module: string;
+ name: string;
+ }
+ abstract class Module {
+ static customSections(module: Module, sectionName: string): ArrayBuffer[];
+ static exports(module: Module): ModuleExportDescriptor[];
+ static imports(module: Module): ModuleImportDescriptor[];
+ }
+ type TableKind = "anyfunc" | "externref";
+ interface TableDescriptor {
+ element: TableKind;
+ initial: number;
+ maximum?: number;
+ }
+ class Table {
+ constructor(descriptor: TableDescriptor, value?: any);
+ readonly length: number;
+ get(index: number): any;
+ grow(delta: number, value?: any): number;
+ set(index: number, value?: any): void;
+ }
+ function instantiate(module: Module, imports?: Imports): Promise;
+ function validate(bytes: BufferSource): boolean;
+}
+/**
+ * This ServiceWorker API interface represents the global execution context of a service worker.
+ * Available only in secure contexts.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope)
+ */
+interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
+ DOMException: typeof DOMException;
+ WorkerGlobalScope: typeof WorkerGlobalScope;
+ btoa(data: string): string;
+ atob(data: string): string;
+ setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;
+ setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;
+ clearTimeout(timeoutId: number | null): void;
+ setInterval(callback: (...args: any[]) => void, msDelay?: number): number;
+ setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;
+ clearInterval(timeoutId: number | null): void;
+ queueMicrotask(task: Function): void;
+ structuredClone(value: T, options?: StructuredSerializeOptions): T;
+ reportError(error: any): void;
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise;
+ self: ServiceWorkerGlobalScope;
+ crypto: Crypto;
+ caches: CacheStorage;
+ scheduler: Scheduler;
+ performance: Performance;
+ Cloudflare: Cloudflare;
+ readonly origin: string;
+ Event: typeof Event;
+ ExtendableEvent: typeof ExtendableEvent;
+ CustomEvent: typeof CustomEvent;
+ PromiseRejectionEvent: typeof PromiseRejectionEvent;
+ FetchEvent: typeof FetchEvent;
+ TailEvent: typeof TailEvent;
+ TraceEvent: typeof TailEvent;
+ ScheduledEvent: typeof ScheduledEvent;
+ MessageEvent: typeof MessageEvent;
+ CloseEvent: typeof CloseEvent;
+ ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader;
+ ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader;
+ ReadableStream: typeof ReadableStream;
+ WritableStream: typeof WritableStream;
+ WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter;
+ TransformStream: typeof TransformStream;
+ ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy;
+ CountQueuingStrategy: typeof CountQueuingStrategy;
+ ErrorEvent: typeof ErrorEvent;
+ EventSource: typeof EventSource;
+ ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest;
+ ReadableStreamDefaultController: typeof ReadableStreamDefaultController;
+ ReadableByteStreamController: typeof ReadableByteStreamController;
+ WritableStreamDefaultController: typeof WritableStreamDefaultController;
+ TransformStreamDefaultController: typeof TransformStreamDefaultController;
+ CompressionStream: typeof CompressionStream;
+ DecompressionStream: typeof DecompressionStream;
+ TextEncoderStream: typeof TextEncoderStream;
+ TextDecoderStream: typeof TextDecoderStream;
+ Headers: typeof Headers;
+ Body: typeof Body;
+ Request: typeof Request;
+ Response: typeof Response;
+ WebSocket: typeof WebSocket;
+ WebSocketPair: typeof WebSocketPair;
+ WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair;
+ AbortController: typeof AbortController;
+ AbortSignal: typeof AbortSignal;
+ TextDecoder: typeof TextDecoder;
+ TextEncoder: typeof TextEncoder;
+ navigator: Navigator;
+ Navigator: typeof Navigator;
+ URL: typeof URL;
+ URLSearchParams: typeof URLSearchParams;
+ URLPattern: typeof URLPattern;
+ Blob: typeof Blob;
+ File: typeof File;
+ FormData: typeof FormData;
+ Crypto: typeof Crypto;
+ SubtleCrypto: typeof SubtleCrypto;
+ CryptoKey: typeof CryptoKey;
+ CacheStorage: typeof CacheStorage;
+ Cache: typeof Cache;
+ FixedLengthStream: typeof FixedLengthStream;
+ IdentityTransformStream: typeof IdentityTransformStream;
+ HTMLRewriter: typeof HTMLRewriter;
+}
+declare function addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void;
+declare function removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void;
+/**
+ * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)
+ */
+declare function dispatchEvent(event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap]): boolean;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */
+declare function btoa(data: string): string;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */
+declare function atob(data: string): string;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setTimeout) */
+declare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setTimeout) */
+declare function setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/clearTimeout) */
+declare function clearTimeout(timeoutId: number | null): void;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setInterval) */
+declare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setInterval) */
+declare function setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/clearInterval) */
+declare function clearInterval(timeoutId: number | null): void;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/queueMicrotask) */
+declare function queueMicrotask(task: Function): void;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/structuredClone) */
+declare function structuredClone(value: T, options?: StructuredSerializeOptions): T;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/reportError) */
+declare function reportError(error: any): void;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch) */
+declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise;
+declare const self: ServiceWorkerGlobalScope;
+/**
+* The Web Crypto API provides a set of low-level functions for common cryptographic tasks.
+* The Workers runtime implements the full surface of this API, but with some differences in
+* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)
+* compared to those implemented in most browsers.
+*
+* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)
+*/
+declare const crypto: Crypto;
+/**
+* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.
+*
+* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)
+*/
+declare const caches: CacheStorage;
+declare const scheduler: Scheduler;
+/**
+* The Workers runtime supports a subset of the Performance API, used to measure timing and performance,
+* as well as timing of subrequests and other operations.
+*
+* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)
+*/
+declare const performance: Performance;
+declare const Cloudflare: Cloudflare;
+declare const origin: string;
+declare const navigator: Navigator;
+interface TestController {
+}
+interface ExecutionContext {
+ waitUntil(promise: Promise): void;
+ passThroughOnException(): void;
+ props: any;
+}
+type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise;
+type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise;
+type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise;
+type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise;
+type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise;
+type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise;
+type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise;
+interface ExportedHandler {
+ fetch?: ExportedHandlerFetchHandler;
+ tail?: ExportedHandlerTailHandler;
+ trace?: ExportedHandlerTraceHandler;
+ tailStream?: ExportedHandlerTailStreamHandler;
+ scheduled?: ExportedHandlerScheduledHandler;
+ test?: ExportedHandlerTestHandler;
+ email?: EmailExportedHandler;
+ queue?: ExportedHandlerQueueHandler;
+}
+interface StructuredSerializeOptions {
+ transfer?: any[];
+}
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent) */
+declare abstract class PromiseRejectionEvent extends Event {
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise) */
+ readonly promise: Promise;
+ /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason) */
+ readonly reason: any;
+}
+declare abstract class Navigator {
+ sendBeacon(url: string, body?: (ReadableStream | string | (ArrayBuffer | ArrayBufferView) | Blob | FormData | URLSearchParams | URLSearchParams)): boolean;
+ readonly userAgent: string;
+ readonly hardwareConcurrency: number;
+}
+/**
+* The Workers runtime supports a subset of the Performance API, used to measure timing and performance,
+* as well as timing of subrequests and other operations.
+*
+* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)
+*/
+interface Performance {
+ /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */
+ readonly timeOrigin: number;
+ /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */
+ now(): number;
+}
+interface AlarmInvocationInfo {
+ readonly isRetry: boolean;
+ readonly retryCount: number;
+}
+interface Cloudflare {
+ readonly compatibilityFlags: Record;
+}
+interface DurableObject {
+ fetch(request: Request): Response | Promise;
+ alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise;
+ webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise;
+ webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise;
+ webSocketError?(ws: WebSocket, error: unknown): void | Promise;
+}
+type DurableObjectStub = Fetcher & {
+ readonly id: DurableObjectId;
+ readonly name?: string;
+};
+interface DurableObjectId {
+ toString(): string;
+ equals(other: DurableObjectId): boolean;
+ readonly name?: string;
+}
+interface DurableObjectNamespace {
+ newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId;
+ idFromName(name: string): DurableObjectId;
+ idFromString(id: string): DurableObjectId;
+ get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub;
+ jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace;
+}
+type DurableObjectJurisdiction = "eu" | "fedramp";
+interface DurableObjectNamespaceNewUniqueIdOptions {
+ jurisdiction?: DurableObjectJurisdiction;
+}
+type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me";
+interface DurableObjectNamespaceGetDurableObjectOptions {
+ locationHint?: DurableObjectLocationHint;
+}
+interface DurableObjectState {
+ waitUntil(promise: Promise): void;
+ readonly id: DurableObjectId;
+ readonly storage: DurableObjectStorage;
+ container?: Container;
+ blockConcurrencyWhile(callback: () => Promise): Promise;
+ acceptWebSocket(ws: WebSocket, tags?: string[]): void;
+ getWebSockets(tag?: string): WebSocket[];
+ setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void;
+ getWebSocketAutoResponse(): WebSocketRequestResponsePair | null;
+ getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null;
+ setHibernatableWebSocketEventTimeout(timeoutMs?: number): void;
+ getHibernatableWebSocketEventTimeout(): number | null;
+ getTags(ws: WebSocket): string[];
+ abort(reason?: string): void;
+}
+interface DurableObjectTransaction {
+ get(key: string, options?: DurableObjectGetOptions): Promise;
+ get(keys: string[], options?: DurableObjectGetOptions): Promise