148 Commits

Author SHA1 Message Date
Émile Ré
bb9fb22913 Release @probo/cookie-banner/v0.10.1
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-07-28 15:07:11 +02:00
Émile Ré
85bdcffdbb Allow integrations in discovery mode
Signed-off-by: Émile Ré <emile@probo.com>
2026-07-28 14:23:23 +02:00
Sacha Al Himdani
4c57d201a4 Make license declarations consistently MIT
The source headers, LICENSE files, and license metadata had drifted
apart. Align the entire project to MIT:

- Convert every source-file header to the MIT text across all comment
  styles (Go, TS, TSX, JS, MJS, SQL, CSS, GraphQL, shell), including
  SPDX-License-Identifier tags
- Set the root and cookie-banner LICENSE files to the MIT text with a
  "MIT License" title line
- Switch the package.json license fields, Docker image label, and
  cookie-banner README to MIT
- Update docs and the genmodels header generator accordingly
- Normalize copyright lines to a single format
  (Copyright (c) <year(s)> Probo Inc <hello@probo.com>.): unify the
  hello@getprobo.com and hello@probo.inc emails to hello@probo.com and
  the comma-separated years to a hyphenated range

Genuine third-party references are intentionally left untouched: the
Lucide icon attributions (Lucide is ISC) and the trivy dependency
license allowlist.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-13 16:21:14 +02:00
Émile Ré
01bdccaf2a Release @probo/cookie-banner/v0.10.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-06-19 15:25:48 +02:00
Émile Ré
f98b73f073 Upgrade frontend toolchain to latest majors
Bump React 19.2, Relay 21, React Router 8, Vite 8 with
@vitejs/plugin-react 6, ESLint 10, GraphQL 17, TypeScript 6,
@types/node 24, and Tailwind 4.3 across the workspaces.

vite-plugin-react 6 (Vite 8) no longer runs Babel, so the Relay
tagged-template transform now runs through @rolldown/plugin-babel
in the console and trust Vite configs.

Relay 21 ships first-party types and enables the ambiguous-alias
check by default; disable that flag to preserve existing queries
and add explicit usePreloadedQuery type arguments where the new
types no longer infer the operation. TypeScript 6 deprecations and
stricter inference are addressed in tsconfigs and call sites.

Keep n8n-node on ESLint 9 and eslint-plugin-react on 7.37.5, the
newest releases compatible with their toolchains.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-19 13:51:17 +02:00
Émile Ré
f1fc2dc0e0 Show persistent for local-storage trackers
Local storage, IndexedDB, and cache storage have no expiry yet
persist until explicitly cleared, so a missing max-age should read
as "persistent", not "session" (the latter only fits cookies and
session storage, which end with the session or tab).

Thread the tracker type through humanizeSeconds (helpers) and
humanizeDuration (cookie-banner, with a localized persistent label)
and pass it at every console and banner call site. The consent
record query now selects trackerType so its duration column can
make the same distinction. This mirrors the Go HumanizedDuration
helper that already renders these types as persistent.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-19 10:46:44 +02:00
dependabot[bot]
853f2404a6 Bump esbuild from 0.27.7 to 0.28.1
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.27.7 to 0.28.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.7...v0.28.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-16 13:37:43 +02:00
Émile Ré
2fd3a4acfb Release @probo/cookie-banner/v0.9.3
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-12 19:04:35 +02:00
Émile Ré
92e829cbe5 Hold cookie banner scroll lock against smooth scrollers
The preference panel scroll lock relied on `overflow: hidden` on the
document element, which JS-driven smooth-scroll libraries such as Lenis
(used by Framer, e.g. getbluejay.ai) bypass entirely since they
intercept wheel/touch events and scroll programmatically.

Pin the body with `position: fixed` (saving and restoring the scroll
position) so the viewport has no scroll distance left to animate, even
for libraries that capture wheel events on the window before us. Keep
capture-phase wheel/touch cancellation as defense-in-depth for sites
that scroll an inner container, while still letting the panel's own
list scroll.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-12 15:07:09 +02:00
Ludovic Vielle
f713a8b1cb Release @probo/cookie-banner/v0.9.2
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-11 16:27:38 +02:00
Sacha Al Himdani
c7e2fc209f Use probo.com for bare and marketing domain references
Replace the bare `getprobo.com` domain and the `www.getprobo.com`
marketing host with `probo.com` / `www.probo.com` across the codebase.
Functional subdomains (app, console, notification, custom, test,
cookie-banner, compliance) keep their existing `getprobo.com` hosts,
and changelog entries are left untouched.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-11 13:46:10 +02:00
Émile Ré
c3936a7173 Release @probo/cookie-banner/v0.9.1
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 18:52:38 +02:00
Émile Ré
e3ccb76b03 Lock page scroll while cookie panel is open
The preference panel is a fixed, backdrop-less card whose only
scrollable region is the category list. Wheel events over the
header, footer, padding, or at the list edges chained to the host
page, so the page scrolled instead of the panel.

