Add CORS for the sync API
This commit is contained in:
@@ -96,9 +96,6 @@ jobs:
|
||||
- name: copy .env.local to apps/readest-app
|
||||
run: cp .env.local apps/readest-app/.env.local
|
||||
|
||||
- name: fix dynamic route for Next.js, see https://github.com/vercel/next.js/discussions/55393
|
||||
run: rimraf "apps/readest-app/src/app/reader/[ids]"
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.config.os == 'ubuntu-latest'
|
||||
run: |
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||
"@zip.js/zip.js": "^2.7.53",
|
||||
"clsx": "^2.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"cssbeautify": "^0.3.1",
|
||||
"foliate-js": "workspace:*",
|
||||
"js-md5": "^0.8.3",
|
||||
@@ -57,6 +58,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.1.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/cssbeautify": "^0.3.5",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/react": "18.3.12",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": "'self' 'unsafe-inline' blob: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost https://fonts.gstatic.com https://db.onlinewebfonts.com",
|
||||
"connect-src": "'self' blob: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co",
|
||||
"connect-src": "'self' blob: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com",
|
||||
"img-src": "'self' blob: data: asset: http://asset.localhost https://*",
|
||||
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost",
|
||||
"frame-src": "'self' blob: asset: http://asset.localhost",
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
import { EnvProvider } from '@/context/EnvContext';
|
||||
import { CSPostHogProvider } from '@/context/PHContext';
|
||||
import { SyncProvider } from '@/context/SyncContext';
|
||||
import Providers from '@/components/Providers';
|
||||
|
||||
import '../styles/globals.css';
|
||||
import '../styles/fonts.css';
|
||||
@@ -39,15 +36,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<meta name='twitter:description' content={description} />
|
||||
<meta name='twitter:image' content={previewImage} />
|
||||
</head>
|
||||
<CSPostHogProvider>
|
||||
<body>
|
||||
<EnvProvider>
|
||||
<AuthProvider>
|
||||
<SyncProvider>{children}</SyncProvider>
|
||||
</AuthProvider>
|
||||
</EnvProvider>
|
||||
</body>
|
||||
</CSPostHogProvider>
|
||||
<body>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, isSelectMode, onImp
|
||||
const [selectedBooks, setSelectedBooks] = useState<string[]>([]);
|
||||
const [showDeleteAlert, setShowDeleteAlert] = useState(false);
|
||||
const [clickedImage, setClickedImage] = useState<string | null>(null);
|
||||
const [importBookUrl] = useState(searchParams.get('url') || '');
|
||||
const [importBookUrl] = useState(searchParams?.get('url') || '');
|
||||
const isImportingBook = useRef(false);
|
||||
|
||||
const { setLibrary } = useLibraryStore();
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import Reader from '../components/Reader';
|
||||
|
||||
export default async function Page({ params }: { params: Promise<{ ids: string }> }) {
|
||||
const ids = decodeURIComponent((await params).ids);
|
||||
return <Reader ids={ids} />;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
|
||||
if (isInitiating.current) return;
|
||||
isInitiating.current = true;
|
||||
|
||||
const bookIds = ids || searchParams.get('ids') || '';
|
||||
const bookIds = ids || searchParams?.get('ids') || '';
|
||||
const initialIds = bookIds.split(BOOK_IDS_SEPARATOR).filter(Boolean);
|
||||
const initialBookKeys = initialIds.map((id) => `${id}-${uniqueId()}`);
|
||||
setBookKeys(initialBookKeys);
|
||||
|
||||
@@ -21,7 +21,7 @@ const useBooksManager = () => {
|
||||
if (shouldUpdateSearchParams) {
|
||||
const ids = bookKeys.map((key) => key.split('-')[0]!);
|
||||
if (ids) {
|
||||
navigateToReader(router, ids, searchParams.toString(), { scroll: false });
|
||||
navigateToReader(router, ids, searchParams?.toString() || '', { scroll: false });
|
||||
}
|
||||
setShouldUpdateSearchParams(false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
import { EnvProvider } from '@/context/EnvContext';
|
||||
import { CSPostHogProvider } from '@/context/PHContext';
|
||||
import { SyncProvider } from '@/context/SyncContext';
|
||||
|
||||
const Providers = ({ children }: { children: React.ReactNode }) => (
|
||||
<CSPostHogProvider>
|
||||
<EnvProvider>
|
||||
<AuthProvider>
|
||||
<SyncProvider>{children}</SyncProvider>
|
||||
</AuthProvider>
|
||||
</EnvProvider>
|
||||
</CSPostHogProvider>
|
||||
);
|
||||
|
||||
export default Providers;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { AppProps } from 'next/app';
|
||||
import Providers from '@/components/Providers';
|
||||
|
||||
import '../styles/globals.css';
|
||||
import '../styles/fonts.css';
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<Providers>
|
||||
<Component {...pageProps} />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
@@ -1,3 +1,5 @@
|
||||
import Cors from 'cors';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { PostgrestError } from '@supabase/supabase-js';
|
||||
import { supabase, createSupabaseClient } from '@/utils/supabase';
|
||||
@@ -207,3 +209,72 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to wait for a middleware to execute before continuing
|
||||
// And to throw an error when an error happens in a middleware
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: Function) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fn(req, res, (result: unknown) => {
|
||||
if (result instanceof Error) {
|
||||
return reject(result);
|
||||
}
|
||||
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const cors = Cors({
|
||||
methods: ['POST', 'GET', 'HEAD'],
|
||||
});
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (!req.url) {
|
||||
return res.status(400).json({ error: 'Invalid request URL' });
|
||||
}
|
||||
|
||||
const protocol = process.env['PROTOCOL'] || 'http';
|
||||
const host = process.env['HOST'] || 'localhost:3000';
|
||||
const url = new URL(req.url, `${protocol}://${host}`);
|
||||
|
||||
await runMiddleware(req, res, cors);
|
||||
|
||||
try {
|
||||
let response: Response;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const nextReq = new NextRequest(url.toString(), {
|
||||
headers: new Headers(req.headers as Record<string, string>),
|
||||
method: 'GET',
|
||||
});
|
||||
response = await GET(nextReq);
|
||||
} else if (req.method === 'POST') {
|
||||
const nextReq = new NextRequest(url.toString(), {
|
||||
headers: new Headers(req.headers as Record<string, string>),
|
||||
method: 'POST',
|
||||
body: JSON.stringify(req.body), // Ensure the body is a string
|
||||
});
|
||||
response = await POST(nextReq);
|
||||
} else {
|
||||
res.setHeader('Allow', ['GET', 'POST']);
|
||||
return res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
|
||||
res.status(response.status);
|
||||
|
||||
response.headers.forEach((value, key) => {
|
||||
res.setHeader(key, value);
|
||||
});
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
res.send(buffer);
|
||||
} catch (error) {
|
||||
console.error('Error processing request:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
import { EnvProvider } from '@/context/EnvContext';
|
||||
import { CSPostHogProvider } from '@/context/PHContext';
|
||||
import { SyncProvider } from '@/context/SyncContext';
|
||||
import Reader from '@/app/reader/components/Reader';
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
const ids = router.query['ids'] as string;
|
||||
return (
|
||||
<CSPostHogProvider>
|
||||
<EnvProvider>
|
||||
<AuthProvider>
|
||||
<SyncProvider>
|
||||
<Reader ids={ids} />
|
||||
</SyncProvider>
|
||||
</AuthProvider>
|
||||
</EnvProvider>
|
||||
</CSPostHogProvider>
|
||||
);
|
||||
}
|
||||
Generated
+28
@@ -80,6 +80,9 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
cors:
|
||||
specifier: ^2.8.5
|
||||
version: 2.8.5
|
||||
cssbeautify:
|
||||
specifier: ^0.3.1
|
||||
version: 0.3.1
|
||||
@@ -114,6 +117,9 @@ importers:
|
||||
'@tauri-apps/cli':
|
||||
specifier: 2.1.0
|
||||
version: 2.1.0
|
||||
'@types/cors':
|
||||
specifier: ^2.8.17
|
||||
version: 2.8.17
|
||||
'@types/cssbeautify':
|
||||
specifier: ^0.3.5
|
||||
version: 0.3.5
|
||||
@@ -794,6 +800,9 @@ packages:
|
||||
'@types/common-tags@1.8.4':
|
||||
resolution: {integrity: sha512-S+1hLDJPjWNDhcGxsxEbepzaxWqURP/o+3cP4aa2w7yBXgdcmKGQtZzP8JbyfOd0m+33nh+8+kvxYE2UJtBDkg==}
|
||||
|
||||
'@types/cors@2.8.17':
|
||||
resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
|
||||
|
||||
'@types/cssbeautify@0.3.5':
|
||||
resolution: {integrity: sha512-bkxuJdUu7Liw14EouI8IFNvJXvd5IioAZdDvlCFBXv+Sel8uPK9rcxfvOchQKqw2AtppxRvcYaWmimi0t0Yuow==}
|
||||
|
||||
@@ -1207,6 +1216,10 @@ packages:
|
||||
core-js@3.39.0:
|
||||
resolution: {integrity: sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==}
|
||||
|
||||
cors@2.8.5:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
cpx2@8.0.0:
|
||||
resolution: {integrity: sha512-RxD9jrSVNSOmfcbiPlr3XnKbUKH9K1w2HCv0skczUKhsZTueiDBecxuaSAKQkYSLQaGVA4ZQJZlTj5hVNNEvKg==}
|
||||
engines: {node: ^20.0.0 || >=22.0.0, npm: '>=10'}
|
||||
@@ -2857,6 +2870,10 @@ packages:
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
|
||||
vary@1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
watchpack@2.4.2:
|
||||
resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -3467,6 +3484,10 @@ snapshots:
|
||||
|
||||
'@types/common-tags@1.8.4': {}
|
||||
|
||||
'@types/cors@2.8.17':
|
||||
dependencies:
|
||||
'@types/node': 22.10.1
|
||||
|
||||
'@types/cssbeautify@0.3.5': {}
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
@@ -3960,6 +3981,11 @@ snapshots:
|
||||
|
||||
core-js@3.39.0: {}
|
||||
|
||||
cors@2.8.5:
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
cpx2@8.0.0:
|
||||
dependencies:
|
||||
debounce: 2.2.0
|
||||
@@ -5805,6 +5831,8 @@ snapshots:
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
optional: true
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
watchpack@2.4.2:
|
||||
dependencies:
|
||||
glob-to-regexp: 0.4.1
|
||||
|
||||
Reference in New Issue
Block a user