diff --git a/3 b/3
new file mode 100644
index 00000000..371bd01a
--- /dev/null
+++ b/3
@@ -0,0 +1,119 @@
+export const OWNER = "Yidadaa";
+export const REPO = "ChatGPT-Next-Web";
+export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
+export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
+export const UPDATE_URL = `${REPO_URL}#keep-updated`;
+export const RELEASE_URL = `${REPO_URL}/releases`;
+export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
+export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
+export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
+export const DEFAULT_API_HOST = "https://chatgpt1.nextweb.fun/api/proxy";
+
+export enum Path {
+ Home = "/",
+ Chat = "/chat",
+ Settings = "/settings",
+ NewChat = "/new-chat",
+ Masks = "/masks",
+ Auth = "/auth",
+}
+
+export enum SlotID {
+ AppBody = "app-body",
+}
+
+export enum FileName {
+ Masks = "masks.json",
+ Prompts = "prompts.json",
+}
+
+export enum StoreKey {
+ Chat = "chat-next-web-store",
+ Access = "access-control",
+ Config = "app-config",
+ Mask = "mask-store",
+ Prompt = "prompt-store",
+ Update = "chat-update",
+ Sync = "sync",
+}
+
+export const MAX_SIDEBAR_WIDTH = 500;
+export const MIN_SIDEBAR_WIDTH = 230;
+export const NARROW_SIDEBAR_WIDTH = 100;
+
+export const ACCESS_CODE_PREFIX = "nk-";
+
+export const LAST_INPUT_KEY = "last-input";
+export const UNFINISHED_INPUT = (id: string) => "unfinished-input-" + id;
+
+export const STORAGE_KEY = "chatgpt-next-web";
+
+export const REQUEST_TIMEOUT_MS = 60000;
+
+export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
+
+export const OpenaiPath = {
+ ChatPath: "v1/chat/completions",
+ UsagePath: "dashboard/billing/usage",
+ SubsPath: "dashboard/billing/subscription",
+ ListModelPath: "v1/models",
+};
+
+export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
+export const DEFAULT_SYSTEM_TEMPLATE = `
+You are ChatGPT, a large language model trained by OpenAI.
+Knowledge cutoff: 2021-09
+Current model: {{model}}
+Current time: {{time}}`;
+
+export const SUMMARIZE_MODEL = "gpt-3.5-turbo";
+
+export const DEFAULT_MODELS = [
+ {
+ name: "gpt-4",
+ available: true,
+ },
+ {
+ name: "gpt-4-0314",
+ available: true,
+ },
+ {
+ name: "gpt-4-0613",
+ available: true,
+ },
+ {
+ name: "gpt-4-32k",
+ available: true,
+ },
+ {
+ name: "gpt-4-32k-0314",
+ available: true,
+ },
+ {
+ name: "gpt-4-32k-0613",
+ available: true,
+ },
+ {
+ name: "gpt-3.5-turbo",
+ available: true,
+ },
+ {
+ name: "gpt-3.5-turbo-0301",
+ available: true,
+ },
+ {
+ name: "gpt-3.5-turbo-0613",
+ available: true,
+ },
+ {
+ name: "gpt-3.5-turbo-16k",
+ available: true,
+ },
+ {
+ name: "gpt-3.5-turbo-16k-0613",
+ available: true,
+ },
+] as const;
+
+export const CHAT_PAGE_SIZE = 15;
+export const MAX_RENDER_MSG_COUNT = 45;
diff --git a/app/api/cors/[...path]/route.ts b/app/api/cors/[...path]/route.ts
new file mode 100644
index 00000000..c461d250
--- /dev/null
+++ b/app/api/cors/[...path]/route.ts
@@ -0,0 +1,44 @@
+import { NextRequest, NextResponse } from "next/server";
+
+async function handle(
+ req: NextRequest,
+ { params }: { params: { path: string[] } },
+) {
+ if (req.method === "OPTIONS") {
+ return NextResponse.json({ body: "OK" }, { status: 200 });
+ }
+
+ const [protocol, ...subpath] = params.path;
+ const targetUrl = `${protocol}://${subpath.join("/")}`;
+
+ const method = req.headers.get("method") ?? undefined;
+ const shouldNotHaveBody = ["get", "head"].includes(
+ method?.toLowerCase() ?? "",
+ );
+
+ const fetchOptions: RequestInit = {
+ headers: {
+ authorization: req.headers.get("authorization") ?? "",
+ },
+ body: shouldNotHaveBody ? null : req.body,
+ method,
+ // @ts-ignore
+ duplex: "half",
+ };
+
+ console.log("[Any Proxy]", targetUrl);
+
+ const fetchResult = fetch(targetUrl, fetchOptions);
+
+ return fetchResult;
+}
+
+export const GET = handle;
+export const POST = handle;
+export const PUT = handle;
+
+// nextjs dose not support those https methods, sucks
+export const PROFIND = handle;
+export const MKCOL = handle;
+
+export const runtime = "edge";
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 9de603bb..8e43e1d1 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -12,6 +12,12 @@ import EditIcon from "../icons/edit.svg";
import EyeIcon from "../icons/eye.svg";
import DownloadIcon from "../icons/download.svg";
import UploadIcon from "../icons/upload.svg";
+import ConfigIcon from "../icons/config.svg";
+import ConfirmIcon from "../icons/confirm.svg";
+
+import ConnectionIcon from "../icons/connection.svg";
+import CloudSuccessIcon from "../icons/cloud-success.svg";
+import CloudFailIcon from "../icons/cloud-fail.svg";
import {
Input,
@@ -54,6 +60,7 @@ import { getClientConfig } from "../config/client";
import { useSyncStore } from "../store/sync";
import { nanoid } from "nanoid";
import { useMaskStore } from "../store/mask";
+import { ProviderType } from "../utils/cloud";
function EditPromptModal(props: { id: string; onClose: () => void }) {
const promptStore = usePromptStore();
@@ -247,12 +254,183 @@ function DangerItems() {
);
}
+function CheckButton() {
+ const syncStore = useSyncStore();
+
+ const couldCheck = useMemo(() => {
+ return syncStore.coundSync();
+ }, [syncStore]);
+
+ const [checkState, setCheckState] = useState<
+ "none" | "checking" | "success" | "failed"
+ >("none");
+
+ async function check() {
+ setCheckState("checking");
+ const valid = await syncStore.check();
+ setCheckState(valid ? "success" : "failed");
+ }
+
+ if (!couldCheck) return null;
+
+ return (
+
+ ) : checkState === "checking" ? (
+
+ ) : checkState === "success" ? (
+
+ ) : checkState === "failed" ? (
+
+ ) : (
+
+ )
+ }
+ >
+ );
+}
+
+function SyncConfigModal(props: { onClose?: () => void }) {
+ const syncStore = useSyncStore();
+
+ return (
+
+ );
+}
+
function SyncItems() {
const syncStore = useSyncStore();
- const webdav = syncStore.webDavConfig;
const chatStore = useChatStore();
const promptStore = usePromptStore();
const maskStore = useMaskStore();
+ const couldSync = useMemo(() => {
+ return syncStore.coundSync();
+ }, [syncStore]);
+
+ const [showSyncConfigModal, setShowSyncConfigModal] = useState(false);
const stateOverview = useMemo(() => {
const sessions = chatStore.sessions;
@@ -267,42 +445,71 @@ function SyncItems() {
}, [chatStore.sessions, maskStore.masks, promptStore.prompts]);
return (
-
-
- }
- text={Locale.UI.Sync}
- onClick={() => {
- showToast(Locale.WIP);
- }}
- />
-
+ <>
+
+
+
+ }
+ text={Locale.UI.Config}
+ onClick={() => {
+ setShowSyncConfigModal(true);
+ }}
+ />
+ {couldSync && (
+ }
+ text={Locale.UI.Sync}
+ onClick={async () => {
+ try {
+ await syncStore.sync();
+ showToast(Locale.Settings.Sync.Success);
+ } catch (e) {
+ showToast(Locale.Settings.Sync.Fail);
+ console.error("[Sync]", e);
+ }
+ }}
+ />
+ )}
+
+
-
-
- }
- text={Locale.UI.Export}
- onClick={() => {
- syncStore.export();
- }}
- />
- }
- text={Locale.UI.Import}
- onClick={() => {
- syncStore.import();
- }}
- />
-
-
-
+
+
+ }
+ text={Locale.UI.Export}
+ onClick={() => {
+ syncStore.export();
+ }}
+ />
+ }
+ text={Locale.UI.Import}
+ onClick={() => {
+ syncStore.import();
+ }}
+ />
+
+
+
+
+ {showSyncConfigModal && (
+ setShowSyncConfigModal(false)} />
+ )}
+ >
);
}
diff --git a/app/constant.ts b/app/constant.ts
index 2141820c..f76eb3a9 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -7,7 +7,9 @@ export const RELEASE_URL = `${REPO_URL}/releases`;
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
-export const DEFAULT_API_HOST = "https://chatgpt1.nextweb.fun/api/proxy";
+
+export const DEFAULT_CORS_HOST = "https://chatgpt2.nextweb.fun";
+export const DEFAULT_API_HOST = `${DEFAULT_CORS_HOST}/api/proxy`;
export enum Path {
Home = "/",
@@ -18,6 +20,10 @@ export enum Path {
Auth = "/auth",
}
+export enum ApiPath {
+ Cors = "/api/cors",
+}
+
export enum SlotID {
AppBody = "app-body",
}
@@ -46,6 +52,8 @@ export const ACCESS_CODE_PREFIX = "nk-";
export const LAST_INPUT_KEY = "last-input";
export const UNFINISHED_INPUT = (id: string) => "unfinished-input-" + id;
+export const STORAGE_KEY = "chatgpt-next-web";
+
export const REQUEST_TIMEOUT_MS = 60000;
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
diff --git a/app/icons/cloud-fail.svg b/app/icons/cloud-fail.svg
new file mode 100644
index 00000000..6e6a35fe
--- /dev/null
+++ b/app/icons/cloud-fail.svg
@@ -0,0 +1 @@
+
diff --git a/app/icons/cloud-success.svg b/app/icons/cloud-success.svg
new file mode 100644
index 00000000..8c5f3d6f
--- /dev/null
+++ b/app/icons/cloud-success.svg
@@ -0,0 +1 @@
+
diff --git a/app/icons/config.svg b/app/icons/config.svg
new file mode 100644
index 00000000..7e1d23a2
--- /dev/null
+++ b/app/icons/config.svg
@@ -0,0 +1 @@
+
diff --git a/app/icons/connection.svg b/app/icons/connection.svg
new file mode 100644
index 00000000..03687302
--- /dev/null
+++ b/app/icons/connection.svg
@@ -0,0 +1 @@
+
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index a1753417..1b8850f4 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -179,7 +179,35 @@ const cn = {
SubTitle: "根据对话内容生成合适的标题",
},
Sync: {
- LastUpdate: "上次同步",
+ CloudState: "云端数据",
+ NotSyncYet: "还没有进行过同步",
+ Success: "同步成功",
+ Fail: "同步失败",
+
+ Config: {
+ Modal: {
+ Title: "配置云同步",
+ },
+ SyncType: {
+ Title: "同步类型",
+ SubTitle: "选择喜爱的同步服务器",
+ },
+ Proxy: {
+ Title: "启用代理",
+ SubTitle: "在浏览器中同步时,必须启用代理以避免跨域限制",
+ },
+ ProxyUrl: {
+ Title: "代理地址",
+ SubTitle: "仅适用于本项目自带的跨域代理",
+ },
+
+ WebDav: {
+ Endpoint: "WebDAV 地址",
+ UserName: "用户名",
+ Password: "密码",
+ },
+ },
+
LocalState: "本地数据",
Overview: (overview: any) => {
return `${overview.chat} 次对话,${overview.message} 条消息,${overview.prompt} 条提示词,${overview.mask} 个面具`;
@@ -366,6 +394,7 @@ const cn = {
Export: "导出",
Import: "导入",
Sync: "同步",
+ Config: "配置",
},
Exporter: {
Model: "模型",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index e3129578..ebbf1a37 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -181,7 +181,36 @@ const en: LocaleType = {
SubTitle: "Generate a suitable title based on the conversation content",
},
Sync: {
- LastUpdate: "Last Update",
+ CloudState: "Last Update",
+ NotSyncYet: "Not sync yet",
+ Success: "Sync Success",
+ Fail: "Sync Fail",
+
+ Config: {
+ Modal: {
+ Title: "Config Sync",
+ },
+ SyncType: {
+ Title: "Sync Type",
+ SubTitle: "Choose your favorite sync service",
+ },
+ Proxy: {
+ Title: "Enable CORS Proxy",
+ SubTitle: "Enable a proxy to avoid cross-origin restrictions",
+ },
+ ProxyUrl: {
+ Title: "Proxy Endpoint",
+ SubTitle:
+ "Only applicable to the built-in CORS proxy for this project",
+ },
+
+ WebDav: {
+ Endpoint: "WebDAV Endpoint",
+ UserName: "User Name",
+ Password: "Password",
+ },
+ },
+
LocalState: "Local Data",
Overview: (overview: any) => {
return `${overview.chat} chats,${overview.message} messages,${overview.prompt} prompts,${overview.mask} masks`;
@@ -366,6 +395,7 @@ const en: LocaleType = {
Export: "Export",
Import: "Import",
Sync: "Sync",
+ Config: "Config",
},
Exporter: {
Model: "Model",
diff --git a/app/store/sync.ts b/app/store/sync.ts
index 502cf71c..29b6a82c 100644
--- a/app/store/sync.ts
+++ b/app/store/sync.ts
@@ -1,15 +1,18 @@
import { Updater } from "../typing";
-import { StoreKey } from "../constant";
+import { ApiPath, StoreKey } from "../constant";
import { createPersistStore } from "../utils/store";
import {
AppState,
getLocalAppState,
+ GetStoreState,
mergeAppState,
setLocalAppState,
} from "../utils/sync";
import { downloadAs, readFromFile } from "../utils";
import { showToast } from "../components/ui-lib";
import Locale from "../locales";
+import { createSyncClient, ProviderType } from "../utils/cloud";
+import { corsPath } from "../utils/cors";
export interface WebDavConfig {
server: string;
@@ -17,22 +20,43 @@ export interface WebDavConfig {
password: string;
}
+export type SyncStore = GetStoreState;
+
export const useSyncStore = createPersistStore(
{
- webDavConfig: {
- server: "",
+ provider: ProviderType.WebDAV,
+ useProxy: true,
+ proxyUrl: corsPath(ApiPath.Cors),
+
+ webdav: {
+ endpoint: "",
username: "",
password: "",
},
+ upstash: {
+ endpoint: "",
+ username: "",
+ apiKey: "",
+ },
+
lastSyncTime: 0,
+ lastProvider: "",
},
(set, get) => ({
+ coundSync() {
+ const config = get()[get().provider];
+ return Object.values(config).every((c) => c.toString().length > 0);
+ },
+
+ markSyncTime() {
+ set({ lastSyncTime: Date.now(), lastProvider: get().provider });
+ },
+
export() {
const state = getLocalAppState();
const fileName = `Backup-${new Date().toLocaleString()}.json`;
downloadAs(JSON.stringify(state), fileName);
- set({ lastSyncTime: Date.now() });
},
async import() {
@@ -50,47 +74,36 @@ export const useSyncStore = createPersistStore(
}
},
- async check() {
+ getClient() {
+ const provider = get().provider;
+ const client = createSyncClient(provider, get());
+ return client;
+ },
+
+ async sync() {
+ const localState = getLocalAppState();
+ const provider = get().provider;
+ const config = get()[provider];
+ const client = this.getClient();
+
try {
- const res = await fetch(this.path(""), {
- method: "PROFIND",
- headers: this.headers(),
- });
- const sanitizedRes = {
- status: res.status,
- statusText: res.statusText,
- headers: res.headers,
- };
- console.log(sanitizedRes);
- return res.status === 207;
+ const remoteState = JSON.parse(
+ await client.get(config.username),
+ ) as AppState;
+ mergeAppState(localState, remoteState);
+ setLocalAppState(localState);
} catch (e) {
- console.error("[Sync] ", e);
- return false;
+ console.log("[Sync] failed to get remoate state", e);
}
+
+ await client.set(config.username, JSON.stringify(localState));
+
+ this.markSyncTime();
},
- path(path: string) {
- let url = get().webDavConfig.server;
-
- if (!url.endsWith("/")) {
- url += "/";
- }
-
- if (path.startsWith("/")) {
- path = path.slice(1);
- }
-
- return url + path;
- },
-
- headers() {
- const auth = btoa(
- [get().webDavConfig.username, get().webDavConfig.password].join(":"),
- );
-
- return {
- Authorization: `Basic ${auth}`,
- };
+ async check() {
+ const client = this.getClient();
+ return await client.check();
},
}),
{
diff --git a/app/utils/cloud/index.ts b/app/utils/cloud/index.ts
new file mode 100644
index 00000000..63908249
--- /dev/null
+++ b/app/utils/cloud/index.ts
@@ -0,0 +1,33 @@
+import { createWebDavClient } from "./webdav";
+import { createUpstashClient } from "./upstash";
+
+export enum ProviderType {
+ WebDAV = "webdav",
+ UpStash = "upstash",
+}
+
+export const SyncClients = {
+ [ProviderType.UpStash]: createUpstashClient,
+ [ProviderType.WebDAV]: createWebDavClient,
+} as const;
+
+type SyncClientConfig = {
+ [K in keyof typeof SyncClients]: (typeof SyncClients)[K] extends (
+ _: infer C,
+ ) => any
+ ? C
+ : never;
+};
+
+export type SyncClient = {
+ get: (key: string) => Promise;
+ set: (key: string, value: string) => Promise;
+ check: () => Promise;
+};
+
+export function createSyncClient(
+ provider: T,
+ config: SyncClientConfig[T],
+): SyncClient {
+ return SyncClients[provider](config as any) as any;
+}
diff --git a/app/utils/cloud/upstash.ts b/app/utils/cloud/upstash.ts
new file mode 100644
index 00000000..6f9b30f6
--- /dev/null
+++ b/app/utils/cloud/upstash.ts
@@ -0,0 +1,39 @@
+import { SyncStore } from "@/app/store/sync";
+
+export type UpstashConfig = SyncStore["upstash"];
+export type UpStashClient = ReturnType;
+
+export function createUpstashClient(config: UpstashConfig) {
+ return {
+ async check() {
+ return true;
+ },
+
+ async get() {
+ throw Error("[Sync] not implemented");
+ },
+
+ async set() {
+ throw Error("[Sync] not implemented");
+ },
+
+ headers() {
+ return {
+ Authorization: `Basic ${config.apiKey}`,
+ };
+ },
+ path(path: string) {
+ let url = config.endpoint;
+
+ if (!url.endsWith("/")) {
+ url += "/";
+ }
+
+ if (path.startsWith("/")) {
+ path = path.slice(1);
+ }
+
+ return url + path;
+ },
+ };
+}
diff --git a/app/utils/cloud/webdav.ts b/app/utils/cloud/webdav.ts
new file mode 100644
index 00000000..5386b4d1
--- /dev/null
+++ b/app/utils/cloud/webdav.ts
@@ -0,0 +1,78 @@
+import { STORAGE_KEY } from "@/app/constant";
+import { SyncStore } from "@/app/store/sync";
+import { corsFetch } from "../cors";
+
+export type WebDAVConfig = SyncStore["webdav"];
+export type WebDavClient = ReturnType;
+
+export function createWebDavClient(store: SyncStore) {
+ const folder = STORAGE_KEY;
+ const fileName = `${folder}/backup.json`;
+ const config = store.webdav;
+ const proxyUrl =
+ store.useProxy && store.proxyUrl.length > 0 ? store.proxyUrl : undefined;
+
+ return {
+ async check() {
+ try {
+ const res = await corsFetch(this.path(folder), {
+ method: "MKCOL",
+ headers: this.headers(),
+ proxyUrl,
+ });
+
+ console.log("[WebDav] check", res.status, res.statusText);
+
+ return [201, 200, 404].includes(res.status);
+ } catch (e) {
+ console.error("[WebDav] failed to check", e);
+ }
+
+ return false;
+ },
+
+ async get(key: string) {
+ const res = await corsFetch(this.path(fileName), {
+ method: "GET",
+ headers: this.headers(),
+ proxyUrl,
+ });
+
+ console.log("[WebDav] get key = ", key, res.status, res.statusText);
+
+ return await res.text();
+ },
+
+ async set(key: string, value: string) {
+ const res = await corsFetch(this.path(fileName), {
+ method: "PUT",
+ headers: this.headers(),
+ body: value,
+ proxyUrl,
+ });
+
+ console.log("[WebDav] set key = ", key, res.status, res.statusText);
+ },
+
+ headers() {
+ const auth = btoa(config.username + ":" + config.password);
+
+ return {
+ authorization: `Basic ${auth}`,
+ };
+ },
+ path(path: string) {
+ let url = config.endpoint;
+
+ if (!url.endsWith("/")) {
+ url += "/";
+ }
+
+ if (path.startsWith("/")) {
+ path = path.slice(1);
+ }
+
+ return url + path;
+ },
+ };
+}
diff --git a/app/utils/cors.ts b/app/utils/cors.ts
new file mode 100644
index 00000000..773f152a
--- /dev/null
+++ b/app/utils/cors.ts
@@ -0,0 +1,50 @@
+import { getClientConfig } from "../config/client";
+import { ApiPath, DEFAULT_CORS_HOST } from "../constant";
+
+export function corsPath(path: string) {
+ const baseUrl = getClientConfig()?.isApp ? `${DEFAULT_CORS_HOST}` : "";
+
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+
+ if (!path.endsWith("/")) {
+ path += "/";
+ }
+
+ return `${baseUrl}${path}`;
+}
+
+export function corsFetch(
+ url: string,
+ options: RequestInit & {
+ proxyUrl?: string;
+ },
+) {
+ if (!url.startsWith("http")) {
+ throw Error("[CORS Fetch] url must starts with http/https");
+ }
+
+ let proxyUrl = options.proxyUrl ?? corsPath(ApiPath.Cors);
+ if (!proxyUrl.endsWith("/")) {
+ proxyUrl += "/";
+ }
+
+ url = url.replace("://", "/");
+
+ const corsOptions = {
+ ...options,
+ method: "POST",
+ headers: options.method
+ ? {
+ ...options.headers,
+ method: options.method,
+ }
+ : options.headers,
+ };
+
+ const corsUrl = proxyUrl + url;
+ console.info("[CORS] target = ", corsUrl);
+
+ return fetch(corsUrl, corsOptions);
+}
diff --git a/next.config.mjs b/next.config.mjs
index c8f17de8..4faa63e5 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -35,27 +35,29 @@ const nextConfig = {
},
};
+const CorsHeaders = [
+ { key: "Access-Control-Allow-Credentials", value: "true" },
+ { key: "Access-Control-Allow-Origin", value: "*" },
+ {
+ key: "Access-Control-Allow-Methods",
+ value: "*",
+ },
+ {
+ key: "Access-Control-Allow-Headers",
+ value: "*",
+ },
+ {
+ key: "Access-Control-Max-Age",
+ value: "86400",
+ },
+];
+
if (mode !== "export") {
nextConfig.headers = async () => {
return [
{
source: "/api/:path*",
- headers: [
- { key: "Access-Control-Allow-Credentials", value: "true" },
- { key: "Access-Control-Allow-Origin", value: "*" },
- {
- key: "Access-Control-Allow-Methods",
- value: "*",
- },
- {
- key: "Access-Control-Allow-Headers",
- value: "*",
- },
- {
- key: "Access-Control-Max-Age",
- value: "86400",
- },
- ],
+ headers: CorsHeaders,
},
];
};
@@ -76,15 +78,6 @@ if (mode !== "export") {
},
];
- const apiUrl = process.env.API_URL;
- if (apiUrl) {
- console.log("[Next] using api url ", apiUrl);
- ret.push({
- source: "/api/:path*",
- destination: `${apiUrl}/:path*`,
- });
- }
-
return {
beforeFiles: ret,
};