Treat the open panel as the modal it already claims to be: lock
document scroll while state is "panel" (compensating for scrollbar
width to avoid layout shift) and add overscroll-behavior: contain
to the list. Restore scroll on close and on disconnect so the page
is never left frozen.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:41 +02:00
Émile Ré
eb7878c618 Exclude SDK bundle URL from tracker attribution
The cookie/storage/resource detectors only skipped the API base-url
origin and the page origin when computing a tracker's initiator. When
the SDK is served from a CDN (e.g. jsDelivr) distinct from the API
host, its own wrapper frames sit atop every setItem/document.cookie
call stack and were never skipped, so getInitiatorURL returned the
bundle URL. Third-party and browser-extension writes were therefore
misattributed to cookie-banner.iife.js.

Capture the SDK's own served script URL once at load (currentScript
src, with an Error().stack fallback) and exclude it, at URL level, from
both the initiator stack-walk and the resource detector. URL-level
exclusion keeps other trackers served from the same shared CDN
detectable.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:41 +02:00
Sacha Al Himdani
9ac71f948f Update contact email to hello@probo.com
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-09 16:45:23 +02:00
Bryan Frimin
45e1797893 Release @probo/cookie-banner/v0.9.0
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-05 16:38:32 +02:00
Bryan Frimin
44da0e251a Add new supported language
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-05 15:44:05 +02:00
Ludovic Vielle
ab05e501ac Release @probo/cookie-banner/v0.8.0 2026-06-03 14:48:48 +02:00
Émile Ré
300e1b6688 Show tracker type in cookie banner trackers list
The themed banner and headless cookie list exposed only the tracker
name, description, and duration, so trackers that share a name but
differ in technology were indistinguishable to visitors.

Add the tracker type to the headless CookieItem model and render it as a
labeled "Type: <value>" detail under the tracker name, with an explicit
aria-label for assistive technology. Tracker type names are Web platform
API names (Cookie, Local storage, IndexedDB, ...), so they are kept
canonical while only the surrounding label is localized.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-02 16:21:01 +02:00
Bryan Frimin
1a397b13bc Release @probo/cookie-banner/v0.7.0
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-27 18:20:56 -07:00
Émile Ré
a447c62e19 Wire PostHog into the cookie-banner React example
Add a deferred PostHog wiring under examples/cookie-banner-react that
boots posthog.init() inside the probo-ready handler and derives
cookieless_mode and opt_out_capturing_by_default from the consent
snapshot for the category flagged with posthog_consent.

Driving the init args off the snapshot rather than consent_mode plugs
two cases the simpler "consent_mode alone" rule got wrong:

  * OPT_OUT regulation, returning rejector: init would have booted in
    on_reject + capture-on, fired a $pageview synchronously, and only
    then called opt_out_capturing(). That single captured pageview
    (and the posthog cookie) leaked on every page load.
  * OPT_IN regulation, returning acceptor: init would have forced
    "always" + opt-out, costing the visitor cookies and a one-tick
    capture delay even though they had already consented.

The snapshot already encodes the regulation default
(buildDefaultConsentData on the cookie-banner client returns true for
non-necessary categories under OPT_OUT and false under OPT_IN) and any
persisted answer from a prior visit, so a single boolean drives both
init args.

Re-export the public domain types (BannerConfig, Category, Regulation,
ConsentAction, ConsentRecord, CookieItem, VisitorConsent) from
@probo/cookie-banner so the example can type the probo-ready event
detail without duck-typing it.

Adopt the PUBLIC_ env prefix in Vite so the example reads the same env
var names (PUBLIC_COOKIE_BANNER_ID, PUBLIC_COOKIE_BANNER_API_BASE_URL,
PUBLIC_POSTHOG_API_KEY) already used on getprobo.com, and add a
matching .env.example.

