Skip to content

Commit 7a6f365

Browse files
committed
Change expiry in consent storage to date string
1 parent 71aca88 commit 7a6f365

5 files changed

Lines changed: 118 additions & 33 deletions

File tree

src/app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { useZendeskIframeStyles } from '@/hooks/use-zendesk-iframe-styles';
1616
import { useZendeskClickHandlers } from '@/hooks/use-zendesk-click-handlers';
1717
import {
1818
CONSENT_STORAGE_KEY,
19-
DAY_IN_MILLISECONDS,
19+
CONSENT_STORAGE_TTL,
2020
EMBEDDED_TARGET_ELEMENT,
2121
} from '@/config/common';
2222
import { ZENDESK_SCRIPT_URL } from '@/config/zendesk';
@@ -47,7 +47,7 @@ export default function Home() {
4747
const onContinue = useCallback(() => {
4848
window.firePixelEvent?.('consent');
4949

50-
setStorageWithExpiry(CONSENT_STORAGE_KEY, true, 30 * DAY_IN_MILLISECONDS);
50+
setStorageWithExpiry(CONSENT_STORAGE_KEY, true, CONSENT_STORAGE_TTL);
5151

5252
dispatch({ type: 'SET_LOAD_WIDGET' });
5353
}, [dispatch]);

src/config/common.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const DECLINE_BUTTON_TEXT = 'Cancel';
77
export const CONSENT_BUTTON_TEXT = 'Continue to Chat';
88
export const START_CHAT_BUTTON_TEXT = 'Start Chat';
99
export const CONSENT_STORAGE_KEY = 'ddg_consent';
10+
export const CONSENT_STORAGE_TTL = 30;
1011

1112
/**
1213
* Determine if using custom domain (e.g., subscription-support.duckduckgo.com)
@@ -23,5 +24,3 @@ export const basePath =
2324
process.env.NODE_ENV === 'production' && !useCustomDomain
2425
? '/zendesk-subscription-support-messenger'
2526
: '';
26-
27-
export const DAY_IN_MILLISECONDS = 864e5;

src/tests/unit/get-storage-with-expiry.test.ts

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
import { test, expect } from '@playwright/test';
22
import { getStorageWithExpiry } from '@/utils/get-storage-with-expiry';
3+
import { formatDateString } from '@/utils/set-storage-with-expiry';
4+
5+
/**
6+
* Calculates an expiry date by adding days to today's date.
7+
*
8+
* @function calculateExpiryDate
9+
* @param {number} days - Number of days to add to today's date
10+
*
11+
* @returns {string} Expiry date string in YYYY-MM-DD format
12+
*/
13+
function calculateExpiryDate(days: number): string {
14+
const today = new Date();
15+
const expiryDate = new Date(today);
16+
expiryDate.setDate(today.getDate() + days);
17+
18+
return formatDateString(expiryDate);
19+
}
320

421
// localStorage mock
522
function createLocalStorageMock() {
@@ -64,7 +81,7 @@ test.describe('getStorageWithExpiry', () => {
6481
test('should return stored boolean value when item has not expired', () => {
6582
const item = {
6683
value: true,
67-
expiry: Date.now() + 86400000, // 24 hours from now
84+
expiry: calculateExpiryDate(1), // 1 day from now
6885
};
6986

7087
localStorage.setItem('test-key', JSON.stringify(item));
@@ -77,7 +94,7 @@ test.describe('getStorageWithExpiry', () => {
7794
test('should return false when stored value is false', () => {
7895
const item = {
7996
value: false,
80-
expiry: Date.now() + 86400000,
97+
expiry: calculateExpiryDate(1),
8198
};
8299

83100
localStorage.setItem('test-key', JSON.stringify(item));
@@ -88,9 +105,13 @@ test.describe('getStorageWithExpiry', () => {
88105
});
89106

90107
test('should return null when expired', () => {
108+
const today = new Date();
109+
const yesterday = new Date(today);
110+
yesterday.setDate(today.getDate() - 1);
111+
91112
const item = {
92113
value: true,
93-
expiry: Date.now() - 1000, // Expired 1 second ago
114+
expiry: formatDateString(yesterday), // Expired yesterday
94115
};
95116

96117
localStorage.setItem('expired-key', JSON.stringify(item));
@@ -123,7 +144,7 @@ test.describe('getStorageWithExpiry', () => {
123144

124145
test('should return null when structure is invalid - missing value', () => {
125146
const item = {
126-
expiry: Date.now() + 86400000,
147+
expiry: calculateExpiryDate(1),
127148
// value is missing
128149
};
129150

@@ -134,10 +155,10 @@ test.describe('getStorageWithExpiry', () => {
134155
expect(result).toBeNull();
135156
});
136157

137-
test('should return null when expiry is not a number', () => {
158+
test('should return null when expiry is not a string', () => {
138159
const item = {
139160
value: true,
140-
expiry: 'not-a-number',
161+
expiry: 1234567890, // Number instead of date string
141162
};
142163

143164
localStorage.setItem('invalid-expiry-type-key', JSON.stringify(item));
@@ -150,7 +171,7 @@ test.describe('getStorageWithExpiry', () => {
150171
test('should return null when value is not a boolean', () => {
151172
const item = {
152173
value: 'not-a-boolean',
153-
expiry: Date.now() + 86400000,
174+
expiry: calculateExpiryDate(1),
154175
};
155176

156177
localStorage.setItem('invalid-value-type-key', JSON.stringify(item));
@@ -175,4 +196,37 @@ test.describe('getStorageWithExpiry', () => {
175196

176197
expect(result).toBeNull();
177198
});
199+
200+
test('should return value when expiry date is today', () => {
201+
const today = formatDateString(new Date());
202+
203+
const item = {
204+
value: true,
205+
expiry: today,
206+
};
207+
208+
localStorage.setItem('today-expiry-key', JSON.stringify(item));
209+
210+
const result = getStorageWithExpiry('today-expiry-key');
211+
212+
// Should not be expired since expiry is at start of day (midnight)
213+
expect(result).toBe(true);
214+
});
215+
216+
test('should return null when expiry date is before today', () => {
217+
const today = new Date();
218+
const twoDaysAgo = new Date(today);
219+
twoDaysAgo.setDate(today.getDate() - 2);
220+
221+
const item = {
222+
value: true,
223+
expiry: formatDateString(twoDaysAgo),
224+
};
225+
226+
localStorage.setItem('past-expiry-key', JSON.stringify(item));
227+
228+
const result = getStorageWithExpiry('past-expiry-key');
229+
230+
expect(result).toBeNull();
231+
});
178232
});

src/utils/get-storage-with-expiry.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { isBrowser } from './is-browser';
2+
import { formatDateString } from './set-storage-with-expiry';
23

34
/**
45
* Retrieves a boolean value from localStorage, checking if it has expired.
6+
* Expired and invalid items are automatically removed from storage.
57
*
6-
* If the item has expired or doesn't exist, returns null and removes expired
7-
* items from storage. Handles invalid JSON by returning null. Returns null
8-
* when called in a server-side environment where localStorage is not available.
8+
* Expiry is compared as date strings (YYYY-MM-DD format), so items expire at
9+
* the start of the expiry date (midnight).
910
*
1011
* @function getStorageWithExpiry
1112
* @param {string} key - The localStorage key to retrieve
1213
*
13-
* @returns {boolean | null | undefined} The stored boolean value, or null if expired,
14-
* missing, invalid, or undefined when called server-side
14+
* @returns {boolean | null | undefined} Returns `undefined` when called
15+
* server-side, `null` if expired/missing/invalid, or the stored boolean value
16+
* if valid and not expired
1517
*/
1618
export function getStorageWithExpiry(key: string): boolean | null | undefined {
1719
if (!isBrowser()) {
@@ -31,7 +33,7 @@ export function getStorageWithExpiry(key: string): boolean | null | undefined {
3133
if (
3234
typeof item !== 'object' ||
3335
item === null ||
34-
typeof item.expiry !== 'number' ||
36+
typeof item.expiry !== 'string' ||
3537
typeof item.value !== 'boolean'
3638
) {
3739
// Invalid structure
@@ -40,8 +42,10 @@ export function getStorageWithExpiry(key: string): boolean | null | undefined {
4042
return null;
4143
}
4244

43-
// Expired
44-
if (Date.now() > item.expiry) {
45+
const today = formatDateString(new Date());
46+
const isExpired = today > item.expiry;
47+
48+
if (isExpired) {
4549
localStorage.removeItem(key);
4650

4751
return null;

src/utils/set-storage-with-expiry.ts

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,66 @@
11
import { isBrowser } from './is-browser';
22

33
/**
4-
* Stores a boolean value in localStorage with an expiration time.
4+
* Formats a date as YYYY-MM-DD string.
55
*
6-
* The value is stored as a JSON object containing the value and expiry timestamp.
7-
* When retrieved using {@link getStorageWithExpiry}, expired items are automatically
8-
* removed from storage. No-op when called in a server-side environment where
9-
* localStorage is not available.
6+
* @function formatDateString
7+
* @param {Date} date - The date to format
8+
*
9+
* @returns {string} Formatted date string in YYYY-MM-DD format
10+
*/
11+
export function formatDateString(date: Date): string {
12+
const year = date.getFullYear();
13+
const month = String(date.getMonth() + 1).padStart(2, '0');
14+
const day = String(date.getDate()).padStart(2, '0');
15+
16+
return `${year}-${month}-${day}`;
17+
}
18+
19+
/**
20+
* Calculates an expiry date by adding days to today's date.
21+
*
22+
* @function calculateExpiryDate
23+
* @param {number} days - Number of days to add to today's date
24+
*
25+
* @returns {string} Expiry date string in YYYY-MM-DD format
26+
*/
27+
function calculateExpiryDate(days: number): string {
28+
const today = new Date();
29+
const expiryDate = new Date(today);
30+
31+
expiryDate.setDate(today.getDate() + days);
32+
33+
return formatDateString(expiryDate);
34+
}
35+
36+
/**
37+
* Stores a boolean value in localStorage with an expiration date.
38+
*
39+
* The item is stored as a JSON object containing the value and expiry date
40+
* string in YYYY-MM-DD format. When retrieved using {@link
41+
* getStorageWithExpiry}, expired items are automatically removed from storage.
42+
* No-op when called in a server-side environment where localStorage is not
43+
* available.
1044
*
1145
* @function setStorageWithExpiry
1246
* @param {string} key - The localStorage key to store the value under
1347
* @param {boolean} value - The boolean value to store
14-
* @param {number} ttl - Time to live in milliseconds (e.g., 86400000 for 24 hours)
48+
* @param {number} days - Number of days from today until expiration
1549
*
1650
* @returns {void}
17-
*
18-
* @example
19-
* ```ts
20-
* // Store consent for 24 hours
21-
* setStorageWithExpiry('ddg_consent', true, 86400000);
22-
* ```
2351
*/
2452
export function setStorageWithExpiry(
2553
key: string,
2654
value: boolean,
27-
ttl: number,
55+
days: number,
2856
): void {
2957
if (!isBrowser()) {
3058
return;
3159
}
3260

3361
const item = {
3462
value,
35-
expiry: Date.now() + ttl,
63+
expiry: calculateExpiryDate(days),
3664
};
3765

3866
localStorage.setItem(key, JSON.stringify(item));

0 commit comments

Comments
 (0)