Skip to content

Commit 181ef84

Browse files
committed
feat(core): implement customizable block duration with smart session lock
- Add block duration global setting (15m to 24h) in the dashboard UI. - Replace dynamic reset intervals with an immutable `NEXT_RESET_KEY` in `SessionStorage`. - Implement a smart gatekeeper in `StorageFacade` to prevent time-travel bypass during active blocks. - Add localized i18n keys and values for the new duration options.
1 parent 34d838a commit 181ef84

8 files changed

Lines changed: 126 additions & 3 deletions

File tree

src/core/storage/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ class StorageFacade {
3737
return false;
3838
}
3939

40+
public async isAnyPlatformBlocked(): Promise<boolean> {
41+
const platforms: PlatformId[] = ['youtube_shorts', 'youtube_watch', 'instagram', 'global'];
42+
for (const p of platforms) {
43+
if (await this.isCurrentlyBlocked(p)) return true;
44+
}
45+
return false;
46+
}
47+
4048
async resetGlobalSettings() {
4149
return this.settings.resetGlobalSettings();
4250
}
@@ -111,6 +119,21 @@ class StorageFacade {
111119
return this.settings.setTrackingMode(mode);
112120
}
113121

122+
async getBlockDuration() {
123+
return this.settings.getBlockDuration();
124+
}
125+
126+
async setBlockDuration(minutes: number) {
127+
await this.settings.setBlockDuration(minutes);
128+
129+
const isAnyBlocked = await this.isAnyPlatformBlocked();
130+
if (!isAnyBlocked) {
131+
const lastReset = (await this.driver.get<number>('limitra_last_reset')) || Date.now();
132+
const newNextReset = lastReset + minutes * 60 * 1000;
133+
await this.driver.set('limitra_next_reset', newNextReset);
134+
}
135+
}
136+
114137
// Analytics
115138
async getCount(platform: PlatformId) {
116139
return this.stats.getCount(platform);

src/core/storage/session.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
11
import { StorageDriver } from './driver';
22
import { StatsStorage } from './stats';
33
import { PlatformId } from '../../types';
4+
import { DEFAULT_GLOBAL_SETTINGS } from './settings';
45

56
const LAST_RESET_KEY = 'limitra_last_reset';
6-
const RESET_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
7+
const NEXT_RESET_KEY = 'limitra_next_reset';
78

89
export class SessionStorage {
910
constructor(
1011
private driver: StorageDriver,
1112
private stats: StatsStorage,
1213
) {}
1314

15+
private async getDurationMs(): Promise<number> {
16+
const mins =
17+
(await this.driver.get<number>('limitra_block_duration')) ??
18+
DEFAULT_GLOBAL_SETTINGS.blockDuration;
19+
return mins * 60 * 1000;
20+
}
21+
1422
async ensureSession(platforms: PlatformId[]): Promise<boolean> {
1523
const lastReset = await this.driver.get<number>(LAST_RESET_KEY);
24+
let nextReset = await this.driver.get<number>(NEXT_RESET_KEY);
1625
const now = Date.now();
17-
if (!lastReset || now - lastReset >= RESET_INTERVAL_MS) {
26+
27+
if (!nextReset && lastReset) {
28+
const intervalMs = await this.getDurationMs();
29+
nextReset = lastReset + intervalMs;
30+
await this.driver.set(NEXT_RESET_KEY, nextReset);
31+
}
32+
33+
if (!lastReset || !nextReset || now >= nextReset) {
1834
for (const platform of platforms) {
1935
await this.stats.resetCount(platform);
2036
await this.stats.setTimeSpent(platform, 0);
2137
}
38+
39+
const intervalMs = await this.getDurationMs();
2240
await this.driver.set<number>(LAST_RESET_KEY, now);
41+
await this.driver.set<number>(NEXT_RESET_KEY, now + intervalMs);
42+
2343
return true;
2444
}
2545
return false;
@@ -41,7 +61,11 @@ export class SessionStorage {
4161
}
4262

4363
async getNextResetTime(): Promise<number> {
64+
const nextReset = await this.driver.get<number>(NEXT_RESET_KEY);
65+
if (nextReset) return nextReset;
66+
4467
const lastReset = (await this.driver.get<number>(LAST_RESET_KEY)) || Date.now();
45-
return lastReset + RESET_INTERVAL_MS;
68+
const intervalMs = await this.getDurationMs();
69+
return lastReset + intervalMs;
4670
}
4771
}

src/core/storage/settings.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
66
theme: 'auto',
77
quoteTone: 'random',
88
trackingMode: 'strict',
9+
blockDuration: 180, // 3 hours
910
};
1011

1112
export class SettingsStorage {
@@ -16,6 +17,7 @@ export class SettingsStorage {
1617
await this.setTheme(DEFAULT_GLOBAL_SETTINGS.theme);
1718
await this.setQuoteTone(DEFAULT_GLOBAL_SETTINGS.quoteTone);
1819
await this.setTrackingMode(DEFAULT_GLOBAL_SETTINGS.trackingMode);
20+
await this.setBlockDuration(DEFAULT_GLOBAL_SETTINGS.blockDuration);
1921
}
2022

2123
// General settings
@@ -47,6 +49,17 @@ export class SettingsStorage {
4749
await this.driver.set('limitra_tracking_mode', mode);
4850
}
4951

52+
async getBlockDuration(): Promise<number> {
53+
return (
54+
(await this.driver.get<number>('limitra_block_duration')) ??
55+
DEFAULT_GLOBAL_SETTINGS.blockDuration
56+
);
57+
}
58+
59+
async setBlockDuration(minutes: number): Promise<void> {
60+
await this.driver.set('limitra_block_duration', minutes);
61+
}
62+
5063
// Platform-specific settings
5164
async getLimit(platform: PlatformId): Promise<number> {
5265
return (await this.driver.get<number>(`limitra_${platform}_limit`)) ?? 0;

src/i18n/locales/ar.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ const ar: LocaleStrings = {
6060
cancelBtn: 'إلغاء',
6161
resettingBtn: 'جاري الاستعادة...',
6262
modalBadgeWarning: 'تحذير',
63+
blockDurationLabel: 'مدة الحظر',
64+
descBlockDuration: 'حدد المدة التي ستبقى فيها المنصة محظورة بعد تجاوز الحد المسموح.',
65+
duration15m: '15 دقيقة',
66+
duration1h: 'ساعة واحدة',
67+
duration3h: '3 ساعات',
68+
duration6h: '6 ساعات',
69+
duration12h: '12 ساعة',
70+
duration24h: '24 ساعة',
6371
},
6472
tones: {
6573
random: 'عشوائي',

src/i18n/locales/en.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ const en: LocaleStrings = {
6262
cancelBtn: 'Cancel',
6363
resettingBtn: 'Resetting...',
6464
modalBadgeWarning: 'WARNING',
65+
blockDurationLabel: 'Block Duration',
66+
descBlockDuration: 'Select how long a platform remains blocked after reaching the limit.',
67+
duration15m: '15 Minutes',
68+
duration1h: '1 Hour',
69+
duration3h: '3 Hours',
70+
duration6h: '6 Hours',
71+
duration12h: '12 Hours',
72+
duration24h: '24 Hours',
6573
},
6674
tones: {
6775
random: 'Random',

src/i18n/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ export interface LocaleStrings {
5959
cancelBtn: string;
6060
resettingBtn: string;
6161
modalBadgeWarning: string;
62+
blockDurationLabel: string;
63+
descBlockDuration: string;
64+
duration15m: string;
65+
duration1h: string;
66+
duration3h: string;
67+
duration6h: string;
68+
duration12h: string;
69+
duration24h: string;
6270
};
6371
tones: {
6472
random: string;

src/ui/settings/index.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,25 @@ <h5 class="setting-title" id="lbl-dash-tracking">Tracking Mode</h5>
134134
</select>
135135
</div>
136136
</div>
137+
138+
<div class="setting-item">
139+
<div class="setting-info">
140+
<h5 class="setting-title" id="lbl-dash-duration">Block Duration</h5>
141+
<p class="setting-desc" id="desc-dash-duration">
142+
Select how long a platform remains blocked after reaching the limit.
143+
</p>
144+
</div>
145+
<div class="setting-control">
146+
<select id="dash-duration" class="brutal-select">
147+
<option value="15" id="opt-dur-15m">15 Minutes</option>
148+
<option value="60" id="opt-dur-1h">1 Hour</option>
149+
<option value="180" id="opt-dur-3h">3 Hours</option>
150+
<option value="360" id="opt-dur-6h">6 Hours</option>
151+
<option value="720" id="opt-dur-12h">12 Hours</option>
152+
<option value="1440" id="opt-dur-24h">24 Hours</option>
153+
</select>
154+
</div>
155+
</div>
137156
</div>
138157
</div>
139158

src/ui/settings/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ function updateUITexts() {
6262
'group-customization': t.dashboard.groupCustomization,
6363
'group-security': t.dashboard.groupSecurity,
6464

65+
// Block Duration
66+
'lbl-dash-duration': t.dashboard.blockDurationLabel,
67+
'desc-dash-duration': t.dashboard.descBlockDuration,
68+
'opt-dur-15m': t.dashboard.duration15m,
69+
'opt-dur-1h': t.dashboard.duration1h,
70+
'opt-dur-3h': t.dashboard.duration3h,
71+
'opt-dur-6h': t.dashboard.duration6h,
72+
'opt-dur-12h': t.dashboard.duration12h,
73+
'opt-dur-24h': t.dashboard.duration24h,
74+
6575
// Data
6676
'group-data': t.dashboard.groupData,
6777
'lbl-dash-reset': t.dashboard.resetTitle,
@@ -169,6 +179,13 @@ async function init() {
169179
await storage.setQuoteTone(target.value);
170180
});
171181

182+
const dashDurationInput = document.getElementById('dash-duration') as HTMLSelectElement;
183+
dashDurationInput.value = String(await storage.getBlockDuration());
184+
dashDurationInput.addEventListener('change', async (event) => {
185+
const target = event.target as HTMLSelectElement;
186+
await storage.setBlockDuration(Number(target.value));
187+
});
188+
172189
const btnResetSettings = document.getElementById('btn-reset-settings') as HTMLButtonElement;
173190
if (btnResetSettings) {
174191
btnResetSettings.addEventListener('click', async () => {
@@ -220,6 +237,9 @@ async function init() {
220237
updateTrackingDescription(currentTracking);
221238
});
222239
}
240+
if (changes['limitra_block_duration']) {
241+
dashDurationInput.value = String(changes['limitra_block_duration'].newValue);
242+
}
223243
if (changes['limitra_quote_tone']) {
224244
dashToneInput.value = String(changes['limitra_quote_tone'].newValue);
225245
}

0 commit comments

Comments
 (0)