Ignore *.tsbuildinfo at the repo root; TypeScript's incremental cache
is machine-local and does not belong in the tree.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-27 13:06:02 +02:00
Sacha Al Himdani
82b531718e Release @probo/cookie-banner/v0.6.0
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-26 18:38:07 +02:00
Émile Ré
bd5c527548 fix(cookie-banner): disconnect observer in load() error path
When fetchJSON failed, load() unconditionally overwrote this.observer
with a fresh MutationObserver, leaving any previous one observing the
DOM. Repeated load() calls that hit the catch branch leaked one
observer per call. Mirror the disconnect-first pattern already used in
activate().

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:54 +02:00
Émile Ré
d181fbe386 Add eager GCM bootstrap and remove PostHog integration
GCM now creates dataLayer and denies all consent types before the
config fetch, closing the gap where gtag could track freely during
the async config load. PostHog integration is removed because
script blocking via data-cookie-consent is the correct mechanism
for vendors that don't support a queue/replay pattern.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:53 +02:00
Émile Ré
8ff68798fb fix(cookie-banner): detect browser-extension stack frames
STACK_URL_RE only matched `https?://` URLs, so extension URLs
(`chrome-extension://`, `moz-extension://`, `safari-web-extension://`)
never reached the EXTENSION_URL_RE classifier. The `fromExtension`
flag was effectively always false and extension-originated cookie
and storage activity was misattributed as `script`.

Broaden STACK_URL_RE to also match the three extension schemes so
the existing classifier branch fires and sets `fromExtension = true`
before the URL parsing path runs.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:48 +02:00
Émile Ré
1c3ce56b48 Mark page-world extension writes with EXTENSION source
The previous cleanup deleted every isExtensionCaller() site, including
the one in cookie/storage detectors that did fire reliably for the
residual case: page-world extensions (MV3 main world, userscripts with
@grant none) whose stack contains a chrome-/moz-/safari-web-extension
frame at the synchronous write. Recover that signal for free by
returning fromExtension from getInitiatorURL (it already walks the
stack and discards extension frames via continue), and have the cookie
and storage detectors report source: "extension" instead of "script"
when the flag is set.

End-to-end plumbing reuses the existing source column: extend the
cookie_source Postgres enum with EXTENSION, add the CookieSourceExtension
constant with a doc block describing each bucket's actual semantics,
add the handler.go switch cases, expose EXTENSION on the GraphQL and
MCP CookieSource enums, and add the Extension option to the console
source filter.

Update bestSource in the pattern analysis worker so a glob merging
only extension-attributed exact patterns is no longer silently rolled
up to PRE_EXISTING. New precedence is SCRIPT > EXTENSION > PRE_EXISTING,
matching the upsert SQL's "page-script wins" rule and the asymmetric
signal strength of each bucket.

Out of scope: any behavioural use of EXTENSION (auto-exclusion,
denylist classification, dashboard surfacing) -- that belongs in the
follow-up backend denylist plan.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:48 +02:00
Émile Ré
b04454eaee Drop ineffective extension-caller wraps from cookie banner
The synchronous Storage/Document/Element/fetch/XHR/sendBeacon wraps
and the resource-detector attribution machinery relied on
isExtensionCaller() finding a chrome-extension:// frame in the JS
stack. For Chromium/Edge/Safari MV3 isolated-world content scripts
-- the dominant case -- those wraps live in the page realm while
the extension uses its own copy of every prototype we hook, so the
check never fires and the marking never runs.

Backend denylisting (planned) covers the same cases more cheaply,
retroactively, and across all tenants, so the elaborate frontend
plumbing no longer earns its complexity. Keep only the parts that
backend classification cannot replace: isExtensionContext() (SDK
loaded inside an extension page) and the http/https-only filter
in processResource (drops chrome-extension:// URLs surfaced via
PerformanceObserver). resource-detector.ts shrinks from ~920 to
~225 lines.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:48 +02:00
Émile Ré
5b9698494d Mark extension <link> writes before rel-based classification
handleAttributeMutation returned early when resourceTypeForElement
yielded null, which happens for `<link href>` whenever `rel` is not yet
"stylesheet". An extension that called `link.setAttribute("href", ...)`
before setting `rel` therefore skipped extensionElements and
extensionUrls marking; when the rel was filled in later and the browser
fetched the stylesheet, PerformanceObserver -- whose stack carries no
extension frame -- reported it as a page tracker.

