Repository navigation
Conversation
HubSpot chat showed every dashboard user as "Unknown Visitor", so tickets created from chat had no contact or company. Before loading the widget, the dashboard now fetches a visitor identification token from the API and sets identificationEmail and identificationToken in hsConversationsSettings. If the token request fails, the widget still loads, anonymously as before. A previous user's identity is always cleared first. Calls made during the fetch share one request and one load(), and "open chat" loads the widget identified too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
talissoncosta
left a comment
There was a problem hiding this comment.
Nice, this closes the "Unknown Visitor" gap with a sane fallback. A few questions inline, mostly about reusing the token and the logout path. None blocking for a draft.
question on the description: "logging in as someone else in the same tab never reuses it" is true for the identity, but HubSpot's messagesUtk cookie and old thread stay until clear-on-logout lands. Worth softening that line, or did you test it with two accounts?
| const user = AccountStore.getUser() as AccountModel | undefined | ||
| if (!user?.email) return | ||
| try { | ||
| const { token } = await data.get( |
There was a problem hiding this comment.
suggestion: Could this go through RTK Query? We'd get reuse for free: the cache keeps the token, overlapping calls share one request, and logout already clears it via resetApiState().
getHubspotChatToken: builder.query<Res['hubspotChatToken'], void>({
keepUnusedDataFor: 11 * 60 * 60, // HubSpot tokens last 12h
query: () => ({ url: 'auth/users/me/hubspot-chat-token/' }),
}),If #8813 also returns the email the token was minted for, we wouldn't need AccountStore here at all:
const { data } = await getStore().dispatch(
hubspotChatTokenService.endpoints.getHubspotChatToken.initiate(undefined, { subscribe: false }),
)
if (data) {
window.hsConversationsSettings = { ...settings, identificationEmail: data.email, identificationToken: data.token }
}| identifyHubSpotVisitor() | ||
| .then(() => { | ||
| // Re-checked: the org may have changed while the token was fetched. | ||
| if (canLoadHubSpot()) { |
There was a problem hiding this comment.
question: If the user logs out while the token request is in flight, this still loads the widget with their identity. I reproduced it with a unit test (identifyChatUser(), then hideChat(), then resolve the token: load() is called with the old token).
A flag set on logout fixes it:
let loggedOut = false
// in loadHubSpotWidget, after isLoadingHubSpot = true
loggedOut = false
// after the fetch
if (!loggedOut && canLoadHubSpot()) {
// in hideChat
loggedOut = trueWith it, both the logout case and a logout plus login during the fetch stop loading the old identity, and the existing tests still pass.
| beforeEach(() => { | ||
| jest.resetAllMocks() | ||
| mockUtils.isOrgOnFreePlan.mockReturnValue(false) | ||
| mockAccountStore.getUser.mockReturnValue({ email: USER_EMAIL } as any) |
There was a problem hiding this comment.
suggestion: as any hides every other field. With the email coming from the token response, this could mock the token endpoint with a typed Res['hubspotChatToken'] instead of a cast user.
Thanks for submitting a PR! Please check the boxes below:
docs/if required so people know about the feature. (Not required: internal support widget.)Changes
Contributes to #8813
HubSpot chat (#8710) shows every logged-in user as "Unknown Visitor", so tickets created from chat have no contact or company. Pylon chat got the user's email and a signed hash. The HubSpot path sent nothing. Now the dashboard tells HubSpot who is chatting before the widget loads.
widget.load(), the dashboard fetches a token fromauth/users/me/hubspot-chat-token/(feat(Support): Add an endpoint that issues HubSpot chat visitor tokens #8813). It then setsidentificationEmailandidentificationTokeninhsConversationsSettings, which is where HubSpot reads them.load(). The org is checked again after the fetch.Safe to merge before #8813 is deployed: until then, chat just stays anonymous. Merging #8813 first avoids a console error on each page load for users with HubSpot chat.
Not in this PR: clearing HubSpot's chat cookies on logout. HubSpot's
clear({ resetWidget: true })re-creates the widget, which conflicts withloadImmediately: false, so it needs testing on its own.Review effort: 2/5
How did you test this code?
common/__tests__/loadChat.test.ts: the identity is set beforeload(), anonymous fallback when the request fails, a previous identity is dropped, overlapping calls make one request and one load, no request when already loaded, no load when the org changes during the fetch. Existing tests updated for the async load.CI=truelint on changed lines, the full unit test suite and the production build pass.hubspot_chaton, open chat and send a message, then check that the conversation and ticket in HubSpot show the user's contact, not "Unknown Visitor".🤖 Generated with Claude Code