Skip to content

Commit 34915c9

Browse files
feat(chatwoot,#79): expose typed config fields on ChatwootLoadOptions
Eleven Chatwoot settings (type, widgetStyle, darkMode, position, locale, useBrowserLanguage, hideMessageBubble, showPopoutButton, showUnreadMessagesDialog, launcherTitle, baseDomain) were previously only reachable via the opaque settings blob. Promote them to first-class, typed fields and merge them into window.chatwootSettings before boot, keeping `settings` as an escape hatch for anything not yet typed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c6ee227 commit 34915c9

2 files changed

Lines changed: 97 additions & 1 deletion

File tree

src/providers/chatwoot.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,46 @@ function normalizeBaseUrl(url: string): string {
9393
return u
9494
}
9595

96+
const TYPED_SETTINGS_KEYS = [
97+
"type",
98+
"widgetStyle",
99+
"darkMode",
100+
"position",
101+
"locale",
102+
"useBrowserLanguage",
103+
"hideMessageBubble",
104+
"showPopoutButton",
105+
"showUnreadMessagesDialog",
106+
"launcherTitle",
107+
"baseDomain",
108+
] as const satisfies ReadonlyArray<keyof ChatwootLoadOptions>
109+
96110
export interface ChatwootLoadOptions extends LoadOptions {
97111
websiteToken: string
98112
baseUrl?: string
113+
/** Bubble design. */
114+
type?: "standard" | "expanded_bubble"
115+
/** Widget chrome variant. */
116+
widgetStyle?: "standard" | "flat"
117+
/** Initial color scheme; runtime override available via setColorScheme(). */
118+
darkMode?: "light" | "auto"
119+
/** Bubble alignment. */
120+
position?: "left" | "right"
121+
/** Initial locale; runtime override available via setLocale(). */
122+
locale?: string
123+
/** When true, Chatwoot picks the locale from the visitor's browser. */
124+
useBrowserLanguage?: boolean
125+
/** Hide the floating message bubble. */
126+
hideMessageBubble?: boolean
127+
/** Show a popout button inside the widget header. */
128+
showPopoutButton?: boolean
129+
/** Show the unread-messages dialog. */
130+
showUnreadMessagesDialog?: boolean
131+
/** Custom launcher button title. */
132+
launcherTitle?: string
133+
/** Cookie domain (typically used for cross-subdomain identity). */
134+
baseDomain?: string
135+
/** Escape hatch for any setting not yet typed; merged before typed fields. */
99136
settings?: Record<string, unknown>
100137
}
101138

@@ -112,7 +149,12 @@ export async function load(options: ChatwootLoadOptions): Promise<void> {
112149
currentToken = options.websiteToken
113150
currentBaseUrl = baseUrl
114151
await waitForDefer(options.defer ?? "immediate")
115-
if (options.settings) w().chatwootSettings = options.settings
152+
const settings: Record<string, unknown> = { ...options.settings }
153+
for (const key of TYPED_SETTINGS_KEYS) {
154+
const v = options[key]
155+
if (v !== undefined) settings[key] = v
156+
}
157+
if (Object.keys(settings).length > 0) w().chatwootSettings = settings
116158

117159
readyPromise = new Promise((r) => {
118160
readyResolve = r

test/chatwoot.browser.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,60 @@ describe("chatwoot (browser) — vendor-doc audit fixes (#79)", () => {
116116
await chatwoot.destroy()
117117
})
118118

119+
it("typed config fields populate window.chatwootSettings", async () => {
120+
const rec: Recorded = {
121+
setColorScheme: [],
122+
deleteCustomAttribute: [],
123+
deleteConversationCustomAttribute: [],
124+
popoutChatWindow: 0,
125+
}
126+
const chatwoot = await import("../src/providers/chatwoot.ts")
127+
const loadPromise = chatwoot.load({
128+
websiteToken: "tok_typed",
129+
type: "expanded_bubble",
130+
widgetStyle: "flat",
131+
darkMode: "auto",
132+
position: "left",
133+
locale: "tr",
134+
useBrowserLanguage: false,
135+
hideMessageBubble: true,
136+
showPopoutButton: true,
137+
showUnreadMessagesDialog: false,
138+
launcherTitle: "Yardım",
139+
baseDomain: ".example.com",
140+
settings: { customField: "kept" },
141+
})
142+
await new Promise((r) => setTimeout(r, 0))
143+
144+
// biome-ignore lint/suspicious/noExplicitAny: test shim
145+
const settings = (globalThis as any).chatwootSettings as Record<string, unknown>
146+
expect(settings).toMatchObject({
147+
type: "expanded_bubble",
148+
widgetStyle: "flat",
149+
darkMode: "auto",
150+
position: "left",
151+
locale: "tr",
152+
useBrowserLanguage: false,
153+
hideMessageBubble: true,
154+
showPopoutButton: true,
155+
showUnreadMessagesDialog: false,
156+
launcherTitle: "Yardım",
157+
baseDomain: ".example.com",
158+
customField: "kept",
159+
})
160+
161+
// Finish boot so destroy() doesn't race.
162+
const script = document.getElementById("ahize-chatwoot") as HTMLScriptElement
163+
// biome-ignore lint/suspicious/noExplicitAny: test shim
164+
;(globalThis as any).chatwootSDK = { run: () => {} }
165+
// biome-ignore lint/suspicious/noExplicitAny: test shim
166+
;(globalThis as any).$chatwoot = fakeChatwoot(rec)
167+
script.dispatchEvent(new Event("load"))
168+
window.dispatchEvent(new CustomEvent("chatwoot:ready"))
169+
await loadPromise
170+
await chatwoot.destroy()
171+
})
172+
119173
it("bridges chatwoot:opened / closed / on-start-conversation / postback", async () => {
120174
const rec: Recorded = {
121175
setColorScheme: [],

0 commit comments

Comments
 (0)