Skip to content

Commit 8e59348

Browse files
committed
feat(core): implement AND/OR block conditions and dynamic overlay reasons
- Fix ghost overlay bug on extension reload or re-installation. - Enforce central `isCurrentlyBlocked` check before executing punishments to respect AND/OR rules. - Add Block Condition dropdown (Strict/Flexible) to settings dashboard with dynamic descriptions. - Add full i18n support (English/Arabic) for the new condition settings and overlay triggers. - Re-add readonly modifier to PlatformAdapter ID to ensure immutability.
1 parent 9fc0a04 commit 8e59348

13 files changed

Lines changed: 173 additions & 24 deletions

File tree

src/app/orchestrator.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,41 @@ export class AppOrchestrator {
4040
const pId = this.activeAdapter.id;
4141

4242
const safeBlock = async (reason: string = 'time') => {
43-
if (!this.isBlocked) {
44-
this.isBlocked = true;
45-
console.warn(`[Limitra] Initiating block logic. Reason: ${reason}`);
46-
if (this.sessionManager) {
47-
await this.sessionManager.blockSession();
43+
if (this.isBlocked) return;
44+
45+
const reallyBlocked = await this.storage.isCurrentlyBlocked(pId);
46+
if (!reallyBlocked) return;
47+
48+
this.isBlocked = true;
49+
50+
let finalReason = reason;
51+
52+
if (reason !== 'bypass') {
53+
const isLimitEnabled = await this.storage.getEnableLimit(pId);
54+
const limit = await this.storage.getLimit(pId);
55+
const count = await this.storage.getCount(pId);
56+
const limitReached = isLimitEnabled && limit > 0 && count >= limit;
57+
58+
const isTimeEnabled = await this.storage.getEnableTime(pId);
59+
const timeLimit = await this.storage.getTimeLimit(pId);
60+
const timeSpent = await this.storage.getTimeSpent(pId);
61+
const timeReached = isTimeEnabled && timeLimit > 0 && timeSpent >= timeLimit * 60 * 1000;
62+
63+
if (limitReached && timeReached) {
64+
finalReason = 'both';
65+
} else if (limitReached) {
66+
finalReason = 'count';
67+
} else if (timeReached) {
68+
finalReason = 'time';
4869
}
49-
this.activeAdapter.executePunishment();
50-
await showOverlay(reason);
5170
}
71+
72+
console.warn(`[Limitra] Initiating block logic. Reason: ${finalReason}`);
73+
if (this.sessionManager) {
74+
await this.sessionManager.blockSession();
75+
}
76+
this.activeAdapter.executePunishment();
77+
await showOverlay(finalReason);
5278
};
5379

5480
this.messenger = new Messenger(

src/content.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ if (!window.__LIMITRA_INJECTED__) {
2929
isBootstrapping = true;
3030

3131
try {
32+
const ghostOverlay = document.getElementById('limitra-overlay');
33+
if (ghostOverlay) {
34+
console.warn('[Limitra] Removing ghost overlay from previous installation.');
35+
ghostOverlay.remove();
36+
document.body.classList.remove('limitra-global-punishment');
37+
}
38+
3239
if (currentApp) {
3340
console.warn('[Limitra] Platform change detected. Destroying old instance...');
3441
currentApp.destroy();

src/core/background-orchestrator.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ export class BackgroundOrchestrator {
4040
const limitMs = timeLimitMins * 60 * 1000;
4141

4242
if (timeSpentMs >= limitMs) {
43-
const message: ExtensionMessage = { action: AppAction.BLOCK_NOW, platform };
44-
await this.tabManager.sendMessageToPattern(config.urlPatterns, message);
43+
const reallyBlocked = await this.storage.isCurrentlyBlocked(platform);
44+
if (reallyBlocked) {
45+
const message: ExtensionMessage = { action: AppAction.BLOCK_NOW, platform };
46+
await this.tabManager.sendMessageToPattern(config.urlPatterns, message);
47+
}
4548
} else {
4649
const timeRemainingMs = limitMs - timeSpentMs;
4750
let delayInMinutes = timeRemainingMs / 60000;

src/core/storage/index.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,41 @@ export class StorageFacade {
2727
if (bypassed) return true;
2828

2929
const isLimitEnabled = await this.settings.getEnableLimit(platform);
30+
const isTimeEnabled = await this.settings.getEnableTime(platform);
31+
32+
let limitReached = false;
33+
let hasLimitRule = false;
3034
if (isLimitEnabled) {
3135
const limit = await this.settings.getLimit(platform);
32-
const count = await this.stats.getCount(platform);
33-
if (limit > 0 && count >= limit) return true;
36+
if (limit > 0) {
37+
hasLimitRule = true;
38+
const count = await this.stats.getCount(platform);
39+
limitReached = count >= limit;
40+
}
3441
}
3542

36-
const isTimeEnabled = await this.settings.getEnableTime(platform);
43+
let timeReached = false;
44+
let hasTimeRule = false;
3745
if (isTimeEnabled) {
3846
const timeLimitMins = await this.settings.getTimeLimit(platform);
39-
const timeSpentMs = await this.stats.getTimeSpent(platform);
40-
if (timeLimitMins > 0 && timeSpentMs >= timeLimitMins * 60 * 1000) return true;
47+
if (timeLimitMins > 0) {
48+
hasTimeRule = true;
49+
const timeSpentMs = await this.stats.getTimeSpent(platform);
50+
timeReached = timeSpentMs >= timeLimitMins * 60 * 1000;
51+
}
4152
}
4253

43-
return false;
54+
if (!hasLimitRule && !hasTimeRule) return false;
55+
if (hasLimitRule && !hasTimeRule) return limitReached;
56+
if (!hasLimitRule && hasTimeRule) return timeReached;
57+
58+
// Both rules are active, check the user's preference
59+
const condition = await this.settings.getBlockCondition();
60+
if (condition === 'and') {
61+
return limitReached && timeReached;
62+
} else {
63+
return limitReached || timeReached;
64+
}
4465
}
4566

4667
public async isAnyPlatformBlocked(): Promise<boolean> {
@@ -148,6 +169,13 @@ export class StorageFacade {
148169
}
149170
}
150171

172+
async getBlockCondition() {
173+
return this.settings.getBlockCondition();
174+
}
175+
async setBlockCondition(condition: string) {
176+
return this.settings.setBlockCondition(condition);
177+
}
178+
151179
// Analytics
152180
async getCount(platform: PlatformId) {
153181
return this.stats.getCount(platform);

src/core/storage/settings.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
77
quoteTone: 'random',
88
trackingMode: 'strict',
99
blockDuration: 180, // 3 hours
10+
blockCondition: 'or',
1011
};
1112

1213
export class SettingsStorage {
@@ -18,6 +19,7 @@ export class SettingsStorage {
1819
await this.setQuoteTone(DEFAULT_GLOBAL_SETTINGS.quoteTone);
1920
await this.setTrackingMode(DEFAULT_GLOBAL_SETTINGS.trackingMode);
2021
await this.setBlockDuration(DEFAULT_GLOBAL_SETTINGS.blockDuration);
22+
await this.setBlockCondition(DEFAULT_GLOBAL_SETTINGS.blockCondition);
2123
}
2224

2325
// General settings
@@ -60,6 +62,16 @@ export class SettingsStorage {
6062
await this.driver.set('limitra_block_duration', minutes);
6163
}
6264

65+
async getBlockCondition(): Promise<string> {
66+
return (
67+
(await this.driver.get<string>('limitra_block_condition')) ??
68+
DEFAULT_GLOBAL_SETTINGS.blockCondition
69+
);
70+
}
71+
async setBlockCondition(condition: string): Promise<void> {
72+
await this.driver.set('limitra_block_condition', condition);
73+
}
74+
6375
// Platform-specific settings
6476
async getLimit(platform: PlatformId): Promise<number> {
6577
return (await this.driver.get<number>(`limitra_${platform}_limit`)) ?? 0;

src/i18n/locales/ar.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const ar: LocaleStrings = {
1515
reason_count: 'السبب: تجاوز عدد الفيديوهات',
1616
reason_time: 'السبب: انتهاء الوقت المسموح',
1717
reason_bypass: 'السبب: محاولة تحايل',
18+
reason_both: 'السبب: انتهاء الوقت وعدد الفيديوهات معاً',
1819
lockedBtn: 'مقفول!',
1920
sessionActiveBtn: 'جلسة نشطة!',
2021
selectPlatform: 'اختر المنصة...',
@@ -69,6 +70,12 @@ const ar: LocaleStrings = {
6970
duration6h: '6 ساعات',
7071
duration12h: '12 ساعة',
7172
duration24h: '24 ساعة',
73+
blockConditionLabel: 'شرط الحظر',
74+
descBlockCondition: 'اختر متى تظهر شاشة الحظر في حال تفعيل كلا العدادين.',
75+
condOr: 'صارم (أو)',
76+
condAnd: 'مرن (و)',
77+
descCondOr: 'تفعيل الحظر بمجرد انتهاء أي عداد (الوقت أو الفيديوهات).',
78+
descCondAnd: 'تفعيل الحظر فقط عند انتهاء كلا العدادين معاً.',
7279
},
7380
tones: {
7481
random: 'عشوائي',

src/i18n/locales/en.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const en: LocaleStrings = {
1515
reason_count: 'Triggered by: Video Counter',
1616
reason_time: 'Triggered by: Time Limit',
1717
reason_bypass: 'Triggered by: Anti-Bypass System',
18+
reason_both: 'Triggered by: Time & Video Limits',
1819
lockedBtn: 'Locked!',
1920
sessionActiveBtn: 'Session Active!',
2021
selectPlatform: 'Select Platform...',
@@ -71,6 +72,12 @@ const en: LocaleStrings = {
7172
duration6h: '6 Hours',
7273
duration12h: '12 Hours',
7374
duration24h: '24 Hours',
75+
blockConditionLabel: 'Block Condition',
76+
descBlockCondition: 'Choose when the block screen should appear if both limits are active.',
77+
condOr: 'Strict (OR)',
78+
condAnd: 'Flexible (AND)',
79+
descCondOr: 'Block as soon as ANY limit (Time or Videos) is reached.',
80+
descCondAnd: 'Block ONLY when BOTH limits (Time and Videos) are reached.',
7481
},
7582
tones: {
7683
random: 'Random',

src/i18n/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface LocaleStrings {
1515
reason_count: string;
1616
reason_time: string;
1717
reason_bypass: string;
18+
reason_both: string;
1819
lockedBtn: string;
1920
sessionActiveBtn: string;
2021
selectPlatform: string;
@@ -68,6 +69,12 @@ export interface LocaleStrings {
6869
duration6h: string;
6970
duration12h: string;
7071
duration24h: string;
72+
blockConditionLabel: string;
73+
descBlockCondition: string;
74+
condOr: string;
75+
condAnd: string;
76+
descCondOr: string;
77+
descCondAnd: string;
7178
};
7279
tones: {
7380
random: string;

src/platforms/youtube/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { StorageChangeListener } from '../../core/storage/driver';
44
import { StorageFacade } from '../../core/storage/index';
55

66
export class YouTubeAdapter implements PlatformAdapter {
7-
public id: PlatformId;
7+
public readonly id: PlatformId;
88
public name: string;
99

1010
// Dynamic threshold: 1.5s for Shorts, 10s for regular Watch videos

src/ui/overlay/controller.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ export async function showOverlay(reasonKey: string = 'time'): Promise<void> {
1818
const toneKey = await storage.getQuoteTone();
1919
const quoteText = i18n.getRandomQuote(toneKey);
2020

21-
const reasonText =
22-
reasonKey === 'count'
23-
? i18n.t.popup.reason_count
24-
: reasonKey === 'bypass'
25-
? i18n.t.popup.reason_bypass
26-
: i18n.t.popup.reason_time;
21+
const reasonMap: Record<string, string> = {
22+
count: i18n.t.popup.reason_count,
23+
time: i18n.t.popup.reason_time,
24+
bypass: i18n.t.popup.reason_bypass,
25+
both: i18n.t.popup.reason_both,
26+
};
27+
28+
const reasonText = reasonMap[reasonKey] || i18n.t.popup.reason_time;
2729

2830
const badgeText = i18n.t.overlay.badgeBlocked;
2931
const unlocksInText = i18n.t.overlay.unlocksIn;

0 commit comments

Comments
 (0)