Emit clean stack traces from iframe (#63)

This commit is contained in:
Will Chen
2025-05-01 21:50:06 -07:00
committed by GitHub
parent 1bbfedc668
commit 79b7f865fc
5 changed files with 519 additions and 342 deletions

185
scaffold/dyad-shim.js Normal file
View File

@@ -0,0 +1,185 @@
(function () {
const isInsideIframe = window.parent !== window;
if (!isInsideIframe) return;
let previousUrl = window.location.href;
const PARENT_TARGET_ORIGIN = "*";
// --- History API Overrides ---
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
const handleStateChangeAndNotify = (originalMethod, state, title, url) => {
const oldUrlForMessage = previousUrl;
let newUrl;
try {
newUrl = url
? new URL(url, window.location.href).href
: window.location.href;
} catch (e) {
newUrl = window.location.href;
}
const navigationType =
originalMethod === originalPushState ? "pushState" : "replaceState";
try {
// Pass the original state directly
originalMethod.call(history, state, title, url);
previousUrl = window.location.href;
window.parent.postMessage(
{
type: navigationType,
payload: { oldUrl: oldUrlForMessage, newUrl: newUrl },
},
PARENT_TARGET_ORIGIN
);
} catch (e) {
console.error(
`[vite-dev-plugin] Error calling original ${navigationType}: `,
e
);
window.parent.postMessage(
{
type: "navigation-error",
payload: {
operation: navigationType,
message: e.message,
error: e.toString(),
stateAttempted: state,
urlAttempted: url,
},
},
PARENT_TARGET_ORIGIN
);
}
};
history.pushState = function (state, title, url) {
handleStateChangeAndNotify(originalPushState, state, title, url);
};
history.replaceState = function (state, title, url) {
handleStateChangeAndNotify(originalReplaceState, state, title, url);
};
// --- Listener for Back/Forward Navigation (popstate event) ---
window.addEventListener("popstate", (event) => {
const currentUrl = window.location.href;
previousUrl = currentUrl;
});
// --- Listener for Commands from Parent ---
window.addEventListener("message", (event) => {
if (
event.source !== window.parent ||
!event.data ||
typeof event.data !== "object"
)
return;
if (event.data.type === "navigate") {
const direction = event.data.payload?.direction;
if (direction === "forward") history.forward();
else if (direction === "backward") history.back();
}
});
// --- Sourcemapped Error Handling ---
function sendSourcemappedErrorToParent(error, sourceType) {
if (typeof window.StackTrace === "undefined") {
console.error("[vite-dev-plugin] StackTrace object not found.");
// Send simplified raw data if StackTrace isn't available
window.parent.postMessage(
{
type: sourceType,
payload: {
message: error?.message || String(error),
stack:
error?.stack || "<no stack available - StackTrace.js missing>",
},
},
PARENT_TARGET_ORIGIN
);
return;
}
window.StackTrace.fromError(error)
.then((stackFrames) => {
const sourcemappedStack = stackFrames
.map((sf) => sf.toString())
.join("\n");
const payload = {
message: error?.message || String(error),
stack: sourcemappedStack,
};
window.parent.postMessage(
{
type: "iframe-sourcemapped-error",
payload: { ...payload, originalSourceType: sourceType },
},
PARENT_TARGET_ORIGIN
);
})
.catch((mappingError) => {
console.error(
"[vite-dev-plugin] Error during stacktrace sourcemapping:",
mappingError
);
const payload = {
message: error?.message || String(error),
// Provide the raw stack or an indication of mapping failure
stack: error?.stack
? `Sourcemapping failed: ${mappingError.message}\n--- Raw Stack ---\n${error.stack}`
: `Sourcemapping failed: ${mappingError.message}\n<no raw stack available>`,
};
window.parent.postMessage(
{
type: "iframe-sourcemapped-error",
payload: { ...payload, originalSourceType: sourceType },
},
PARENT_TARGET_ORIGIN
);
});
}
window.addEventListener("error", (event) => {
let error = event.error;
if (!(error instanceof Error)) {
window.parent.postMessage(
{
type: "window-error",
payload: {
message: error.toString(),
stack: "<no stack available - an improper error was thrown>",
},
},
PARENT_TARGET_ORIGIN
);
return;
}
sendSourcemappedErrorToParent(error, "window-error");
});
window.addEventListener("unhandledrejection", (event) => {
let error = event.reason;
if (!(error instanceof Error)) {
window.parent.postMessage(
{
type: "unhandled-rejection",
payload: {
message: event.reason.toString(),
stack:
"<no stack available - an improper error was thrown (promise)>",
},
},
PARENT_TARGET_ORIGIN
);
return;
}
sendSourcemappedErrorToParent(error, "unhandled-rejection");
});
})();

View File

@@ -56,6 +56,7 @@
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"recharts": "^2.12.7", "recharts": "^2.12.7",
"sonner": "^1.5.0", "sonner": "^1.5.0",
"stacktrace-js": "^2.0.2",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.3", "vaul": "^0.9.3",
@@ -67,7 +68,7 @@
"@types/node": "^22.5.5", "@types/node": "^22.5.5",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.5.0", "@vitejs/plugin-react-swc": "^3.9.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.9.0", "eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0",
@@ -77,6 +78,6 @@
"tailwindcss": "^3.4.11", "tailwindcss": "^3.4.11",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"typescript-eslint": "^8.0.1", "typescript-eslint": "^8.0.1",
"vite": "^5.4.1" "vite": "^6.3.4"
} }
} }