Check isExtensionCaller() before classification and tag the element and
URL whenever the element type can ever initiate a load via the given
attribute, using a new couldLoadResource helper. The page-caller path
still uses the strict resourceTypeForElement so non-stylesheet `<link>`
writes do not generate spurious reports.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:48 +02:00
Émile Ré
2261ed0c8f Add synchronous DOM hooks to cookie banner resource detector
Wrap script/iframe/img/link/source IDL setters, setAttribute, the four
HTML-parsing entry points, fetch, XHR, and sendBeacon so extension stacks
are visible at the synchronous call site. Coordinate via an
extensionElements WeakSet and an extensionUrls Set so the existing
MutationObserver and PerformanceObserver paths drop the same resources.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:47 +02:00
Émile Ré
f1d320361e Filter extension activity across all cookie banner detectors
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:47 +02:00
Émile Ré
e2b8ee15e7 Track source on detected storage trackers
The cookie detector tags every detection with a source (script,
pre-existing, http) but the storage detector did not, so storage
rows always landed in detected_trackers with source NULL even though
the SDK already distinguishes wrapper writes from pre-load scans.

Plumb a "script"/"pre-existing" source from the storage detector
through the report endpoint into DetectedStorageItem, so the column
gets populated for localStorage, sessionStorage, indexedDB and
cacheStorage entries. No schema change is needed: detected_trackers
already accepts CookieSource values regardless of tracker_type, and
the existing row renders the badge as soon as it is non-null.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:47 +02:00
Émile Ré
f41dd9566b Release @probo/cookie-banner/v0.5.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-20 13:55:22 +04:00
Émile Ré
10e35c6afc Share consent
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-19 12:52:11 +04:00
Émile Ré
0f32037e70 Fix duplicate callback dispatch in ConsentManager.subscribe
When `_ready` was already true, `subscribe` called `cb` directly and
then `onReady` called it again, producing duplicate side effects.
Remove the redundant direct call since `onReady` already handles the
immediate-dispatch case.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-19 12:52:10 +04:00
Émile Ré
c17757565f Make ConsentManager referentially stable and add subscribe method
getAll() now returns a cached snapshot that is only replaced on state
changes, making it safe for useSyncExternalStore. The new subscribe()
method unifies onReady + onChange with immediate replay so React
consumers get a single subscription point.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-19 12:52:10 +04:00
Émile Ré
4078567c04 Add programmatic consent API to @probo/cookie-banner
Expose a ConsentManager singleton via `@probo/cookie-banner/consent`
(ESM) and `window.Probo.consent` (IIFE) so customers can check and
react to consent state in their bundled JavaScript code, solving the
problem of third-party SDKs initialized programmatically that cannot
be blocked via data-cookie-consent attributes.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-19 12:52:10 +04:00
Émile Ré
7b9090a9f1 Release @probo/cookie-banner/v0.4.1
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-13 15:25:57 +04:00
Émile Ré
e9d786c5d6 Skip consent API fetch for new visitors
When visitorId is null (no localStorage entry), the client now
skips both the cookie check and the /consents/:id fetch that
was always returning 404 for first-time visitors. The visitor ID
is created lazily on first consent action instead.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-13 12:49:13 +04:00
Émile Ré
a07ced47ae Release @probo/cookie-banner/v0.4.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 10:51:26 +04:00
Émile Ré
b81795e62e cookie-banner: attempt sendBeacon even when async flush is in-flight
The early return from flushSync() when this.flushing was true skipped
the synchronous sendBeacon path, dropping pending reports during page
unload. Move the flushing guard after the sendBeacon attempt so the
unload-safe transport is always tried; only the keepalive-fetch
fallback is gated on the mutex.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:57 +04:00
Émile Ré
f8fad78d0c cookie-banner: keep flush entries until delivery is confirmed
flushSync() previously deleted pending entries unconditionally
once a transport accepted the payload. That dropped data when
the page was restored from bfcache after visibilitychange:hidden,
and it allowed concurrent flushes to take overlapping batches.

Now items are only removed from `pending` once the transport
confirms delivery: synchronously for sendBeacon, in the keepalive
fetch's `.then` on an `ok` response. Failed fetches leave entries
queued so the next flush can retry, and a `flushing` guard prevents
overlapping in-flight batches.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:57 +04:00
Émile Ré
438ca439de cookie-banner: rename ThirdPartyDetector to ResourceDetector
The detector emits tracker_resource entries (scripts, iframes, images,
beacons, fonts, fetches, media, service workers); the "third-party"
qualifier was misleading because the same code path also fires for
service workers, which are always same-origin per browser security
rules. Rename the class and file to match what they actually produce.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:56 +04:00
Émile Ré
bec0254597 cookie-banner: share one ReportQueue across detectors
The three detectors (cookies, storage, resources) each duplicated the
same debounce, batch, retry, and NotFoundError plumbing and each fired
its own POST /report despite the server already accepting a unified
{cookies, storage, resources} payload. Collapse the three sender paths
into a single ReportQueue so a 2 s debounce window produces one request
instead of up to three, dedup is centralised behind type-namespaced
keys (c:/s:/r:) that cannot collide across detectors, and a tab-close
drain via sendBeacon (with keepalive fetch fallback) saves the last
debounce window of detections that previously vanished on unload.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:56 +04:00
Émile Ré
c8f130ed45 cookiebanner: address PR review feedback
- Detectors: keep batched entries in `pending` until the POST succeeds
  and guard against concurrent flushes, so transient network errors no
  longer silently drop detection reports.
