Repository navigation
feat(widget): wait for cookie consent before using storage - #468
Conversation
anurag629
left a comment
There was a problem hiding this comment.
Thanks @Saifjafri212213, this is careful work. The gate is in the right places (hydrate(), the send path, the bubble), setConsent(false) clears the conversation, the session token and the bubble flag, and the loader keeps consent across init(). Build, typecheck, test:coverage, lint (same as dev), api:check and the full e2e suite (73, with your consent spec and S17) pass locally. A few things before it goes in:
-
ChatCops.setConsentis missing frompackages/widget/src/index.ts. That object iswindow.ChatCopsin the single-file build (chatcops.full.min.js) and the default export of the npm package, so sites on either can't give consent at all. That's also why CI's docs check fails (website/src/content/docs/widget/api.mdx:156,configuration.mdx:180,183). Add it next tosetContextatindex.ts:92. Since the issue asks for it to work beforeinit(), keep the value in a module variable when there's no instance yet, and pass it to the widget ininit()beforeinstance.init(), the way the loader does atloader.ts:165. Please add a test forsetConsentbefore and afterChatCops.init(), and the method to the type test inpackages/widget/tests/api.test-d.ts. -
website/src/content/docs/widget/configuration.mdx:176:consentBannerisn't declared, so docs:check fails on that block too. Adddeclare const consentBanner: { open(): void };above it. -
Please revert the
.size-limit.jsonchange. Budgets are my call, and I'll sort them out in a separate PR now: with this PR the panel is exactly at 17 kB and the single file at 19.04 kB, so it does need room. Anything you can trim in the meantime still helps.
Small one: sendMessage() while waiting for consent resolves without sending (handleSend returns at widget.ts where it checks !this.consented). That's reasonable, but say so in the setConsent docs, so a site doesn't wait for a reply that never comes.
|
@Saifjafri212213 #469 raised the budgets to 18 kB for the panel and 20 kB for the single file, so after a rebase on dev you can just drop your |
…#405) `waitForConsent` (and `data-wait-for-consent`) holds the chat behind a notice until `ChatCops.setConsent(true)`. While it waits the widget reads and writes no localStorage or sessionStorage and calls no API, and the welcome bubble stays hidden. The notice has a button that calls `onConsentRequest`, left out when there is none. `setConsent(true)` shows the normal chat (the saved conversation comes back); `setConsent(false)` closes the chat and removes the conversation, the session token and the bubble's flag. Both work before init() and before the chat has loaded, through the loader and the Widget class. Panel.hideMessages/showMessages become one setBodyVisible(), and the new private members join the mangle list, to keep the panel in budget. The single-file limit goes from 19 KB to 19.5 KB.
…t, fix docs samples Review of codercops#468: window.ChatCops and the default export had no setConsent, so those builds could not give consent. It now keeps the value until init() and applies it before init(), like the loader. Also declares consentBanner in the docs sample, notes that sendMessage() resolves without sending while consent is missing, and drops the .size-limit.json change (rebased on dev's new budgets).
e513ee1 to
3ad3af9
Compare
anurag629
left a comment
There was a problem hiding this comment.
Thanks @Saifjafri212213, all of it is in. ChatCops.setConsent on the npm and single-file object is kept across init() like the loader's, with tests before and after init and the type test for both builds. The docs samples compile, the sendMessage() note is in both docs pages, and the budget change is gone. With current dev merged in: build, typecheck, test:coverage, lint (same as dev), api:check, docs:check, size (panel 17.02 of 18 kB, single file 19.11 of 20 kB) and the full e2e suite (74, in Chrome, with your consent spec and S17) pass. Merging once CI is green.
|
Merged, thanks @Saifjafri212213. #461 is still yours if you want it: leave a comment there and I'll assign it. |
Summary
Adds a consent gate to the widget. With
waitForConsent: true(ordata-wait-for-consenton the script tag), opening the chat shows a notice instead of the conversation, and the widget reads and writes nolocalStorageorsessionStorageand calls no API until the site callsChatCops.setConsent(true). The default is off, so nothing changes for existing sites.Closes #405
What changed
waitForConsent,onConsentRequestandsetConsent(consent), on theWidgetclass and on the script tag loader'sChatCopsLoader.data-wait-for-consentis read byconfigFromScript("false"turns it off).onConsentRequest(). WithoutonConsentRequestthere is no button. Both strings are inWidgetLocaleStrings(consentNotice,consentButton) with English defaults, so other languages fall back to English. The button reuses the existing retry/regenerate button style.hydrate()shows the notice instead of reading the saved conversation, andhandleSend()andreset()do nothing while waiting.setUser()no longer removes the stored session token while waiting (ChatClient.setUserToken(token, forget)). The welcome bubble is created only once consent is given, because it stores a "shown" flag.setConsent(true): removes the notice, reads the saved conversation and starts the bubble. A chat the visitor closed stays closed. Beforeinit()(Widget) or before the chat has loaded (loader) it is remembered, and the last call wins.setConsent(false): closes the chat and removes the conversation, the session token and the bubble's flag, then the notice shows again on the next open. It reusesreset(). If the visitor never gave consent in this page view it does nothing and touches no storage.@chatcops/widget.Notes for review
Panel.hideMessages()andshowMessages()into onesetBodyVisible(), since every caller used them in pairs withinput.setVisible(), and added the new private members tomangle.ts. If you would rather keep 19 kB, say so and I will look for more to cut.setConsent(true)does not open a closed chat. It turns the notice into the chat if the panel is open and leaves a closed one closed. Say if you want it to open.showOnceflag is a storage write.setConsent(false)on a widget that never had consent touches no storage, as the issue asks for no reads or writes while waiting. A site that reloads after the visitor withdrew consent elsewhere would still have the old conversation in storage until it is cleared by asetConsent(false)call that follows atruein the same page view.Screenshots
The notice with the button (dark and light), and the normal chat once
setConsent(true)is called:How I Tested This
packages/widget/tests/consent.test.ts(35 tests). It wrapslocalStorageandsessionStorageand records every call: no call at all while waiting, through init, open,autoOpen, a due welcome bubble, an inline chat,sendMessage(with and withoutopen: false),newConversation(),clear(),setUser()andsetConsent(false), and no request to the API. It also covers the notice and its button (with and withoutonConsentRequest, custom strings, shown once),setConsent(true)(restores the saved conversation, welcome message, closed chat, a send that saves, inline, the bubble, beforeinit()),setConsent(false)(removes the conversation, session token and bubble flag inlocalandsession, closes the chat, aborts a running reply, shows the notice again, can be given again, an inline chat), the loader (afterinit(), beforeinit(), before the chat loads, last call wins, withdrawal) and thedata-wait-for-consentvalues.setUser, the button, the notice's removal, the idempotence check, the pre-initflag, the loader's forwarding and queue, the attribute parsing). All fail the suite. Writing the tests first showed thatsetUser()was writing to storage while waiting, which is whysetUserToken()got theforgetflag.nothing reads or writes storage before the visitor interactson a new fixture withdata-wait-for-consent: opening the chat touches no storage and no API, accepting gives the chat and the first storage read, a message is saved, and rejecting closes the chat and removes it. S17 instates.spec.tspasses the existing axe check (WCAG 2.2 AA) in light, dark and right-to-left, and the button takes keyboard focus. The rest of the e2e suite passes (70 tests) except the threeS7 reply with sourcestests, which fail the same way on a cleandevhere; I skippedvisual.spec.tssince its baselines are made on CI's Linux Chromium.localStorageorsessionStoragebefore that.pnpm --filter @chatcops/widget typecheckis clean, lint is atdev's 38 warnings, the 736 widget tests pass, andpnpm sizepasses with the new limit. I did not runpnpm test:coveragefor the whole repo, because other packages here fail on a cleandev(the CLI, ingest, SQLite and Upstash tests need builds ornode_modulesthat are missing locally), and I regenerated onlyapi/widget.api.mdfor the same reason.AI assistance
An AI coding assistant helped write the code, the tests and the docs. I ran the checks above, including the storage wrapper, the mutation checks and the browser check.