diff --git a/docs/mpa-school-link-handoff.md b/docs/mpa-school-link-handoff.md index dbf7b7e..c5bf815 100644 --- a/docs/mpa-school-link-handoff.md +++ b/docs/mpa-school-link-handoff.md @@ -11,11 +11,12 @@ ## 호출 시퀀스 (목표 상태) -``` +```text 앱 (ac/re 보유, cchaksa_session 없음) │ ├─ ① POST /api/session - │ body: { accessToken, refreshToken, isPortalLinked } + │ body: { accessToken, refreshToken } + │ (isPortalLinked 는 서버가 항상 false 로 강제. 요청 바디에서 받지 않음) │ └─ BFF: sealData → Set-Cookie: cchaksa_session │ ├─ ② WebView open /mpa/resync/login @@ -83,6 +84,7 @@ Auth0 의 *Native to Web SSO* 가 사용하는 패턴. 앱이 ac/re 토큰 자 iOS 14+ 의 ITP(Intelligent Tracking Prevention) 가 app-bound 로 등록되지 않은 도메인의 쿠키를 강등시킨다. 우리가 발급하는 `cchaksa_session` 의 30일 maxAge 가 사실상 무력화되어 webview 진입마다 재익스체인지가 필요해진다. `Info.plist`: + ```xml WKAppBoundDomains @@ -126,7 +128,8 @@ iOS 14+ 의 ITP(Intelligent Tracking Prevention) 가 app-bound 로 등록되지 - 이후 앱의 ac/re 토큰이 회전될 때마다 다시 호출 (선택, webview 가 살아있는 동안 401 방지) 호출 예시: -``` + +```http POST https://cchaksa.com/api/session Content-Type: application/json diff --git a/package.json b/package.json index c3e9a72..ea8eb48 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "swagger-typescript-api": "^13.1.1", "typescript": "5.6.3", "vitest": "^3.0.5", - "wrangler": "^4" + "wrangler": "^4.59.1" }, "packageManager": "yarn@4.5.0", "resolutions": { diff --git a/src/app/(funnel)/complete/page.tsx b/src/app/(funnel)/complete/page.tsx index 5997ec4..c75edc0 100644 --- a/src/app/(funnel)/complete/page.tsx +++ b/src/app/(funnel)/complete/page.tsx @@ -6,7 +6,7 @@ import { ROUTES } from '@/constants/routes'; import { useInternalRouter } from '@/hooks/useInternalRouter'; import { getSemesterInfo } from '@/lib/utils/semester'; import { FunnelHeadline } from '../components'; -import { useStudentInfo } from '../contexts'; +import { useFunnelContext } from '../contexts'; import styles from './page.module.scss'; function InfoRow({ label, value }: { label: string; value: string }) { @@ -19,7 +19,7 @@ function InfoRow({ label, value }: { label: string; value: string }) { } export default function Complete() { - const { studentInfo } = useStudentInfo(); + const { studentInfo } = useFunnelContext(); const router = useInternalRouter(); const handleNext = () => { router.push(`${ROUTES.FUNNEL.TARGET_SCORE}`); diff --git a/src/app/(funnel)/contexts/FunnelContext.tsx b/src/app/(funnel)/contexts/FunnelContext.tsx index ba3288e..363a4bf 100644 --- a/src/app/(funnel)/contexts/FunnelContext.tsx +++ b/src/app/(funnel)/contexts/FunnelContext.tsx @@ -24,14 +24,6 @@ export function FunnelProvider({ children }: { children: ReactNode }) { ); } -export function useStudentInfo() { - const context = useContext(FunnelContext); - if (!context) { - throw new Error('useStudentInfo must be used within a FunnelProvider'); - } - return context; -} - export function useFunnelContext() { const context = useContext(FunnelContext); if (!context) { diff --git a/src/app/(funnel)/contexts/index.ts b/src/app/(funnel)/contexts/index.ts index ae0b9ad..f8f008f 100644 --- a/src/app/(funnel)/contexts/index.ts +++ b/src/app/(funnel)/contexts/index.ts @@ -1 +1 @@ -export { FunnelProvider, useStudentInfo, useFunnelContext } from './FunnelContext'; +export { FunnelProvider, useFunnelContext } from './FunnelContext'; diff --git a/src/app/(funnel)/portal-login/components/PortalLoginForm/PortalLoginForm.tsx b/src/app/(funnel)/portal-login/components/PortalLoginForm/PortalLoginForm.tsx index d40f45d..b2cc9ab 100644 --- a/src/app/(funnel)/portal-login/components/PortalLoginForm/PortalLoginForm.tsx +++ b/src/app/(funnel)/portal-login/components/PortalLoginForm/PortalLoginForm.tsx @@ -34,13 +34,16 @@ export function PortalLoginForm({ onSuccess, onError }: PortalLoginFormProps) { setJobId(newJobId); onSuccess(); } else { - setErrorMessage('연동 요청에 실패했습니다. 다시 시도해주세요.'); + const fallbackError = new Error('연동 요청에 실패했습니다. 다시 시도해주세요.'); + setErrorMessage(fallbackError.message); + onError?.(fallbackError); } - } catch (err: any) { + } catch (err: unknown) { console.error('[PortalLoginForm] 연동 요청 에러', err); - const message = err?.message ?? '알 수 없는 오류가 발생했어요\n잠시후 다시 시도해주세요'; + const errorInstance = err instanceof Error ? err : new Error(String(err)); + const message = errorInstance.message || '알 수 없는 오류가 발생했어요\n잠시후 다시 시도해주세요'; setErrorMessage(message); - onError?.(err as Error); + onError?.(errorInstance); } }; diff --git a/src/app/(funnel)/scraping/page.tsx b/src/app/(funnel)/scraping/page.tsx index 4d569d3..8ecfb25 100644 --- a/src/app/(funnel)/scraping/page.tsx +++ b/src/app/(funnel)/scraping/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { setUser } from '@sentry/nextjs'; import { ROUTES } from '@/constants/routes'; import { useInternalRouter } from '@/hooks/useInternalRouter'; @@ -19,9 +19,14 @@ export default function ScrapingPage() { const jobDetail = jobStatusData?.data; const { data: summaryData } = usePortalLinkSummary(jobId, jobStatus); + const handledRef = useRef(false); useEffect(() => { + if (handledRef.current) { + return; + } if (summaryData?.data?.studentInfo) { + handledRef.current = true; setStudentInfo(summaryData.data.studentInfo); if (jobId) { setUser({ id: jobId }); diff --git a/src/app/(mpa)/mpa/home/page.tsx b/src/app/(mpa)/mpa/home/page.tsx index f7ece83..68b52b4 100644 --- a/src/app/(mpa)/mpa/home/page.tsx +++ b/src/app/(mpa)/mpa/home/page.tsx @@ -9,14 +9,24 @@ import { SyncUpdateButton, } from '@/features/dashboard/components'; import { useRefreshProfileOnVisible } from '@/features/dashboard/hooks/useRefreshProfileOnVisible'; +import { useInternalRouter } from '@/hooks/useInternalRouter'; import { navigateNative } from '@/lib/webview'; import AsyncBoundary from '@/shared/components/AsyncBoundary'; const MpaHome = () => { useRefreshProfileOnVisible(); + const router = useInternalRouter(); - const goGraduation = () => navigateNative(ROUTES.MPA.GRADUATION_PROGRESS); - const goResync = () => navigateNative(ROUTES.MPA.RESYNC_LOGIN); + const goGraduation = () => { + if (!navigateNative(ROUTES.MPA.GRADUATION_PROGRESS)) { + router.push(ROUTES.MPA.GRADUATION_PROGRESS); + } + }; + const goResync = () => { + if (!navigateNative(ROUTES.MPA.RESYNC_LOGIN)) { + router.push(ROUTES.MPA.RESYNC_LOGIN); + } + }; return ( <> diff --git a/src/app/(mpa)/mpa/resync/scraping/page.tsx b/src/app/(mpa)/mpa/resync/scraping/page.tsx index 970ad9d..21b6a57 100644 --- a/src/app/(mpa)/mpa/resync/scraping/page.tsx +++ b/src/app/(mpa)/mpa/resync/scraping/page.tsx @@ -22,12 +22,13 @@ export default function MpaScrapingPage() { const [errorMessage, setErrorMessage] = useState(null); const router = useInternalRouter(); - const [jobId] = useState(() => { - if (typeof window !== 'undefined') { - return sessionStorage.getItem(RESYNC_JOB_ID_KEY); - } - return null; - }); + const [jobId, setJobId] = useState(null); + const [isJobIdResolved, setIsJobIdResolved] = useState(false); + + useEffect(() => { + setJobId(sessionStorage.getItem(RESYNC_JOB_ID_KEY)); + setIsJobIdResolved(true); + }, []); const { data: jobStatusData, isTimedOut } = usePortalLinkJobPolling(jobId); const jobStatus = jobStatusData?.data?.status; @@ -64,7 +65,7 @@ export default function MpaScrapingPage() { router.push(ROUTES.MPA.RESYNC_LOGIN); }; - if (!jobId) { + if (isJobIdResolved && !jobId) { return (
diff --git a/src/app/api/session/refresh/route.ts b/src/app/api/session/refresh/route.ts index 02d0cca..751e0ec 100644 --- a/src/app/api/session/refresh/route.ts +++ b/src/app/api/session/refresh/route.ts @@ -5,6 +5,8 @@ import type { RefreshTokenApiResponse } from '@/shared/api/data-contracts'; export const dynamic = 'force-dynamic'; +const REFRESH_TIMEOUT_MS = 10_000; + export async function POST() { const session = await getSession(); @@ -12,11 +14,15 @@ export async function POST() { return NextResponse.json({ error: 'NO_REFRESH_TOKEN' }, { status: 401 }); } + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), REFRESH_TIMEOUT_MS); + try { const response = await fetch(`${getApiBaseUrl()}/api/auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken: session.refreshToken }), + signal: controller.signal, }); if (!response.ok) { @@ -37,8 +43,14 @@ export async function POST() { return NextResponse.json({ accessToken: payload.data.accessToken }); } catch (error) { - console.error('[session/refresh] unexpected error', error); session.destroy(); + if (error instanceof DOMException && error.name === 'AbortError') { + console.error('[session/refresh] timed out after', REFRESH_TIMEOUT_MS, 'ms'); + return NextResponse.json({ error: 'REFRESH_TIMEOUT' }, { status: 504 }); + } + console.error('[session/refresh] unexpected error', error); return NextResponse.json({ error: 'REFRESH_ERROR' }, { status: 500 }); + } finally { + clearTimeout(timeoutId); } } diff --git a/src/app/auth/callback/route.ts b/src/app/auth/callback/route.ts index 9e649fd..4173481 100644 --- a/src/app/auth/callback/route.ts +++ b/src/app/auth/callback/route.ts @@ -27,7 +27,7 @@ export async function GET(request: Request) { const idToken = await getKakaoToken(code, redirectUri); const { accessToken, refreshToken, isPortalLinked } = await authService.login(idToken, nonce, 'KAKAO'); - if (isPortalLinked === undefined || !refreshToken) { + if (typeof isPortalLinked !== 'boolean' || !refreshToken) { throw new AuthError('User is missing or malformed.'); } diff --git a/src/app/auth/success/page.tsx b/src/app/auth/success/page.tsx index 5682fd2..ef57a91 100644 --- a/src/app/auth/success/page.tsx +++ b/src/app/auth/success/page.tsx @@ -3,6 +3,7 @@ import { useEffect, Suspense } from 'react'; import { useSearchParams } from 'next/navigation'; import { type RoutePath, useInternalRouter } from '@/hooks/useInternalRouter'; +import styles from './success.module.scss'; const SuccessContent = () => { const router = useInternalRouter(); @@ -28,12 +29,12 @@ const SuccessContent = () => { hydrate(); }, [router, searchParams]); - return
; + return
; }; const SuccessPage = () => { return ( -
}> + }> ); diff --git a/src/app/auth/success/success.module.scss b/src/app/auth/success/success.module.scss new file mode 100644 index 0000000..e34ab42 --- /dev/null +++ b/src/app/auth/success/success.module.scss @@ -0,0 +1,6 @@ +.container { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; +} diff --git a/src/app/resync/scraping/page.tsx b/src/app/resync/scraping/page.tsx index 1d6566e..b93fe83 100644 --- a/src/app/resync/scraping/page.tsx +++ b/src/app/resync/scraping/page.tsx @@ -11,12 +11,13 @@ export default function ScrapingPage() { const [errorMessage, setErrorMessage] = useState(null); const router = useInternalRouter(); - const [jobId] = useState(() => { - if (typeof window !== 'undefined') { - return sessionStorage.getItem(RESYNC_JOB_ID_KEY); - } - return null; - }); + const [jobId, setJobId] = useState(null); + const [isJobIdResolved, setIsJobIdResolved] = useState(false); + + useEffect(() => { + setJobId(sessionStorage.getItem(RESYNC_JOB_ID_KEY)); + setIsJobIdResolved(true); + }, []); const { data: jobStatusData, isTimedOut } = usePortalLinkJobPolling(jobId); const jobStatus = jobStatusData?.data?.status; @@ -48,7 +49,7 @@ export default function ScrapingPage() { throw new Error(errorMessage); } - if (!jobId) { + if (isJobIdResolved && !jobId) { throw new Error('연동 정보를 찾을 수 없습니다. 다시 로그인해주세요.'); } diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx index 5d04119..c552f1b 100644 --- a/src/app/terms/page.tsx +++ b/src/app/terms/page.tsx @@ -1,4 +1,4 @@ -import styles from '../privacy-policy/PrivacyPolicy.module.scss'; +import styles from '@/app/privacy-policy/PrivacyPolicy.module.scss'; export default function TermsPage() { return ( diff --git a/src/features/dashboard/components/SyncUpdateButton/SyncUpdateButton.tsx b/src/features/dashboard/components/SyncUpdateButton/SyncUpdateButton.tsx index a208c20..1078ebe 100644 --- a/src/features/dashboard/components/SyncUpdateButton/SyncUpdateButton.tsx +++ b/src/features/dashboard/components/SyncUpdateButton/SyncUpdateButton.tsx @@ -31,7 +31,7 @@ const SyncUpdateButton = ({ onNavigate }: SyncUpdateButtonProps = {}) => { return ( ); diff --git a/src/features/portal-link/hooks/usePortalLinkJobPolling.ts b/src/features/portal-link/hooks/usePortalLinkJobPolling.ts index fad59b7..6906b58 100644 --- a/src/features/portal-link/hooks/usePortalLinkJobPolling.ts +++ b/src/features/portal-link/hooks/usePortalLinkJobPolling.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { ENV } from '@/config/environment'; import { getJobStatus } from '../services/portalLinkService'; @@ -6,12 +6,19 @@ import { getJobStatus } from '../services/portalLinkService'; const POLLING_INTERVAL_MS = 2000; export function usePortalLinkJobPolling(jobId: string | null) { - const startedAtRef = useRef(null); const [isTimedOut, setIsTimedOut] = useState(false); useEffect(() => { setIsTimedOut(false); - startedAtRef.current = jobId ? Date.now() : null; + if (!jobId) { + return; + } + + const timeoutId = setTimeout(() => { + setIsTimedOut(true); + }, ENV.PORTAL_LINK_TIMEOUT_MS); + + return () => clearTimeout(timeoutId); }, [jobId]); const query = useQuery({ @@ -23,13 +30,18 @@ export function usePortalLinkJobPolling(jobId: string | null) { if (status === 'succeeded' || status === 'failed') { return false; } - if (startedAtRef.current && Date.now() - startedAtRef.current >= ENV.PORTAL_LINK_TIMEOUT_MS) { - setIsTimedOut(true); + if (isTimedOut) { return false; } return POLLING_INTERVAL_MS; }, }); - return { data: query.data, isTimedOut }; + return { + data: query.data, + isTimedOut, + isLoading: query.isLoading, + isError: query.isError, + error: query.error, + }; } diff --git a/src/features/portal-link/hooks/usePortalLinkSummary.ts b/src/features/portal-link/hooks/usePortalLinkSummary.ts index ae27ce3..35d9156 100644 --- a/src/features/portal-link/hooks/usePortalLinkSummary.ts +++ b/src/features/portal-link/hooks/usePortalLinkSummary.ts @@ -4,7 +4,12 @@ import { getJobSummary } from '../services/portalLinkService'; export function usePortalLinkSummary(jobId: string | null, jobStatus: string | undefined) { return useQuery({ queryKey: ['portal-link-summary', jobId], - queryFn: () => getJobSummary(jobId!), + queryFn: () => { + if (!jobId) { + throw new Error('jobId is required to fetch portal link summary'); + } + return getJobSummary(jobId); + }, enabled: Boolean(jobId) && jobStatus === 'succeeded', }); } diff --git a/src/lib/webview/bridge.ts b/src/lib/webview/bridge.ts index 940fa90..3b2339b 100644 --- a/src/lib/webview/bridge.ts +++ b/src/lib/webview/bridge.ts @@ -36,10 +36,10 @@ export const isInWebView = (): boolean => { return resolveBridgePostMessage(w) !== null; }; -export const postBridgeMessage = (message: string): void => { +export const postBridgeMessage = (message: string): boolean => { const w = getWebViewWindow(); if (!w) { - return; + return false; } const post = resolveBridgePostMessage(w); @@ -48,20 +48,22 @@ export const postBridgeMessage = (message: string): void => { // eslint-disable-next-line no-console console.warn('[bridge] no native bridge detected; message dropped:', message); } - return; + return false; } try { post(message); + return true; } catch (err) { captureException(err, { extra: { bridgeMessage: message } }); if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line no-console console.warn('[bridge] postMessage threw; swallowed to protect UI:', message, err); } + return false; } }; -export const navigateNative = (url: string): void => { - postBridgeMessage(`navigate:${url}`); +export const navigateNative = (url: string): boolean => { + return postBridgeMessage(`navigate:${url}`); }; diff --git a/src/shared/api/http-client.ts b/src/shared/api/http-client.ts index 6557935..fa0db47 100644 --- a/src/shared/api/http-client.ts +++ b/src/shared/api/http-client.ts @@ -62,7 +62,7 @@ export enum ContentType { } export class HttpClient { - public baseUrl: string = "https://dev.api.cchaksa.com"; + public baseUrl: string = ""; private securityData: SecurityDataType | null = null; private securityWorker?: ApiConfig["securityWorker"]; private abortControllers = new Map(); diff --git a/wrangler.jsonc b/wrangler.jsonc index dc317c7..a01b913 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -2,7 +2,7 @@ "$schema": "node_modules/wrangler/config-schema.json", "name": "cchaksa", "main": ".open-next/worker.js", - "compatibility_date": "2024-09-23", + "compatibility_date": "2026-05-10", "compatibility_flags": ["nodejs_compat"], "assets": { "directory": ".open-next/assets", diff --git a/yarn.lock b/yarn.lock index e1a3cab..2c0ea78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2292,57 +2292,57 @@ __metadata: languageName: node linkType: hard -"@cloudflare/kv-asset-handler@npm:0.4.2": - version: 0.4.2 - resolution: "@cloudflare/kv-asset-handler@npm:0.4.2" - checksum: 10c0/c8877851ce069b04d32d50a640c9c0faaab054970204f64a4111bac3dd85f177c001a0b57d32f7e65269e3896268b8f94605f31e4fa06253a6a5779587a63d17 +"@cloudflare/kv-asset-handler@npm:0.5.0": + version: 0.5.0 + resolution: "@cloudflare/kv-asset-handler@npm:0.5.0" + checksum: 10c0/46863066ff1628aa76946db6ecb5088ae64ba15d7bda7e4935bd1ab1dcf080cef9cd0920205c9508721153365ef1305984e3584e7ad2dd1c9454df984f441edb languageName: node linkType: hard -"@cloudflare/unenv-preset@npm:2.16.0": - version: 2.16.0 - resolution: "@cloudflare/unenv-preset@npm:2.16.0" +"@cloudflare/unenv-preset@npm:2.16.1": + version: 2.16.1 + resolution: "@cloudflare/unenv-preset@npm:2.16.1" peerDependencies: unenv: 2.0.0-rc.24 - workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 + workerd: ">1.20260305.0 <2.0.0-0" peerDependenciesMeta: workerd: optional: true - checksum: 10c0/ddb2939cd05b5a0cb17d44b98a11b5c8e24dc6fa31171e98d8f1d7ffbb9cd35fde2ad2873715dc90db63183d6768ea43256ce3e4eb8c359542a2051f2d8bd919 + checksum: 10c0/08c9bd9ef488a14fd5330eb2c0829fc221f97f12295f8263dc8e3a6816ebd1a629e7255a3619b8bb688f1f58deafe28042c4e90ea84c926438c768f235f82ad9 languageName: node linkType: hard -"@cloudflare/workerd-darwin-64@npm:1.20260409.1": - version: 1.20260409.1 - resolution: "@cloudflare/workerd-darwin-64@npm:1.20260409.1" +"@cloudflare/workerd-darwin-64@npm:1.20260507.1": + version: 1.20260507.1 + resolution: "@cloudflare/workerd-darwin-64@npm:1.20260507.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@cloudflare/workerd-darwin-arm64@npm:1.20260409.1": - version: 1.20260409.1 - resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20260409.1" +"@cloudflare/workerd-darwin-arm64@npm:1.20260507.1": + version: 1.20260507.1 + resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20260507.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@cloudflare/workerd-linux-64@npm:1.20260409.1": - version: 1.20260409.1 - resolution: "@cloudflare/workerd-linux-64@npm:1.20260409.1" +"@cloudflare/workerd-linux-64@npm:1.20260507.1": + version: 1.20260507.1 + resolution: "@cloudflare/workerd-linux-64@npm:1.20260507.1" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@cloudflare/workerd-linux-arm64@npm:1.20260409.1": - version: 1.20260409.1 - resolution: "@cloudflare/workerd-linux-arm64@npm:1.20260409.1" +"@cloudflare/workerd-linux-arm64@npm:1.20260507.1": + version: 1.20260507.1 + resolution: "@cloudflare/workerd-linux-arm64@npm:1.20260507.1" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@cloudflare/workerd-windows-64@npm:1.20260409.1": - version: 1.20260409.1 - resolution: "@cloudflare/workerd-windows-64@npm:1.20260409.1" +"@cloudflare/workerd-windows-64@npm:1.20260507.1": + version: 1.20260507.1 + resolution: "@cloudflare/workerd-windows-64@npm:1.20260507.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -3737,9 +3737,9 @@ __metadata: languageName: node linkType: hard -"@opennextjs/aws@npm:3.10.1": - version: 3.10.1 - resolution: "@opennextjs/aws@npm:3.10.1" +"@opennextjs/aws@npm:4.0.1": + version: 4.0.1 + resolution: "@opennextjs/aws@npm:4.0.1" dependencies: "@ast-grep/napi": "npm:^0.40.5" "@aws-sdk/client-cloudfront": "npm:3.984.0" @@ -3759,20 +3759,21 @@ __metadata: urlpattern-polyfill: "npm:^10.1.0" yaml: "npm:^2.8.1" peerDependencies: - next: ">=15.5.15 || >=16.2.3" + next: ">=15.5.16 <16 || >=16.2.5" bin: open-next: dist/index.js - checksum: 10c0/0b8fab91c7bb19d7c08e93a21b278a06caccba4a3c4ce43fcda49379820d6f9d1bcf15f68278cf5a20d9a8105efa43011be11968a92b14cd1e1d605617d319c1 + checksum: 10c0/c55240c90de294015a2c6c365f0873043129a6dab2263b46eab844c40a07c698245d078d9338ad8f3cdc95dd33308c0b53dae69fa6cc79c9b69fd157a16f0405 languageName: node linkType: hard "@opennextjs/cloudflare@npm:^1": - version: 1.19.1 - resolution: "@opennextjs/cloudflare@npm:1.19.1" + version: 1.19.8 + resolution: "@opennextjs/cloudflare@npm:1.19.8" dependencies: "@ast-grep/napi": "npm:^0.40.5" "@dotenvx/dotenvx": "npm:1.31.0" - "@opennextjs/aws": "npm:3.10.1" + "@opennextjs/aws": "npm:4.0.1" + ci-info: "npm:^4.2.0" cloudflare: "npm:^4.4.1" comment-json: "npm:^4.5.1" enquirer: "npm:^2.4.1" @@ -3780,11 +3781,11 @@ __metadata: ts-tqdm: "npm:^0.8.6" yargs: "npm:^18.0.0" peerDependencies: - next: ">=15.5.15 || >=16.2.3" - wrangler: ^4.65.0 + next: ">=15.5.16 <16 || >=16.2.5" + wrangler: ^4.86.0 bin: opennextjs-cloudflare: dist/cli/index.js - checksum: 10c0/b5b318ba3eed46354380c61fcbdc383ee907a84576ffbf9710cd448ae73c1ae156a870035d74eedb930709535d7b2f34fbc507bea9f6cacd2fb29cc9d9dd00fb + checksum: 10c0/a593f91d0464e94e84882c5c2f0578acac0b42997af3788e583a143fd40d51f33915bac27515caf3b77852080c751cf4693c5a4d25ae3078087aea164f18a6a9 languageName: node linkType: hard @@ -7236,11 +7237,18 @@ __metadata: typescript: "npm:5.6.3" uuid: "npm:^11.0.3" vitest: "npm:^3.0.5" - wrangler: "npm:^4" + wrangler: "npm:^4.59.1" zod: "npm:^3.24.4" languageName: unknown linkType: soft +"ci-info@npm:^4.2.0": + version: 4.4.0 + resolution: "ci-info@npm:4.4.0" + checksum: 10c0/44156201545b8dde01aa8a09ee2fe9fc7a73b1bef9adbd4606c9f61c8caeeb73fb7a575c88b0443f7b4edb5ee45debaa59ed54ba5f99698339393ca01349eb3a + languageName: node + linkType: hard + "citty@npm:^0.1.6": version: 0.1.6 resolution: "citty@npm:0.1.6" @@ -10464,19 +10472,19 @@ __metadata: languageName: node linkType: hard -"miniflare@npm:4.20260409.0": - version: 4.20260409.0 - resolution: "miniflare@npm:4.20260409.0" +"miniflare@npm:4.20260507.1": + version: 4.20260507.1 + resolution: "miniflare@npm:4.20260507.1" dependencies: "@cspotcode/source-map-support": "npm:0.8.1" sharp: "npm:^0.34.5" - undici: "npm:7.24.4" - workerd: "npm:1.20260409.1" + undici: "npm:7.24.8" + workerd: "npm:1.20260507.1" ws: "npm:8.18.0" youch: "npm:4.1.0-beta.10" bin: miniflare: bootstrap.js - checksum: 10c0/d53db1b87c3058637dd87e7d28ef58e6b8a0309c70c832cad274af1568c3624734408b45ec8a819282080f9b9bd65521c47b1174bdb85a766896562b0d208f16 + checksum: 10c0/b74b74a5ddd6b5bb2fda190029cd07fcf1bace3085f34a60af7f4408e0d31736a042008fd7059c9831e49bf3249b02b28d60683b3ce39c921c00fe76ccc59056 languageName: node linkType: hard @@ -13417,10 +13425,10 @@ __metadata: languageName: node linkType: hard -"undici@npm:7.24.4": - version: 7.24.4 - resolution: "undici@npm:7.24.4" - checksum: 10c0/cb302e81fadb7f0b7946ab77595715c0961b46a025ccecae79ba599432d0bc8d1e3da4dfe7ff66bc74f115c1b8ff0f099bc4e9bf313db4562da23995872c6d17 +"undici@npm:7.24.8": + version: 7.24.8 + resolution: "undici@npm:7.24.8" + checksum: 10c0/5b3cb18b1c6ccff564c37390547b2f137c666ada5083af6d5b5671dc12f73530ae2872e16fab1e3948b46013fed7c81ed10bce23f7dbf21b73794244b70b7eea languageName: node linkType: hard @@ -13922,15 +13930,15 @@ __metadata: languageName: node linkType: hard -"workerd@npm:1.20260409.1": - version: 1.20260409.1 - resolution: "workerd@npm:1.20260409.1" +"workerd@npm:1.20260507.1": + version: 1.20260507.1 + resolution: "workerd@npm:1.20260507.1" dependencies: - "@cloudflare/workerd-darwin-64": "npm:1.20260409.1" - "@cloudflare/workerd-darwin-arm64": "npm:1.20260409.1" - "@cloudflare/workerd-linux-64": "npm:1.20260409.1" - "@cloudflare/workerd-linux-arm64": "npm:1.20260409.1" - "@cloudflare/workerd-windows-64": "npm:1.20260409.1" + "@cloudflare/workerd-darwin-64": "npm:1.20260507.1" + "@cloudflare/workerd-darwin-arm64": "npm:1.20260507.1" + "@cloudflare/workerd-linux-64": "npm:1.20260507.1" + "@cloudflare/workerd-linux-arm64": "npm:1.20260507.1" + "@cloudflare/workerd-windows-64": "npm:1.20260507.1" dependenciesMeta: "@cloudflare/workerd-darwin-64": optional: true @@ -13944,25 +13952,25 @@ __metadata: optional: true bin: workerd: bin/workerd - checksum: 10c0/8fd82ddbd71a2f20ff09d125e46f1fc1999c765aa24698975c3b5b96960d593058506a82881b14e1921b7c031eec85ca559529b15354d92fe589557d3a91e8a3 + checksum: 10c0/5b17cb73e4c4245abec64e5400d4101aaccbf50f7ae378f843ba0d56c214a891126291e35efa4ee99420f65571f16fe78b47276e0c72ab6871526d13d01bb382 languageName: node linkType: hard -"wrangler@npm:^4": - version: 4.81.1 - resolution: "wrangler@npm:4.81.1" +"wrangler@npm:^4.59.1": + version: 4.90.0 + resolution: "wrangler@npm:4.90.0" dependencies: - "@cloudflare/kv-asset-handler": "npm:0.4.2" - "@cloudflare/unenv-preset": "npm:2.16.0" + "@cloudflare/kv-asset-handler": "npm:0.5.0" + "@cloudflare/unenv-preset": "npm:2.16.1" blake3-wasm: "npm:2.1.5" esbuild: "npm:0.27.3" fsevents: "npm:~2.3.2" - miniflare: "npm:4.20260409.0" + miniflare: "npm:4.20260507.1" path-to-regexp: "npm:6.3.0" unenv: "npm:2.0.0-rc.24" - workerd: "npm:1.20260409.1" + workerd: "npm:1.20260507.1" peerDependencies: - "@cloudflare/workers-types": ^4.20260409.1 + "@cloudflare/workers-types": ^4.20260507.1 dependenciesMeta: fsevents: optional: true @@ -13972,7 +13980,7 @@ __metadata: bin: wrangler: bin/wrangler.js wrangler2: bin/wrangler.js - checksum: 10c0/c738e72b9e005d8b9c2b712231da220f1386c7401bc10efea2b73e3a35809104ebb13fb8fb548979ec26e2da3ac50e161219e4d31bd7e9c25d0b4bc5e0941716 + checksum: 10c0/c8bbab061fc51cdbd500b34aeb7a3a938984085edf7c8da0f0924ef4b7454c3e05937978c8f2a6bd426506f06dd7b09869be5735915d645f5320263f854766e3 languageName: node linkType: hard