setForm({ ...form, name: e.target.value })}
+ value={name}
+ onChange={e => setName(e.target.value)}
placeholder={__("Cookie name")}
/>
- setForm({ ...form, duration: e.target.value })}
- placeholder={__("e.g. 1 year")}
+
setForm({ ...form, description: e.target.value })}
+ value={description}
+ onChange={e => setDescription(e.target.value)}
placeholder={__("Description")}
/>
onSave(form)}
+ onClick={handleSave}
disabled={isUpdating}
>
{__("Save")}
diff --git a/packages/cookie-banner/src/client.ts b/packages/cookie-banner/src/client.ts
index 25cbb93d9..ab5e9936b 100644
--- a/packages/cookie-banner/src/client.ts
+++ b/packages/cookie-banner/src/client.ts
@@ -31,7 +31,7 @@ import { getOrCreateVisitorId } from "./visitor";
export interface CookieItem {
name: string;
- duration: string;
+ max_age_seconds: number | null;
description: string;
}
diff --git a/packages/cookie-banner/src/components/cookie-list.ts b/packages/cookie-banner/src/components/cookie-list.ts
index 6be2f8980..c1df41848 100644
--- a/packages/cookie-banner/src/components/cookie-list.ts
+++ b/packages/cookie-banner/src/components/cookie-list.ts
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
import type { CookieItem } from "../client";
+import { humanizeDuration } from "../cookie-utils";
import { getCookieDetailLabels } from "../i18n";
import { ProboElement } from "./base";
import type { ProboCategory } from "./category";
@@ -43,24 +44,28 @@ export class ProboCookieList extends ProboElement {
const cookies = this.category.cookies;
for (const cookie of cookies) {
- this.stampCookie(cookie, labels);
+ this.stampCookie(cookie, labels, lang);
}
}
- private stampCookie(cookie: CookieItem, labels: Record): void {
+ private stampCookie(cookie: CookieItem, labels: Record, lang: string): void {
if (!this.template) return;
+ const duration = cookie.max_age_seconds != null
+ ? humanizeDuration(cookie.max_age_seconds, lang)
+ : humanizeDuration(0, lang);
+
const wrapper = document.createElement("probo-cookie");
wrapper.setAttribute("name", cookie.name);
const clone = this.template.content.cloneNode(true) as DocumentFragment;
this.fillSlots(clone, {
name: cookie.name,
- duration: cookie.duration,
+ duration,
description: cookie.description,
});
this.fillLabels(clone, labels, {
description: cookie.description,
- duration: cookie.duration,
+ duration,
});
wrapper.appendChild(clone);
diff --git a/packages/cookie-banner/src/cookie-utils.ts b/packages/cookie-banner/src/cookie-utils.ts
index af60a82dd..5a1ab0d63 100644
--- a/packages/cookie-banner/src/cookie-utils.ts
+++ b/packages/cookie-banner/src/cookie-utils.ts
@@ -98,7 +98,7 @@ const DURATION_UNITS: [number, string, number][] = [
[60, "duration_minute", 15],
];
-function humanizeDuration(seconds: number, lang?: string): string {
+export function humanizeDuration(seconds: number, lang?: string): string {
const texts = getDurationTexts(lang);
if (seconds <= 0) return texts.duration_session;
@@ -133,17 +133,15 @@ export function parseCookieName(raw: string): string {
return raw.substring(0, eqIdx).trim();
}
-export function parseDuration(raw: string, lang?: string): string {
- const texts = getDurationTexts(lang);
- const session = texts.duration_session;
+export function parseMaxAgeSeconds(raw: string): number | null {
const parts = raw.split(";").map((s) => s.trim());
for (const part of parts) {
const lower = part.toLowerCase();
if (lower.startsWith("max-age=")) {
const val = parseInt(part.substring(8), 10);
- if (isNaN(val) || val <= 0) return session;
- return humanizeDuration(val, lang);
+ if (isNaN(val) || val <= 0) return null;
+ return val;
}
}
@@ -152,16 +150,16 @@ export function parseDuration(raw: string, lang?: string): string {
if (lower.startsWith("expires=")) {
const dateStr = part.substring(8);
const expires = new Date(dateStr);
- if (isNaN(expires.getTime())) return session;
+ if (isNaN(expires.getTime())) return null;
const deltaSeconds = Math.round(
(expires.getTime() - Date.now()) / 1000,
);
- if (deltaSeconds <= 0) return session;
- return humanizeDuration(deltaSeconds, lang);
+ if (deltaSeconds <= 0) return null;
+ return deltaSeconds;
}
}
- return session;
+ return null;
}
export function isDeletion(raw: string): boolean {
diff --git a/packages/cookie-banner/src/detector.ts b/packages/cookie-banner/src/detector.ts
index 3b1bcf2fc..b706ea20f 100644
--- a/packages/cookie-banner/src/detector.ts
+++ b/packages/cookie-banner/src/detector.ts
@@ -12,12 +12,12 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-import { isDeletion, parseCookieName, parseDuration } from "./cookie-utils";
+import { isDeletion, parseCookieName, parseMaxAgeSeconds } from "./cookie-utils";
import { fetchJSON } from "./http";
interface DetectedCookieEntry {
name: string;
- duration: string;
+ max_age_seconds: number | null;
source: "script" | "pre-existing";
}
@@ -93,10 +93,10 @@ export class CookieDetector {
const name = parseCookieName(raw);
if (!name || this.knownNames.has(name) || this.reported.has(name)) return;
- const duration = parseDuration(raw);
+ const maxAgeSeconds = parseMaxAgeSeconds(raw);
this.reported.add(name);
- this.pending.set(name, { name, duration, source: "script" });
+ this.pending.set(name, { name, max_age_seconds: maxAgeSeconds, source: "script" });
this.scheduleFlush();
}
@@ -110,7 +110,7 @@ export class CookieDetector {
continue;
}
this.reported.add(name);
- this.pending.set(name, { name, duration: "session", source: "pre-existing" });
+ this.pending.set(name, { name, max_age_seconds: null, source: "pre-existing" });
}
if (this.pending.size > 0) {
diff --git a/packages/helpers/src/duration.ts b/packages/helpers/src/duration.ts
new file mode 100644
index 000000000..969e04eea
--- /dev/null
+++ b/packages/helpers/src/duration.ts
@@ -0,0 +1,33 @@
+// Copyright (c) 2026 Probo Inc .
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+const UNITS: [number, string, string][] = [
+ [365 * 24 * 3600, "year", "years"],
+ [30 * 24 * 3600, "month", "months"],
+ [7 * 24 * 3600, "week", "weeks"],
+ [24 * 3600, "day", "days"],
+ [3600, "hour", "hours"],
+ [60, "minute", "minutes"],
+];
+
+export function humanizeSeconds(seconds: number | null): string {
+ if (seconds === null || seconds <= 0) return "session";
+ for (const [unit, singular, plural] of UNITS) {
+ if (seconds >= unit && seconds % unit === 0) {
+ const count = seconds / unit;
+ return `${count} ${count === 1 ? singular : plural}`;
+ }
+ }
+ return `${seconds} seconds`;
+}
diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts
index 27d284177..4dcb6e939 100644
--- a/packages/helpers/src/index.ts
+++ b/packages/helpers/src/index.ts
@@ -109,6 +109,7 @@ export {
formatDuration,
parseDate,
} from "./date";
+export { humanizeSeconds } from "./duration";
export { getTrustCenterUrl } from "./trustCenter";
export { detectSocialName } from "./socialUrl";
export { formatError, type GraphQLError } from "./error";
diff --git a/pkg/cookiebanner/service.go b/pkg/cookiebanner/service.go
index 0fbaa1067..6613a3610 100644
--- a/pkg/cookiebanner/service.go
+++ b/pkg/cookiebanner/service.go
@@ -81,15 +81,15 @@ type (
CreateCookieRequest struct {
CookieCategoryID gid.GID
Name string
- Duration string
+ MaxAgeSeconds *int
Description string
}
UpdateCookieRequest struct {
- CookieID gid.GID
- Name *string
- Duration *string
- Description *string
+ CookieID gid.GID
+ Name *string
+ MaxAgeSeconds **int
+ Description *string
}
ReorderCookieCategoryRequest struct {
@@ -107,14 +107,14 @@ type (
Pattern string
MatchType coredata.CookiePatternMatchType
DisplayName string
- Duration string
+ MaxAgeSeconds *int
Description string
}
UpdateCookiePatternRequest struct {
CookiePatternID gid.GID
DisplayName *string
- Duration *string
+ MaxAgeSeconds **int
Description *string
}
@@ -150,9 +150,9 @@ type (
}
DetectedCookie struct {
- Name string
- Duration string
- Source coredata.CookieSource
+ Name string
+ MaxAgeSeconds *int
+ Source coredata.CookieSource
}
ReportDetectedCookiesRequest struct {
@@ -244,7 +244,6 @@ func (r *CreateCookieRequest) Validate() error {
v.Check(r.CookieCategoryID, "cookie_category_id", validator.Required(), validator.GID(coredata.CookieCategoryEntityType))
v.Check(r.Name, "name", validator.Required(), validator.SafeTextNoNewLine(255))
- v.Check(r.Duration, "duration", validator.Required(), validator.SafeTextNoNewLine(255))
v.Check(r.Description, "description", validator.SafeText(1000))
return v.Error()
@@ -255,7 +254,6 @@ func (r *UpdateCookieRequest) Validate() error {
v.Check(r.CookieID, "cookie_id", validator.Required(), validator.GID(coredata.CookieEntityType))
v.Check(r.Name, "name", validator.SafeTextNoNewLine(255))
- v.Check(r.Duration, "duration", validator.SafeTextNoNewLine(255))
v.Check(r.Description, "description", validator.SafeText(1000))
return v.Error()
@@ -286,7 +284,6 @@ func (r *CreateCookiePatternRequest) Validate() error {
}(),
))
v.Check(r.DisplayName, "display_name", validator.Required(), validator.SafeTextNoNewLine(255))
- v.Check(r.Duration, "duration", validator.Required(), validator.SafeTextNoNewLine(255))
v.Check(r.Description, "description", validator.SafeText(1000))
return v.Error()
@@ -297,7 +294,6 @@ func (r *UpdateCookiePatternRequest) Validate() error {
v.Check(r.CookiePatternID, "cookie_pattern_id", validator.Required(), validator.GID(coredata.CookiePatternEntityType))
v.Check(r.DisplayName, "display_name", validator.SafeTextNoNewLine(255))
- v.Check(r.Duration, "duration", validator.SafeTextNoNewLine(255))
v.Check(r.Description, "description", validator.SafeText(1000))
return v.Error()
@@ -411,9 +407,9 @@ func buildSnapshot(
cookiesByCategory[p.CookieCategoryID] = append(
cookiesByCategory[p.CookieCategoryID],
coredata.CookieItem{
- Name: p.DisplayName,
- Duration: p.Duration,
- Description: p.Description,
+ Name: p.DisplayName,
+ MaxAgeSeconds: p.MaxAgeSeconds,
+ Description: p.Description,
},
)
}
@@ -662,6 +658,7 @@ func (s *Service) CreateCookieBanner(
slugToGID[dc.Slug] = category.ID
if dc.Kind == coredata.CookieCategoryKindNecessary {
+ consentMaxAge := req.ConsentExpiryDays * 86400
consentPattern := &coredata.CookiePattern{
ID: gid.New(scope.GetTenantID(), coredata.CookiePatternEntityType),
OrganizationID: banner.OrganizationID,
@@ -670,7 +667,7 @@ func (s *Service) CreateCookieBanner(
Pattern: "probo_consent",
MatchType: coredata.CookiePatternMatchTypeExact,
DisplayName: "probo_consent",
- Duration: fmt.Sprintf("%d days", req.ConsentExpiryDays),
+ MaxAgeSeconds: &consentMaxAge,
Description: "Stores your cookie consent preferences for this website.",
Source: coredata.CookieSourceScript,
CreatedAt: now,
@@ -686,7 +683,7 @@ func (s *Service) CreateCookieBanner(
CookieBannerID: banner.ID,
CookiePatternID: consentPattern.ID,
Name: "probo_consent",
- Duration: fmt.Sprintf("%d days", req.ConsentExpiryDays),
+ MaxAgeSeconds: &consentMaxAge,
Source: coredata.CookieSourceScript,
CreatedAt: now,
UpdatedAt: now,
@@ -1292,7 +1289,7 @@ func (s *Service) CreateCookie(
Pattern: req.Name,
MatchType: coredata.CookiePatternMatchTypeExact,
DisplayName: req.Name,
- Duration: req.Duration,
+ MaxAgeSeconds: req.MaxAgeSeconds,
Description: req.Description,
Source: coredata.CookieSourceScript,
CreatedAt: now,
@@ -1312,7 +1309,7 @@ func (s *Service) CreateCookie(
CookieBannerID: category.CookieBannerID,
CookiePatternID: pattern.ID,
Name: req.Name,
- Duration: req.Duration,
+ MaxAgeSeconds: req.MaxAgeSeconds,
Source: coredata.CookieSourceScript,
CreatedAt: now,
UpdatedAt: now,
@@ -1425,7 +1422,7 @@ func (s *Service) CreateCookiePattern(
Pattern: req.Pattern,
MatchType: req.MatchType,
DisplayName: req.DisplayName,
- Duration: req.Duration,
+ MaxAgeSeconds: req.MaxAgeSeconds,
Description: req.Description,
Source: coredata.CookieSourceScript,
CreatedAt: now,
@@ -1477,8 +1474,8 @@ func (s *Service) UpdateCookiePattern(
if req.DisplayName != nil {
pattern.DisplayName = *req.DisplayName
}
- if req.Duration != nil {
- pattern.Duration = *req.Duration
+ if req.MaxAgeSeconds != nil {
+ pattern.MaxAgeSeconds = *req.MaxAgeSeconds
}
if req.Description != nil {
pattern.Description = *req.Description
@@ -1702,8 +1699,8 @@ func (s *Service) UpdateCookie(
return fmt.Errorf("cannot load cookie: %w", err)
}
- if req.Duration != nil {
- cookie.Duration = *req.Duration
+ if req.MaxAgeSeconds != nil {
+ cookie.MaxAgeSeconds = *req.MaxAgeSeconds
}
cookie.UpdatedAt = time.Now()
@@ -2637,7 +2634,7 @@ func (s *Service) ReportDetectedCookies(
Pattern: dc.Name,
MatchType: coredata.CookiePatternMatchTypeExact,
DisplayName: dc.Name,
- Duration: dc.Duration,
+ MaxAgeSeconds: dc.MaxAgeSeconds,
Description: "",
Source: dc.Source,
CreatedAt: now,
@@ -2659,7 +2656,7 @@ func (s *Service) ReportDetectedCookies(
CookieBannerID: banner.ID,
CookiePatternID: patternID,
Name: dc.Name,
- Duration: dc.Duration,
+ MaxAgeSeconds: dc.MaxAgeSeconds,
Source: dc.Source,
CreatedAt: now,
UpdatedAt: now,
diff --git a/pkg/cookiebanner/worker.go b/pkg/cookiebanner/worker.go
index a638a97af..0c07b3d30 100644
--- a/pkg/cookiebanner/worker.go
+++ b/pkg/cookiebanner/worker.go
@@ -94,7 +94,7 @@ func (h *patternAnalysisHandler) Process(ctx context.Context, task coredata.Cook
merged := false
for prefix, group := range mergeGroups {
- duration := mostCommonDuration(group)
+ maxAge := mostCommonMaxAge(group)
source := bestSource(group)
prefixPattern := &coredata.CookiePattern{
@@ -105,7 +105,7 @@ func (h *patternAnalysisHandler) Process(ctx context.Context, task coredata.Cook
Pattern: prefix,
MatchType: coredata.CookiePatternMatchTypePrefix,
DisplayName: prefix + "*",
- Duration: duration,
+ MaxAgeSeconds: maxAge,
Description: "",
Source: source,
CreatedAt: time.Now(),
@@ -229,23 +229,35 @@ func bestSource(patterns []*coredata.CookiePattern) coredata.CookieSource {
return coredata.CookieSourcePreExisting
}
-func mostCommonDuration(patterns []*coredata.CookiePattern) string {
- counts := make(map[string]int)
+func mostCommonMaxAge(patterns []*coredata.CookiePattern) *int {
+ type key struct {
+ valid bool
+ val int
+ }
+ counts := make(map[key]int)
for _, p := range patterns {
- counts[p.Duration]++
+ k := key{}
+ if p.MaxAgeSeconds != nil {
+ k = key{valid: true, val: *p.MaxAgeSeconds}
+ }
+ counts[k]++
}
type entry struct {
- duration string
- count int
+ k key
+ count int
}
entries := make([]entry, 0, len(counts))
- for d, c := range counts {
- entries = append(entries, entry{d, c})
+ for k, c := range counts {
+ entries = append(entries, entry{k, c})
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].count > entries[j].count
})
- return entries[0].duration
+ if !entries[0].k.valid {
+ return nil
+ }
+ v := entries[0].k.val
+ return &v
}
diff --git a/pkg/coredata/cookie.go b/pkg/coredata/cookie.go
index 07bc74822..485aaf88e 100644
--- a/pkg/coredata/cookie.go
+++ b/pkg/coredata/cookie.go
@@ -35,7 +35,7 @@ type (
CookieBannerID gid.GID `db:"cookie_banner_id"`
CookiePatternID gid.GID `db:"cookie_pattern_id"`
Name string `db:"name"`
- Duration string `db:"duration"`
+ MaxAgeSeconds *int `db:"max_age_seconds"`
Source CookieSource `db:"source"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
@@ -81,7 +81,7 @@ SELECT
cookie_banner_id,
cookie_pattern_id,
name,
- duration,
+ max_age_seconds,
source,
created_at,
updated_at
@@ -130,7 +130,7 @@ SELECT
cookie_banner_id,
cookie_pattern_id,
name,
- duration,
+ max_age_seconds,
source,
created_at,
updated_at
@@ -208,7 +208,7 @@ SELECT
cookie_banner_id,
cookie_pattern_id,
name,
- duration,
+ max_age_seconds,
source,
created_at,
updated_at
@@ -289,7 +289,7 @@ SELECT
cookie_banner_id,
cookie_pattern_id,
name,
- duration,
+ max_age_seconds,
source,
created_at,
updated_at
@@ -335,7 +335,7 @@ INSERT INTO cookies (
cookie_banner_id,
cookie_pattern_id,
name,
- duration,
+ max_age_seconds,
source,
created_at,
updated_at
@@ -346,7 +346,7 @@ INSERT INTO cookies (
@cookie_banner_id,
@cookie_pattern_id,
@name,
- @duration,
+ @max_age_seconds,
@source,
@created_at,
@updated_at
@@ -360,7 +360,7 @@ INSERT INTO cookies (
"cookie_banner_id": c.CookieBannerID,
"cookie_pattern_id": c.CookiePatternID,
"name": c.Name,
- "duration": c.Duration,
+ "max_age_seconds": c.MaxAgeSeconds,
"source": c.Source,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
@@ -392,7 +392,7 @@ INSERT INTO cookies (
cookie_banner_id,
cookie_pattern_id,
name,
- duration,
+ max_age_seconds,
source,
created_at,
updated_at
@@ -403,7 +403,7 @@ INSERT INTO cookies (
@cookie_banner_id,
@cookie_pattern_id,
@name,
- @duration,
+ @max_age_seconds,
@source,
@created_at,
@updated_at
@@ -420,7 +420,7 @@ ON CONFLICT (cookie_banner_id, name) DO UPDATE
"cookie_banner_id": c.CookieBannerID,
"cookie_pattern_id": c.CookiePatternID,
"name": c.Name,
- "duration": c.Duration,
+ "max_age_seconds": c.MaxAgeSeconds,
"source": c.Source,
"source_script": CookieSourceScript,
"created_at": c.CreatedAt,
@@ -444,7 +444,7 @@ func (c *Cookie) Update(
UPDATE cookies
SET
cookie_pattern_id = @cookie_pattern_id,
- duration = @duration,
+ max_age_seconds = @max_age_seconds,
updated_at = @updated_at
WHERE
%s
@@ -456,7 +456,7 @@ WHERE
args := pgx.StrictNamedArgs{
"id": c.ID,
"cookie_pattern_id": c.CookiePatternID,
- "duration": c.Duration,
+ "max_age_seconds": c.MaxAgeSeconds,
"updated_at": c.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())
diff --git a/pkg/coredata/cookie_category.go b/pkg/coredata/cookie_category.go
index 4939e1f25..8023b3c13 100644
--- a/pkg/coredata/cookie_category.go
+++ b/pkg/coredata/cookie_category.go
@@ -31,9 +31,9 @@ import (
type (
CookieItem struct {
- Name string `json:"name"`
- Duration string `json:"duration"`
- Description string `json:"description"`
+ Name string `json:"name"`
+ MaxAgeSeconds *int `json:"max_age_seconds"`
+ Description string `json:"description"`
}
CookieItems []CookieItem
diff --git a/pkg/coredata/cookie_pattern.go b/pkg/coredata/cookie_pattern.go
index c9658b2b0..44dab3059 100644
--- a/pkg/coredata/cookie_pattern.go
+++ b/pkg/coredata/cookie_pattern.go
@@ -37,7 +37,7 @@ type (
Pattern string `db:"pattern"`
MatchType CookiePatternMatchType `db:"match_type"`
DisplayName string `db:"display_name"`
- Duration string `db:"duration"`
+ MaxAgeSeconds *int `db:"max_age_seconds"`
Description string `db:"description"`
Source CookieSource `db:"source"`
CreatedAt time.Time `db:"created_at"`
@@ -86,7 +86,7 @@ SELECT
pattern,
match_type,
display_name,
- duration,
+ max_age_seconds,
description,
source,
created_at,
@@ -138,7 +138,7 @@ SELECT
pattern,
match_type,
display_name,
- duration,
+ max_age_seconds,
description,
source,
created_at,
@@ -201,7 +201,7 @@ SELECT
pattern,
match_type,
display_name,
- duration,
+ max_age_seconds,
description,
source,
created_at,
@@ -281,7 +281,7 @@ SELECT
pattern,
match_type,
display_name,
- duration,
+ max_age_seconds,
description,
source,
created_at,
@@ -330,7 +330,7 @@ INSERT INTO cookie_patterns (
pattern,
match_type,
display_name,
- duration,
+ max_age_seconds,
description,
source,
created_at,
@@ -344,7 +344,7 @@ INSERT INTO cookie_patterns (
@pattern,
@match_type,
@display_name,
- @duration,
+ @max_age_seconds,
@description,
@source,
@created_at,
@@ -361,7 +361,7 @@ INSERT INTO cookie_patterns (
"pattern": cp.Pattern,
"match_type": cp.MatchType,
"display_name": cp.DisplayName,
- "duration": cp.Duration,
+ "max_age_seconds": cp.MaxAgeSeconds,
"description": cp.Description,
"source": cp.Source,
"created_at": cp.CreatedAt,
@@ -396,7 +396,7 @@ INSERT INTO cookie_patterns (
pattern,
match_type,
display_name,
- duration,
+ max_age_seconds,
description,
source,
created_at,
@@ -410,7 +410,7 @@ INSERT INTO cookie_patterns (
@pattern,
@match_type,
@display_name,
- @duration,
+ @max_age_seconds,
@description,
@source,
@created_at,
@@ -428,7 +428,7 @@ ON CONFLICT (cookie_banner_id, pattern) DO NOTHING
"pattern": cp.Pattern,
"match_type": cp.MatchType,
"display_name": cp.DisplayName,
- "duration": cp.Duration,
+ "max_age_seconds": cp.MaxAgeSeconds,
"description": cp.Description,
"source": cp.Source,
"created_at": cp.CreatedAt,
@@ -453,7 +453,7 @@ UPDATE cookie_patterns
SET
cookie_category_id = @cookie_category_id,
display_name = @display_name,
- duration = @duration,
+ max_age_seconds = @max_age_seconds,
description = @description,
updated_at = @updated_at
WHERE
@@ -467,7 +467,7 @@ WHERE
"id": cp.ID,
"cookie_category_id": cp.CookieCategoryID,
"display_name": cp.DisplayName,
- "duration": cp.Duration,
+ "max_age_seconds": cp.MaxAgeSeconds,
"description": cp.Description,
"updated_at": cp.UpdatedAt,
}
diff --git a/pkg/coredata/migrations/20260429T135250Z.sql b/pkg/coredata/migrations/20260429T135250Z.sql
new file mode 100644
index 000000000..f29cd2caf
--- /dev/null
+++ b/pkg/coredata/migrations/20260429T135250Z.sql
@@ -0,0 +1,19 @@
+-- Copyright (c) 2026 Probo Inc .
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted, provided that the above
+-- copyright notice and this permission notice appear in all copies.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+-- PERFORMANCE OF THIS SOFTWARE.
+
+ALTER TABLE cookies DROP COLUMN duration;
+ALTER TABLE cookies ADD COLUMN max_age_seconds INTEGER;
+
+ALTER TABLE cookie_patterns DROP COLUMN duration;
+ALTER TABLE cookie_patterns ADD COLUMN max_age_seconds INTEGER;
diff --git a/pkg/server/api/console/v1/cookie_banner_resolvers.go b/pkg/server/api/console/v1/cookie_banner_resolvers.go
index 5e84ca293..8661a5024 100644
--- a/pkg/server/api/console/v1/cookie_banner_resolvers.go
+++ b/pkg/server/api/console/v1/cookie_banner_resolvers.go
@@ -224,9 +224,9 @@ func (r *cookieBannerVersionResolver) Categories(ctx context.Context, obj *types
cookies := make([]*types.CookieBannerVersionCookie, len(cat.Cookies))
for j, c := range cat.Cookies {
cookies[j] = &types.CookieBannerVersionCookie{
- Name: c.Name,
- Duration: c.Duration,
- Description: c.Description,
+ Name: c.Name,
+ MaxAgeSeconds: c.MaxAgeSeconds,
+ Description: c.Description,
}
}
@@ -754,7 +754,7 @@ func (r *mutationResolver) CreateCookiePattern(ctx context.Context, input types.
Pattern: input.Pattern,
MatchType: input.MatchType,
DisplayName: input.DisplayName,
- Duration: input.Duration,
+ MaxAgeSeconds: input.MaxAgeSeconds,
Description: input.Description,
},
)
@@ -799,7 +799,7 @@ func (r *mutationResolver) UpdateCookiePattern(ctx context.Context, input types.
cookiebanner.UpdateCookiePatternRequest{
CookiePatternID: input.CookiePatternID,
DisplayName: input.DisplayName,
- Duration: input.Duration,
+ MaxAgeSeconds: gqlutils.UnwrapOmittable(input.MaxAgeSeconds),
Description: input.Description,
},
)
diff --git a/pkg/server/api/console/v1/graphql/cookie_banner.graphql b/pkg/server/api/console/v1/graphql/cookie_banner.graphql
index 561b9605a..cf7396a03 100644
--- a/pkg/server/api/console/v1/graphql/cookie_banner.graphql
+++ b/pkg/server/api/console/v1/graphql/cookie_banner.graphql
@@ -200,7 +200,7 @@ type CookiePattern implements Node {
pattern: String!
matchType: CookiePatternMatchType!
displayName: String!
- duration: String!
+ maxAgeSeconds: Int
description: String!
source: CookieSource!
cookieCount: Int! @goField(forceResolver: true)
@@ -243,7 +243,7 @@ type CookieBannerVersionCategory {
type CookieBannerVersionCookie {
name: String!
- duration: String!
+ maxAgeSeconds: Int
description: String!
}
@@ -434,14 +434,14 @@ input CreateCookiePatternInput {
pattern: String!
matchType: CookiePatternMatchType!
displayName: String!
- duration: String!
+ maxAgeSeconds: Int
description: String!
}
input UpdateCookiePatternInput {
cookiePatternId: ID!
displayName: String
- duration: String
+ maxAgeSeconds: Int @goField(omittable: true)
description: String
}
diff --git a/pkg/server/api/console/v1/types/cookie_pattern.go b/pkg/server/api/console/v1/types/cookie_pattern.go
index 109d648b1..3dced9d2f 100644
--- a/pkg/server/api/console/v1/types/cookie_pattern.go
+++ b/pkg/server/api/console/v1/types/cookie_pattern.go
@@ -69,13 +69,13 @@ func NewCookiePattern(cp *coredata.CookiePattern) *CookiePattern {
ID: cp.CookieBannerID,
},
},
- Pattern: cp.Pattern,
- MatchType: cp.MatchType,
- DisplayName: cp.DisplayName,
- Duration: cp.Duration,
- Description: cp.Description,
- Source: cp.Source,
- CreatedAt: cp.CreatedAt,
- UpdatedAt: cp.UpdatedAt,
+ Pattern: cp.Pattern,
+ MatchType: cp.MatchType,
+ DisplayName: cp.DisplayName,
+ MaxAgeSeconds: cp.MaxAgeSeconds,
+ Description: cp.Description,
+ Source: cp.Source,
+ CreatedAt: cp.CreatedAt,
+ UpdatedAt: cp.UpdatedAt,
}
}
diff --git a/pkg/server/api/cookiebanner/v1/handler.go b/pkg/server/api/cookiebanner/v1/handler.go
index 4cd3bb8b9..70af8a2da 100644
--- a/pkg/server/api/cookiebanner/v1/handler.go
+++ b/pkg/server/api/cookiebanner/v1/handler.go
@@ -187,9 +187,9 @@ func (h *Handler) handlePostConsent(w http.ResponseWriter, r *http.Request) {
}
type detectedCookieEntry struct {
- Name string `json:"name"`
- Duration string `json:"duration"`
- Source string `json:"source"`
+ Name string `json:"name"`
+ MaxAgeSeconds *int `json:"max_age_seconds"`
+ Source string `json:"source"`
}
type reportDetectedCookiesBody struct {
@@ -239,9 +239,9 @@ func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Req
detected = append(
detected,
cookiebanner.DetectedCookie{
- Name: name,
- Duration: strings.TrimSpace(c.Duration),
- Source: source,
+ Name: name,
+ MaxAgeSeconds: c.MaxAgeSeconds,
+ Source: source,
},
)
}