Skip to content

Commit c3c3942

Browse files
fix(chatwoot): destroy() now removes the widget DOM + clears storage
Chatwoot's SDK leaves its iframe and bubble holder in the DOM even after \$chatwoot.reset(). destroy() now explicitly removes: - #cw-widget-holder, #cw-bubble-holder, .woot-widget-holder, .woot-widget-bubble, .woot--bubble-holder, iframe.woot-widget-holder - localStorage keys containing 'chatwoot' (try/catch so blocked storage doesn't throw) Fixes the 'destroy basmadim kaybolmadi' report — after destroy() the widget is fully gone and load() starts fresh. env.d.ts gains a minimal Storage interface for the localStorage usage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f341e37 commit c3c3942

2 files changed

Lines changed: 49 additions & 0 deletions

File tree

src/env.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,12 @@ interface HTMLScriptElement {
4141
parentNode: { insertBefore(node: unknown, ref: unknown): void } | null
4242
remove(): void
4343
}
44+
45+
interface Storage {
46+
readonly length: number
47+
key(index: number): string | null
48+
getItem(key: string): string | null
49+
setItem(key: string, value: string): void
50+
removeItem(key: string): void
51+
clear(): void
52+
}

src/providers/chatwoot.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,50 @@ export function shutdown(): Promise<void> {
291291
})
292292
}
293293

294+
const CHATWOOT_DOM_SELECTORS = [
295+
"#cw-widget-holder",
296+
"#cw-bubble-holder",
297+
".woot-widget-holder",
298+
".woot-widget-bubble",
299+
".woot--bubble-holder",
300+
"iframe.woot-widget-holder",
301+
] as const
302+
303+
function removeChatwootDom(): void {
304+
if (!isBrowser()) return
305+
const doc = document as unknown as {
306+
querySelectorAll(selector: string): ArrayLike<{ remove(): void }>
307+
}
308+
for (const selector of CHATWOOT_DOM_SELECTORS) {
309+
const nodes = doc.querySelectorAll(selector)
310+
for (let i = 0; i < nodes.length; i++) {
311+
nodes[i]?.remove()
312+
}
313+
}
314+
}
315+
316+
function clearChatwootStorage(): void {
317+
if (!isBrowser()) return
318+
try {
319+
const storage = (globalThis as unknown as { localStorage?: Storage }).localStorage
320+
if (!storage) return
321+
const drop: string[] = []
322+
for (let i = 0; i < storage.length; i++) {
323+
const key = storage.key(i)
324+
if (key && key.toLowerCase().includes("chatwoot")) drop.push(key)
325+
}
326+
for (const key of drop) storage.removeItem(key)
327+
} catch {
328+
// storage may be blocked by the browser; ignore
329+
}
330+
}
331+
294332
export async function destroy(): Promise<void> {
295333
if (!isBrowser()) return
296334
await shutdown().catch(() => undefined)
297335
removeScript("ahize-chatwoot")
336+
removeChatwootDom()
337+
clearChatwootStorage()
298338
if (readyListener && window) {
299339
window.removeEventListener?.("chatwoot:ready", readyListener)
300340
readyListener = undefined

0 commit comments

Comments
 (0)