370
scaffold/pnpm-lock.yaml generated
View File

@@ -143,6 +143,9 @@ importers:
sonner: sonner:
specifier: ^1.5.0 specifier: ^1.5.0
version: 1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
stacktrace-js:
specifier: ^2.0.2
version: 2.0.2
tailwind-merge: tailwind-merge:
specifier: ^2.5.2 specifier: ^2.5.2
version: 2.6.0 version: 2.6.0
@@ -172,8 +175,8 @@ importers:
specifier: ^18.3.0 specifier: ^18.3.0
version: 18.3.6(@types/react@18.3.20) version: 18.3.6(@types/react@18.3.20)
'@vitejs/plugin-react-swc': '@vitejs/plugin-react-swc':
specifier: ^3.5.0 specifier: ^3.9.0
version: 3.9.0(vite@5.4.18(@types/node@22.14.1)) version: 3.9.0(vite@6.3.4(@types/node@22.14.1)(jiti@1.21.7)(yaml@2.7.1))
autoprefixer: autoprefixer:
specifier: ^10.4.20 specifier: ^10.4.20
version: 10.4.21(postcss@8.5.3) version: 10.4.21(postcss@8.5.3)
@@ -202,8 +205,8 @@ importers:
specifier: ^8.0.1 specifier: ^8.0.1
version: 8.30.1(eslint@9.24.0(jiti@1.21.7))(typescript@5.8.3) version: 8.30.1(eslint@9.24.0(jiti@1.21.7))(typescript@5.8.3)
vite: vite:
specifier: ^5.4.1 specifier: ^6.3.4
version: 5.4.18(@types/node@22.14.1) version: 6.3.4(@types/node@22.14.1)(jiti@1.21.7)(yaml@2.7.1)
packages: packages:
@@ -215,141 +218,153 @@ packages:
resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.25.3':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} resolution: {integrity: sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [ppc64] cpu: [ppc64]
os: [aix] os: [aix]
'@esbuild/android-arm64@0.21.5': '@esbuild/android-arm64@0.25.3':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} resolution: {integrity: sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@esbuild/android-arm@0.21.5': '@esbuild/android-arm@0.25.3':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} resolution: {integrity: sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@esbuild/android-x64@0.21.5': '@esbuild/android-x64@0.25.3':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} resolution: {integrity: sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [android] os: [android]
'@esbuild/darwin-arm64@0.21.5': '@esbuild/darwin-arm64@0.25.3':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} resolution: {integrity: sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@esbuild/darwin-x64@0.21.5': '@esbuild/darwin-x64@0.25.3':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} resolution: {integrity: sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@esbuild/freebsd-arm64@0.21.5': '@esbuild/freebsd-arm64@0.25.3':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} resolution: {integrity: sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@esbuild/freebsd-x64@0.21.5': '@esbuild/freebsd-x64@0.25.3':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} resolution: {integrity: sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@esbuild/linux-arm64@0.21.5': '@esbuild/linux-arm64@0.25.3':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} resolution: {integrity: sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@esbuild/linux-arm@0.21.5': '@esbuild/linux-arm@0.25.3':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} resolution: {integrity: sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@esbuild/linux-ia32@0.21.5': '@esbuild/linux-ia32@0.25.3':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} resolution: {integrity: sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [ia32] cpu: [ia32]
os: [linux] os: [linux]
'@esbuild/linux-loong64@0.21.5': '@esbuild/linux-loong64@0.25.3':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} resolution: {integrity: sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
'@esbuild/linux-mips64el@0.21.5': '@esbuild/linux-mips64el@0.25.3':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} resolution: {integrity: sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [mips64el] cpu: [mips64el]
os: [linux] os: [linux]
'@esbuild/linux-ppc64@0.21.5': '@esbuild/linux-ppc64@0.25.3':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} resolution: {integrity: sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
'@esbuild/linux-riscv64@0.21.5': '@esbuild/linux-riscv64@0.25.3':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} resolution: {integrity: sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
'@esbuild/linux-s390x@0.21.5': '@esbuild/linux-s390x@0.25.3':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} resolution: {integrity: sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
'@esbuild/linux-x64@0.21.5': '@esbuild/linux-x64@0.25.3':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} resolution: {integrity: sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@esbuild/netbsd-x64@0.21.5': '@esbuild/netbsd-arm64@0.25.3':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} resolution: {integrity: sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.25.3':
resolution: {integrity: sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==}
engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [netbsd] os: [netbsd]
'@esbuild/openbsd-x64@0.21.5': '@esbuild/openbsd-arm64@0.25.3':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} resolution: {integrity: sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.25.3':
resolution: {integrity: sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==}
engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [openbsd] os: [openbsd]
'@esbuild/sunos-x64@0.21.5': '@esbuild/sunos-x64@0.25.3':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} resolution: {integrity: sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [sunos] os: [sunos]
'@esbuild/win32-arm64@0.21.5': '@esbuild/win32-arm64@0.25.3':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} resolution: {integrity: sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@esbuild/win32-ia32@0.21.5': '@esbuild/win32-ia32@0.25.3':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} resolution: {integrity: sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@esbuild/win32-x64@0.21.5': '@esbuild/win32-x64@0.25.3':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} resolution: {integrity: sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@@ -1603,9 +1618,12 @@ packages:
emoji-regex@9.2.2: emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
esbuild@0.21.5: error-stack-parser@2.1.4:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==}
engines: {node: '>=12'}
esbuild@0.25.3:
resolution: {integrity: sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==}
engines: {node: '>=18'}
hasBin: true hasBin: true
escalade@3.2.0: escalade@3.2.0:
@@ -1692,6 +1710,14 @@ packages:
fastq@1.19.1: fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
fdir@6.4.4:
resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
@@ -1974,6 +2000,10 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
picomatch@4.0.2:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
pify@2.3.0: pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -2200,6 +2230,22 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
source-map@0.5.6:
resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==}
engines: {node: '>=0.10.0'}
stack-generator@2.0.10:
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
stackframe@1.3.4:
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
stacktrace-gps@3.1.2:
resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==}
stacktrace-js@2.0.2:
resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==}
string-width@4.2.3: string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2256,6 +2302,10 @@ packages:
tiny-invariant@1.3.3: tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tinyglobby@0.2.13:
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
engines: {node: '>=12.0.0'}
to-regex-range@5.0.1: to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
@@ -2332,22 +2382,27 @@ packages:
victory-vendor@36.9.2: victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
vite@5.4.18: vite@6.3.4:
resolution: {integrity: sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==} resolution: {integrity: sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0 '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: '>=1.21.0'
less: '*' less: '*'
lightningcss: ^1.21.0 lightningcss: ^1.21.0
sass: '*' sass: '*'
sass-embedded: '*' sass-embedded: '*'
stylus: '*' stylus: '*'
sugarss: '*' sugarss: '*'
terser: ^5.4.0 terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta: peerDependenciesMeta:
'@types/node': '@types/node':
optional: true optional: true
jiti:
optional: true
less: less:
optional: true optional: true
lightningcss: lightningcss:
@@ -2362,6 +2417,10 @@ packages:
optional: true optional: true
terser: terser:
optional: true optional: true
tsx:
optional: true
yaml:
optional: true
which@2.0.2: which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
@@ -2400,73 +2459,79 @@ snapshots:
dependencies: dependencies:
regenerator-runtime: 0.14.1 regenerator-runtime: 0.14.1
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.25.3':
optional: true optional: true
'@esbuild/android-arm64@0.21.5': '@esbuild/android-arm64@0.25.3':
optional: true optional: true
'@esbuild/android-arm@0.21.5': '@esbuild/android-arm@0.25.3':
optional: true optional: true
'@esbuild/android-x64@0.21.5': '@esbuild/android-x64@0.25.3':
optional: true optional: true
'@esbuild/darwin-arm64@0.21.5': '@esbuild/darwin-arm64@0.25.3':
optional: true optional: true
'@esbuild/darwin-x64@0.21.5': '@esbuild/darwin-x64@0.25.3':
optional: true optional: true
'@esbuild/freebsd-arm64@0.21.5': '@esbuild/freebsd-arm64@0.25.3':
optional: true optional: true
'@esbuild/freebsd-x64@0.21.5': '@esbuild/freebsd-x64@0.25.3':
optional: true optional: true
'@esbuild/linux-arm64@0.21.5': '@esbuild/linux-arm64@0.25.3':
optional: true optional: true
'@esbuild/linux-arm@0.21.5': '@esbuild/linux-arm@0.25.3':
optional: true optional: true
'@esbuild/linux-ia32@0.21.5': '@esbuild/linux-ia32@0.25.3':
optional: true optional: true
'@esbuild/linux-loong64@0.21.5': '@esbuild/linux-loong64@0.25.3':
optional: true optional: true
'@esbuild/linux-mips64el@0.21.5': '@esbuild/linux-mips64el@0.25.3':
optional: true optional: true
'@esbuild/linux-ppc64@0.21.5': '@esbuild/linux-ppc64@0.25.3':
optional: true optional: true
'@esbuild/linux-riscv64@0.21.5': '@esbuild/linux-riscv64@0.25.3':
optional: true optional: true
'@esbuild/linux-s390x@0.21.5': '@esbuild/linux-s390x@0.25.3':
optional: true optional: true
'@esbuild/linux-x64@0.21.5': '@esbuild/linux-x64@0.25.3':
optional: true optional: true
'@esbuild/netbsd-x64@0.21.5': '@esbuild/netbsd-arm64@0.25.3':
optional: true optional: true
'@esbuild/openbsd-x64@0.21.5': '@esbuild/netbsd-x64@0.25.3':
optional: true optional: true
'@esbuild/sunos-x64@0.21.5': '@esbuild/openbsd-arm64@0.25.3':
optional: true optional: true
'@esbuild/win32-arm64@0.21.5': '@esbuild/openbsd-x64@0.25.3':
optional: true optional: true
'@esbuild/win32-ia32@0.21.5': '@esbuild/sunos-x64@0.25.3':
optional: true optional: true
'@esbuild/win32-x64@0.21.5': '@esbuild/win32-arm64@0.25.3':
optional: true
'@esbuild/win32-ia32@0.25.3':
optional: true
'@esbuild/win32-x64@0.25.3':
optional: true optional: true
'@eslint-community/eslint-utils@4.6.1(eslint@9.24.0(jiti@1.21.7))': '@eslint-community/eslint-utils@4.6.1(eslint@9.24.0(jiti@1.21.7))':
@@ -3498,10 +3563,10 @@ snapshots:
'@typescript-eslint/types': 8.30.1 '@typescript-eslint/types': 8.30.1
eslint-visitor-keys: 4.2.0 eslint-visitor-keys: 4.2.0
'@vitejs/plugin-react-swc@3.9.0(vite@5.4.18(@types/node@22.14.1))': '@vitejs/plugin-react-swc@3.9.0(vite@6.3.4(@types/node@22.14.1)(jiti@1.21.7)(yaml@2.7.1))':
dependencies: dependencies:
'@swc/core': 1.11.21 '@swc/core': 1.11.21
vite: 5.4.18(@types/node@22.14.1) vite: 6.3.4(@types/node@22.14.1)(jiti@1.21.7)(yaml@2.7.1)
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/helpers' - '@swc/helpers'
@@ -3717,31 +3782,37 @@ snapshots:
emoji-regex@9.2.2: {} emoji-regex@9.2.2: {}
esbuild@0.21.5: error-stack-parser@2.1.4:
dependencies:
stackframe: 1.3.4
esbuild@0.25.3:
optionalDependencies: optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5 '@esbuild/aix-ppc64': 0.25.3
'@esbuild/android-arm': 0.21.5 '@esbuild/android-arm': 0.25.3
'@esbuild/android-arm64': 0.21.5 '@esbuild/android-arm64': 0.25.3
'@esbuild/android-x64': 0.21.5 '@esbuild/android-x64': 0.25.3
'@esbuild/darwin-arm64': 0.21.5 '@esbuild/darwin-arm64': 0.25.3
'@esbuild/darwin-x64': 0.21.5 '@esbuild/darwin-x64': 0.25.3
'@esbuild/freebsd-arm64': 0.21.5 '@esbuild/freebsd-arm64': 0.25.3
'@esbuild/freebsd-x64': 0.21.5 '@esbuild/freebsd-x64': 0.25.3
'@esbuild/linux-arm': 0.21.5 '@esbuild/linux-arm': 0.25.3
'@esbuild/linux-arm64': 0.21.5 '@esbuild/linux-arm64': 0.25.3
'@esbuild/linux-ia32': 0.21.5 '@esbuild/linux-ia32': 0.25.3
'@esbuild/linux-loong64': 0.21.5 '@esbuild/linux-loong64': 0.25.3
'@esbuild/linux-mips64el': 0.21.5 '@esbuild/linux-mips64el': 0.25.3
'@esbuild/linux-ppc64': 0.21.5 '@esbuild/linux-ppc64': 0.25.3
'@esbuild/linux-riscv64': 0.21.5 '@esbuild/linux-riscv64': 0.25.3
'@esbuild/linux-s390x': 0.21.5 '@esbuild/linux-s390x': 0.25.3
'@esbuild/linux-x64': 0.21.5 '@esbuild/linux-x64': 0.25.3
'@esbuild/netbsd-x64': 0.21.5 '@esbuild/netbsd-arm64': 0.25.3
'@esbuild/openbsd-x64': 0.21.5 '@esbuild/netbsd-x64': 0.25.3
'@esbuild/sunos-x64': 0.21.5 '@esbuild/openbsd-arm64': 0.25.3
'@esbuild/win32-arm64': 0.21.5 '@esbuild/openbsd-x64': 0.25.3
'@esbuild/win32-ia32': 0.21.5 '@esbuild/sunos-x64': 0.25.3
'@esbuild/win32-x64': 0.21.5 '@esbuild/win32-arm64': 0.25.3
'@esbuild/win32-ia32': 0.25.3
'@esbuild/win32-x64': 0.25.3
escalade@3.2.0: {} escalade@3.2.0: {}
@@ -3846,6 +3917,10 @@ snapshots:
dependencies: dependencies:
reusify: 1.1.0 reusify: 1.1.0
fdir@6.4.4(picomatch@4.0.2):
optionalDependencies:
picomatch: 4.0.2
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
dependencies: dependencies:
flat-cache: 4.0.1 flat-cache: 4.0.1
@@ -4082,6 +4157,8 @@ snapshots:
picomatch@2.3.1: {} picomatch@2.3.1: {}
picomatch@4.0.2: {}
pify@2.3.0: {} pify@2.3.0: {}
pirates@4.0.7: {} pirates@4.0.7: {}
@@ -4312,6 +4389,25 @@ snapshots:
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
source-map@0.5.6: {}
stack-generator@2.0.10:
dependencies:
stackframe: 1.3.4
stackframe@1.3.4: {}
stacktrace-gps@3.1.2:
dependencies:
source-map: 0.5.6
stackframe: 1.3.4
stacktrace-js@2.0.2:
dependencies:
error-stack-parser: 2.1.4
stack-generator: 2.0.10
stacktrace-gps: 3.1.2
string-width@4.2.3: string-width@4.2.3:
dependencies: dependencies:
emoji-regex: 8.0.0 emoji-regex: 8.0.0
@@ -4393,6 +4489,11 @@ snapshots:
tiny-invariant@1.3.3: {} tiny-invariant@1.3.3: {}
tinyglobby@0.2.13:
dependencies:
fdir: 6.4.4(picomatch@4.0.2)
picomatch: 4.0.2
to-regex-range@5.0.1: to-regex-range@5.0.1:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
@@ -4476,14 +4577,19 @@ snapshots:
d3-time: 3.1.0 d3-time: 3.1.0
d3-timer: 3.0.1 d3-timer: 3.0.1
vite@5.4.18(@types/node@22.14.1): vite@6.3.4(@types/node@22.14.1)(jiti@1.21.7)(yaml@2.7.1):
dependencies: dependencies:
esbuild: 0.21.5 esbuild: 0.25.3
fdir: 6.4.4(picomatch@4.0.2)
picomatch: 4.0.2
postcss: 8.5.3 postcss: 8.5.3
rollup: 4.40.0 rollup: 4.40.0
tinyglobby: 0.2.13
optionalDependencies: optionalDependencies:
'@types/node': 22.14.1 '@types/node': 22.14.1
fsevents: 2.3.3 fsevents: 2.3.3
jiti: 1.21.7
yaml: 2.7.1
which@2.0.2: which@2.0.2:
dependencies: dependencies:

View File

@@ -1,207 +1,92 @@
import { defineConfig, Plugin } from "vite"; import { defineConfig, Plugin, HtmlTagDescriptor } from "vite";
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react-swc";
import path from "path"; import path from "path";
import fs from "fs";
export function devErrorAndNavigationPlugin(): Plugin { export function devErrorAndNavigationPlugin(): Plugin {
let stacktraceJsContent: string | null = null;
let dyadShimContent: string | null = null;
return { return {
name: "dev-error-and-navigation-handler", name: "dev-error-and-navigation-handler",
apply: "serve", apply: "serve",
configResolved() {
const stackTraceLibPath = path.join(
"node_modules",
"stacktrace-js",
"dist",
"stacktrace.min.js"
);
if (stackTraceLibPath) {
try {
stacktraceJsContent = fs.readFileSync(stackTraceLibPath, "utf-8");
} catch (error) {
console.error(
`[dyad-shim] Failed to read stacktrace.js from ${stackTraceLibPath}:`,
error
);
stacktraceJsContent = null;
}
} else {
console.error(`[dyad-shim] stacktrace.js not found.`);
}
const dyadShimPath = path.join("dyad-shim.js");
if (dyadShimPath) {
try {
dyadShimContent = fs.readFileSync(dyadShimPath, "utf-8");
} catch (error) {
console.error(
`[dyad-shim] Failed to read dyad-shim from ${dyadShimPath}:`,
error
);
dyadShimContent = null;
}
} else {
console.error(`[dyad-shim] stacktrace.js not found.`);
}
},
transformIndexHtml(html) { transformIndexHtml(html) {
return { const tags: HtmlTagDescriptor[] = [];
html,
tags: [
{
tag: "script",
injectTo: "head",
children: `
(function() {
// Check if running inside an iframe immediately
const isInsideIframe = window.parent !== window;
if (!isInsideIframe) {
// If not in an iframe, no need for the rest of the script
// console.log('[vite-dev-navigation] Not inside an iframe. Skipping setup.');
return;
}
// Use a unique key for our timestamp to avoid conflicts (optional, but kept for state consistency) // 1. Inject stacktrace.js
const NAV_TIMESTAMP_KEY = '__viteDevNavTimestamp'; if (stacktraceJsContent) {
let previousUrl = window.location.href; tags.push({
let lastNavigationTimestamp = Date.now(); // Initialize with current time tag: "script",
injectTo: "head-prepend",
children: stacktraceJsContent,
});
} else {
tags.push({
tag: "script",
injectTo: "head-prepend",
children:
"console.warn('[dyad-shim] stacktrace.js library was not injected.');",
});
}
// --- Initial State Timestamp Setup (Optional but helps consistency) --- // 2. Inject dyad shim
try { if (dyadShimContent) {
const initialState = history.state || {}; tags.push({
if (!initialState[NAV_TIMESTAMP_KEY]) { tag: "script",
initialState[NAV_TIMESTAMP_KEY] = lastNavigationTimestamp; injectTo: "head-prepend",
// Use try-catch for replaceState as well, in case state is not serializable initially children: dyadShimContent,
try { });
history.replaceState(initialState, '', window.location.href); } else {
// console.log('[vite-dev-navigation] Initial navigation timestamp set:', lastNavigationTimestamp); tags.push({
} catch(replaceError) { tag: "script",
console.warn('[vite-dev-navigation] Could not set initial navigation timestamp via replaceState:', replaceError); injectTo: "head-prepend",
} children: "console.warn('[dyad-shim] dyad shim was not injected.');",
} else { });
lastNavigationTimestamp = initialState[NAV_TIMESTAMP_KEY]; }
// console.log('[vite-dev-navigation] Using existing initial navigation timestamp:', lastNavigationTimestamp);
}
} catch (e) {
console.warn('[vite-dev-navigation] Could not access or modify initial history state:', e);
}
// --- History API Overrides --- return { html, tags };
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
const handleStateChangeAndNotify = (originalMethod, state, title, url) => {
const newTimestamp = Date.now();
const oldUrlForMessage = previousUrl; // Capture previous URL before potential change
// Prepare new state with timestamp
let newState = state || {};
if (typeof newState !== 'object' || newState === null) {
newState = {};
} else if (Object.isFrozen(newState)) { // Handle frozen state objects
newState = { ...newState };
}
newState[NAV_TIMESTAMP_KEY] = newTimestamp;
// Determine the intended new URL *before* calling the original method
let newUrl;
try {
// Resolve the potentially relative URL against the current location
newUrl = url ? new URL(url, window.location.href).href : window.location.href;
} catch (e) {
console.warn('[vite-dev-navigation] Error constructing URL:', url, e);
newUrl = window.location.href; // Fallback
}
// Determine the type of operation
const navigationType = (originalMethod === originalPushState ? 'pushState' : 'replaceState');
// Call the original history method
try {
originalMethod.call(history, newState, title, url);
// Update internal state *after* successful call
lastNavigationTimestamp = newTimestamp;
previousUrl = window.location.href; // Use the actual URL after the call
// Post message to parent *after* successful state change
// Use the 'newUrl' we calculated earlier as the intended target
// Use 'oldUrlForMessage' as the URL before this operation started
// console.log(\`[vite-dev-navigation] Emitting message: { type: '\${navigationType}', payload: { oldUrl: '\${oldUrlForMessage}', newUrl: '\${newUrl}' } }\`);
window.parent.postMessage({
type: navigationType, // 'pushState' or 'replaceState'
payload: {
oldUrl: oldUrlForMessage,
newUrl: newUrl // The URL passed to pushState/replaceState, resolved
}
}, '*'); // Consider a specific targetOrigin
} catch (e) {
console.error(\`[vite-dev-navigation] Error calling original \${navigationType}: \`, e);
// Optionally notify parent about the error during navigation attempt
window.parent.postMessage({
type: 'navigation-error',
payload: {
operation: navigationType,
message: e.message,
error: e.toString(),
stateAttempted: state, // Be careful sending state, might be large or sensitive
urlAttempted: url
}
}, '*');
}
};
history.pushState = function(state, title, url) {
handleStateChangeAndNotify(originalPushState, state, title, url);
};
history.replaceState = function(state, title, url) {
handleStateChangeAndNotify(originalReplaceState, state, title, url);
};
// --- Listener for Back/Forward Navigation (popstate event) ---
// We keep this listener primarily to update our internal 'previousUrl'
// and 'lastNavigationTimestamp' state so that subsequent push/replace
// messages report the correct 'oldUrl'. We no longer send messages from here.
window.addEventListener('popstate', (event) => {
const currentUrl = window.location.href;
// console.log('[vite-dev-navigation] Popstate event detected. Previous URL was:', previousUrl, 'New URL is:', currentUrl);
const newStateTimestamp = event.state?.[NAV_TIMESTAMP_KEY];
if (typeof newStateTimestamp === 'number') {
lastNavigationTimestamp = newStateTimestamp; // Update timestamp from popped state
// console.log('[vite-dev-navigation] Updated lastNavigationTimestamp from popstate:', lastNavigationTimestamp);
} else {
// console.warn('[vite-dev-navigation] Popstate event state missing navigation timestamp.');
// If timestamp is missing, we might lose track, but there's not much we can do reliably.
// Keep the last known timestamp.
}
// Update previousUrl to reflect the new reality AFTER the popstate event
previousUrl = currentUrl;
});
// --- Listener for Commands from Parent ---
window.addEventListener('message', (event) => {
// Security check: Ensure message is from parent
if (event.source !== window.parent || !event.data || typeof event.data !== 'object') {
return;
}
if (event.data.type === 'navigate') {
const direction = event.data.payload?.direction;
// console.log(\`[vite-dev-navigation] Received command: \${direction}\`);
if (direction === 'forward') {
history.forward();
} else if (direction === 'backward') {
history.back();
} else {
console.warn('[vite-dev-navigation] Received navigate command with invalid direction:', direction);
}
}
});
// --- Existing Error Handling ---
window.addEventListener('error', (event) => {
// console.log('[vite-dev-navigation] Forwarding error event to parent.');
window.parent.postMessage({
type: 'window-error',
payload: {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error?.toString() // Include stack trace if available
}
}, '*');
});
window.addEventListener('unhandledrejection', (event) => {
// console.log('[vite-dev-navigation] Forwarding unhandledrejection event to parent.');
window.parent.postMessage({
type: 'unhandled-rejection',
payload: {
reason: event.reason instanceof Error ? event.reason.toString() : JSON.stringify(event.reason) // Attempt to serialize reason
}
}, '*');
});
// console.log('[vite-dev-navigation] Navigation/error script initialized inside iframe. Initial URL:', previousUrl, 'Initial Timestamp:', lastNavigationTimestamp);
})(); // End of IIFE
`,
},
],
};
}, },
}; };
} }
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({ export default defineConfig(({ mode }) => ({
server: { server: {
host: "::", host: "::",

View File

@@ -45,7 +45,9 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => {
{/* Error message in the middle */} {/* Error message in the middle */}
<div className="px-6 py-1 text-sm"> <div className="px-6 py-1 text-sm">
<div className="text-red-700 dark:text-red-300 text-wrap">{error}</div> <div className="text-red-700 dark:text-red-300 text-wrap font-mono whitespace-pre-wrap break-words text-xs">
{error}
</div>
</div> </div>
{/* Tip message */} {/* Tip message */}
@@ -159,8 +161,16 @@ export const PreviewIframe = ({
const { type, payload } = event.data; const { type, payload } = event.data;
if (type === "window-error") { if (
const errorMessage = `Error in ${payload.filename} (line ${payload.lineno}, col ${payload.colno}): ${payload.message}`; type === "window-error" ||
type === "unhandled-rejection" ||
type === "iframe-sourcemapped-error"
) {
const stack =
type === "iframe-sourcemapped-error"
? payload.stack.split("\n").slice(0, 1).join("\n")
: payload.stack;
const errorMessage = `Error ${payload.message}\nStack trace: ${stack}`;
console.error("Iframe error:", errorMessage); console.error("Iframe error:", errorMessage);
setErrorMessage(errorMessage); setErrorMessage(errorMessage);
setAppOutput((prev) => [ setAppOutput((prev) => [
@@ -171,18 +181,6 @@ export const PreviewIframe = ({
appId: selectedAppId!, appId: selectedAppId!,
}, },
]); ]);
} else if (type === "unhandled-rejection") {
const errorMessage = `Unhandled Promise Rejection: ${payload.reason}`;
console.error("Iframe unhandled rejection:", errorMessage);
setErrorMessage(errorMessage);
setAppOutput((prev) => [
...prev,
{
message: `Iframe unhandled rejection: ${errorMessage}`,
type: "client-error",
appId: selectedAppId!,
},
]);
} else if (type === "pushState" || type === "replaceState") { } else if (type === "pushState" || type === "replaceState") {
console.debug(`Navigation event: ${type}`, payload); console.debug(`Navigation event: ${type}`, payload);
@@ -201,10 +199,6 @@ export const PreviewIframe = ({
newHistory[currentHistoryPosition] = payload.newUrl; newHistory[currentHistoryPosition] = payload.newUrl;
setNavigationHistory(newHistory); setNavigationHistory(newHistory);
} }
// Update navigation buttons state
setCanGoBack(currentHistoryPosition > 0);
setCanGoForward(currentHistoryPosition < navigationHistory.length - 1);
} }
}; };
@@ -212,6 +206,12 @@ export const PreviewIframe = ({
return () => window.removeEventListener("message", handleMessage); return () => window.removeEventListener("message", handleMessage);
}, [navigationHistory, currentHistoryPosition, selectedAppId]); }, [navigationHistory, currentHistoryPosition, selectedAppId]);
useEffect(() => {
// Update navigation buttons state
setCanGoBack(currentHistoryPosition > 0);
setCanGoForward(currentHistoryPosition < navigationHistory.length - 1);
}, [navigationHistory, currentHistoryPosition]);
// Initialize navigation history when iframe loads // Initialize navigation history when iframe loads
useEffect(() => { useEffect(() => {
if (appUrl) { if (appUrl) {