- Worker: add stable tie-breakers to the merge-candidate sort so the
  greedy assignment produces deterministic groups across runs.
- Handler: skip resource entries with an empty URL (zero-value `uri.URI`
  when the `url` field is missing) before persisting them.
- Third-party detector: allow same-origin service worker scripts through
  `processResource` -- service workers are always same-origin by spec,
  so the previous filter made `wrapServiceWorker` unreachable.
- Resource row edit: bump the description cell `colSpan` to 3 so the
  edit row spans all five table columns.
- Resolver: handle `ErrSameResourceCategoryMove` explicitly so the no-op
  move returns a validation error instead of an internal one.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:56 +04:00
Émile Ré
caac9c76db cookiebanner: detect service workers and Cache Storage buckets
A registered service worker is a URL-shaped artifact (origin+path of
the worker script), so it goes in tracker_resources as a new
SERVICE_WORKER resource type. A Cache Storage bucket is an opaque
named string with no URL, so it goes in detected_trackers as a new
CACHE_STORAGE tracker type.

Frontend:
  - StorageDetector wraps caches.open() and enumerates caches.keys()
    on start to surface pre-existing buckets that pre-date the SDK
    load (service workers commonly populate caches eagerly on
    install).
  - ThirdPartyDetector wraps navigator.serviceWorker.register() and
    enumerates getRegistrations() on start.

Both wrappers degrade silently on insecure contexts where these APIs
are unavailable.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
2b3449de1a cookiebanner: broaden resource detection via PerformanceObserver
ThirdPartyDetector previously only saw <script src> and <iframe src>
because it scanned the DOM and watched mutations. Add a single
PerformanceObserver({type:'resource', buffered:true}) that picks up
everything the browser actually loaded:

  - tracking pixels (<img>, <picture>, srcset)
  - cross-origin stylesheets and web fonts
  - fetch / XHR / sendBeacon / ping calls (SDK call-homes)
  - video, audio, embed, object media

initiatorType is mapped to six new tracker_resource_type enum values
(IMAGE, STYLESHEET, FONT, BEACON, FETCH, MEDIA) and the existing
upsert path in tracker_resources picks them up unchanged.

Closes a real gap with headless cookie scanners: most SDKs phone home
via beacons after their script is gone, and the DOM scan never saw it.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
d17c8ba044 cookiebanner: capture script initiator URL on detected trackers
When third-party JS sets a cookie or writes to local/sessionStorage
inside a customer page, the SDK now walks the synchronous call stack
to find the first non-extension, non-Probo, non-first-party http(s)
URL. That origin+path is sent as initiator_url on the report payload,
persisted in a new nullable column on detected_trackers, and preserved
across upserts via COALESCE.

This unlocks per-vendor attribution for cookies and storage writes
without needing pattern name matching, so future categorisation logic
can simply look up the initiator URL in the existing tracker_resources
table and inherit that vendor's category.

GraphQL/MCP exposure is intentionally deferred -- the column is captured
now, surfaced later.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
04fdaed772 cookie-banner: clean up StorageDetector restore-on-stop
localStorage and sessionStorage share Storage.prototype, so there is
only one setItem to wrap and one to restore. The previous code kept a
never-assigned originalSessionSetItem field and a dead second if branch
that restored from a field already nulled out a few lines above.

Collapse the two fields into a single originalSetItem and drop the dead
branch. No behavioural change.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
c0d0221be1 Detect HTTP-header cookies via CookieStore change event
Progressive enhancement for Chromium browsers: listen on the
CookieStore change event to catch cookies set by Set-Cookie HTTP
response headers, which the document.cookie setter hook cannot see.
Adds a new "http" cookie source through the full stack.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:51 +04:00
Émile Ré
a4cb61366f Report full URL instead of bare origin for detected scripts and iframes
Strip query params and send origin+pathname so the backend can
distinguish resources served from the same domain but different paths
(e.g. gtm.js vs recaptcha/api.js on googletagmanager.com).

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:48 +04:00