fix: only tear down an embed script tag we injected - #49
Conversation
loadScript deliberately cooperates with a page that loads embed.js itself — it returns early when window.ImageEditor already exists, and reuses a host-injected tag rather than duplicating it. resetLoader did not: it removed whatever tag matched the URL and deleted window.ImageEditor unconditionally. On such a page a versioned-bundle failure inside one component removed a DOM node we never created and deleted a global we do not own, breaking every other consumer of the embed with no indication why. Track the tags we inject in a WeakSet and gate resetLoader's teardown on it. A tag that has provably failed (error event or reused-tag timeout) is still removed either way — a dead tag is no use to the host either, and leaving it would make every retry reuse it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@sidgaikwad is attempting to deploy a commit to the Unlayer Team on Vercel. A member of the Team first needs to authorize it. |
|
@sidgaikwad two small things:
|
The JSDoc, the call-site comment and the README all justified the reset by claiming embed.js memoizes a failed bundle load forever, so reloading it was the only route back. That is not true of the current loader: it nulls loadPromise in a catch specifically so a transient CDN blip does not kill the editor for the page session. So a remount recovers on its own, including on host-loaded pages where the reset now deliberately leaves the tag and global alone. Describe the reset as clearing the state we own, not as the mechanism recovery depends on. No behaviour change.
|
You are right, and I should have checked rather than inheriting the premise from the existing comment. Fixed in I pulled the live .catch(function (err) {
// Don't memoize failure: a transient CDN blip shouldn't kill the
// image editor for the whole page session — let a reopen retry.
loadPromise = null;
throw err;
});So Updated all three places to describe the reset as clearing the state this module owns, rather than as the mechanism recovery depends on:
PR description rewritten too; the "Trade-off worth reviewing" section is gone and replaced with the correction. No behaviour change — 49 tests, coverage still 100%, lint/typecheck/build clean. One follow-up question this raises, which I did not act on. If I left it in place because (a) it is a behaviour change beyond this PR's scope, and (b) Happy to open a separate issue for it, or drop the call in this PR if you would rather — your call, since you know the CDN's version history. |
Fixes #32.
Problem
loadScriptis deliberately written to cooperate with a page that loadsembed.jsitself — it returns early whenwindow.ImageEditoralready exists, and reuses a host-injected<script>tag rather than injecting a duplicate.resetLoaderdid not honour that:It has no notion of who created the tag, and it is called automatically from the component's terminal
.catch. So on a page where the host owns the embed, a versioned-bundle load failure inside our component removed a DOM node we never created and deleted a global we do not own — breaking every other consumer ofwindow.ImageEditoron that page, with nothing to indicate why.Fix
Track injected tags in a module-level
WeakSetand gateresetLoader's teardown on it. When we don't own the tag, the reset still evicts our cached promise and rejects pending waiters (so our own state is clean), but leaves the page's DOM and global alone.A provably-failed tag is still removed either way — both the
errorevent and the reused-tag timeout mean the script will never fire again, so a dead tag is no use to the host, and leaving it in place would make every retry reuse it and time out. That deliberate behaviour and its existing test are unchanged;failWithjust takes an explicitremoveTagflag now so the reset path can opt out.One ordering detail:
resetLoadercaptures the tag before callingabort(), sinceabortmay remove it.Verification
Both new tests are red without the fix:
loadScripttests pass; 49 total; coverage still 100% across the boardlint,typecheck,buildcleanOn recovery
An earlier version of this description claimed that not owning the tag meant losing the ability to retry. That was wrong, and the docs in the branch repeated it — both are now fixed.
embed.jsdoes not memoize a failed bundle load:So a later
createEditorretries regardless of whether the tag was ours, and there is no permanent-failure mode to trade against. The reset just clears the state this module owns.Updated in this branch: the
resetLoaderJSDoc, the call-site comment inImageEditor.tsx, and the README "Error handling" bullet.