From 28af415e78873e3e0ca8a25408e454e90f2530ea Mon Sep 17 00:00:00 2001 From: Matt Gros <3311227+mpge@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:16:53 -0400 Subject: [PATCH 1/5] fix(widget): make widget-path configurable for NestJS backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared Vue widget hardcoded `/support/widget` as the API path prefix, which matches every host-adapter plugin (Laravel, Rails, Django, Adonis, WordPress, Filament, Symfony, .NET, Go, Spring, Phoenix). But the NestJS reference mounts its WidgetController at `/escalated/widget` — so the shared widget simply couldn't talk to a NestJS backend at all. Adds: - a `widgetPath` prop on EscalatedWidget.vue (default `/support/widget`) - a `data-widget-path` attribute on the loader script tag (also overridable via window.EscalatedWidget.widgetPath) NestJS embedders add `data-widget-path="/escalated/widget"` to the script tag; every other framework continues to work unchanged with the default. Verified no ESLint warnings on modified files. Widget behavior for Laravel etc. is unchanged because the default path is `/support/widget`. --- src/widget/EscalatedWidget.vue | 9 ++++++++- src/widget/index.js | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/widget/EscalatedWidget.vue b/src/widget/EscalatedWidget.vue index cc02784..eb24b67 100644 --- a/src/widget/EscalatedWidget.vue +++ b/src/widget/EscalatedWidget.vue @@ -4,6 +4,13 @@ import { sanitizeHtml } from '../utils/sanitizeHtml'; const props = defineProps({ baseUrl: { type: String, default: '' }, + // Path prefix the host framework mounts widget routes under. Defaults + // to /support/widget which matches every host-adapter plugin + // (Laravel / Rails / Django / Adonis / WordPress / Filament / + // Symfony / .NET / Go / Spring / Phoenix). The NestJS reference + // mounts at /escalated/widget, so embedders on a NestJS backend + // should override this with data-widget-path="/escalated/widget". + widgetPath: { type: String, default: '/support/widget' }, initialColor: { type: String, default: '#4F46E5' }, initialPosition: { type: String, default: 'bottom-right' }, }); @@ -64,7 +71,7 @@ const statusResult = ref(null); let searchTimer = null; async function api(method, apiPath, body = null) { - const url = `${props.baseUrl}/support/widget${apiPath}`; + const url = `${props.baseUrl}${props.widgetPath}${apiPath}`; const opts = { method, headers: { diff --git a/src/widget/index.js b/src/widget/index.js index fe52fa5..c581fea 100644 --- a/src/widget/index.js +++ b/src/widget/index.js @@ -7,6 +7,10 @@ import EscalatedWidget from './EscalatedWidget.vue'; const config = { baseUrl: scriptTag?.getAttribute('data-base-url') || globalConfig.baseUrl || '', + // Host-framework-specific path prefix. Every plugin except the NestJS + // reference mounts at /support/widget; NestJS mounts at + // /escalated/widget. Set via data-widget-path on NestJS backends. + widgetPath: scriptTag?.getAttribute('data-widget-path') || globalConfig.widgetPath || '/support/widget', color: scriptTag?.getAttribute('data-color') || globalConfig.color || '#4F46E5', position: scriptTag?.getAttribute('data-position') || globalConfig.position || 'bottom-right', }; @@ -29,6 +33,7 @@ import EscalatedWidget from './EscalatedWidget.vue'; render() { return h(EscalatedWidget, { baseUrl: config.baseUrl, + widgetPath: config.widgetPath, initialColor: config.color, initialPosition: config.position, }); From 225cf4844b58220e6cf19f1b2c5e6fe3f1c8dd97 Mon Sep 17 00:00:00 2001 From: Matt Gros <3311227+mpge@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:21:11 -0400 Subject: [PATCH 2/5] fix(chat): make useChat widgetPath configurable for NestJS backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same class of bug as escalated#35 (widget hardcoded /support/widget). useChat composable hardcoded /support/widget/chat/* URLs in all six API methods (startChat, sendMessage, sendTyping, endChat, rateChat, checkAvailability), so chat functionality in agent UI pages (ActiveChatsPanel, ChatQueue, Agent/TicketShow) can't reach NestJS's /escalated/widget/chat/* routes. useChat now accepts options.widgetPath (default /support/widget); each URL is built from that prefix. Existing callers keep working unchanged — they call useChat() with no arg and get the default path. NestJS hosts can pass useChat({ widgetPath: '/escalated/widget' }) at each call site, OR the host app's Inertia layer can supply it via a shared context / provide/inject pattern (future refactor). Verified npm run lint clean. --- src/composables/useChat.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/composables/useChat.js b/src/composables/useChat.js index b428b58..e993aa6 100644 --- a/src/composables/useChat.js +++ b/src/composables/useChat.js @@ -6,8 +6,17 @@ import { useRealtime } from './useRealtime'; * * Provides methods to start, send messages, end, and rate chat sessions, * plus real-time subscription via useRealtime. + * + * @param {Object} [options] + * @param {string} [options.widgetPath] - Path prefix under which the host + * framework mounts widget routes. Defaults to `/support/widget` which + * matches every host-adapter plugin (Laravel / Rails / Django / Adonis / + * WordPress / Filament / Symfony / .NET / Go / Spring / Phoenix). On a + * NestJS backend, pass `/escalated/widget` (or read from whatever + * configuration channel the host app exposes to the client). */ -export function useChat() { +export function useChat(options = {}) { + const widgetPath = options.widgetPath ?? '/support/widget'; const connectionState = ref('disconnected'); // disconnected | connecting | connected const messageBuffer = ref([]); @@ -40,7 +49,7 @@ export function useChat() { * @returns {Promise} The created chat session */ async function startChat(data) { - return apiRequest('POST', '/support/widget/chat/start', data); + return apiRequest('POST', `${widgetPath}/chat/start`, data); } /** @@ -50,7 +59,7 @@ export function useChat() { * @returns {Promise} */ async function sendMessage(sessionId, body) { - const message = await apiRequest('POST', `/support/widget/chat/${sessionId}/messages`, body); + const message = await apiRequest('POST', `${widgetPath}/chat/${sessionId}/messages`, body); return message; } @@ -65,7 +74,7 @@ export function useChat() { if (now - lastTypingSent < 3000) return; lastTypingSent = now; try { - await apiRequest('POST', `/support/widget/chat/${sessionId}/typing`); + await apiRequest('POST', `${widgetPath}/chat/${sessionId}/typing`); } catch { // silently ignore typing failures } @@ -77,7 +86,7 @@ export function useChat() { * @returns {Promise} */ async function endChat(sessionId) { - return apiRequest('POST', `/support/widget/chat/${sessionId}/end`); + return apiRequest('POST', `${widgetPath}/chat/${sessionId}/end`); } /** @@ -88,7 +97,7 @@ export function useChat() { * @returns {Promise} */ async function rateChat(sessionId, rating, comment = '') { - return apiRequest('POST', `/support/widget/chat/${sessionId}/rate`, { rating, comment }); + return apiRequest('POST', `${widgetPath}/chat/${sessionId}/rate`, { rating, comment }); } /** @@ -98,7 +107,7 @@ export function useChat() { */ async function checkAvailability(departmentId = null) { const query = departmentId ? `?department_id=${departmentId}` : ''; - return apiRequest('GET', `/support/widget/chat/availability${query}`); + return apiRequest('GET', `${widgetPath}/chat/availability${query}`); } /** From c5724360b0c4fbcb7ede09f0a45ec92b04bbcc85 Mon Sep 17 00:00:00 2001 From: Matt Gros <3311227+mpge@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:24:03 -0400 Subject: [PATCH 3/5] fix(chat): thread widgetPath through agent-UI chat components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires up the useChat options.widgetPath (added earlier in this PR) in all three agent-facing callers: - pages/Agent/TicketShow.vue — also fixes the two inline ChatComposer endpoints (send-endpoint / typing-endpoint) that were hardcoded as `/support/widget/chat/...` outside of useChat. Reads the route prefix from page.props.escalated?.prefix (same pattern used across the shared frontend; default 'support'). - components/ActiveChatsPanel.vue — imports usePage, reads page.props.escalated?.prefix, passes widgetPath to useChat(). - components/ChatQueue.vue — same pattern. Combined with the earlier widget + useChat fixes in this PR, chat functionality now works end-to-end on both default-prefix ('support') and NestJS-prefix ('escalated') host frameworks. Verified `npm run lint` clean. --- src/components/ActiveChatsPanel.vue | 10 ++++++++-- src/components/ChatQueue.vue | 10 ++++++++-- src/pages/Agent/TicketShow.vue | 14 ++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/components/ActiveChatsPanel.vue b/src/components/ActiveChatsPanel.vue index e0e65d2..ae77e19 100644 --- a/src/components/ActiveChatsPanel.vue +++ b/src/components/ActiveChatsPanel.vue @@ -1,5 +1,6 @@