Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions src/components/ListWithTime.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import { convertHHMMToSeconds, convertSecondsToHHMM } from '../utils/converter';
import { Restriction } from '../entity/restriction';
import { BaseTimeList } from '../entity/baseTimeList';
import { Notifications } from '../entity/notification';
import { requestNotificationPermission, checkNotificationPermission } from '../functions/useNotification';

const { t } = useI18n();

Expand Down Expand Up @@ -119,7 +120,7 @@ onMounted(async () => {
}
});

function addToList() {
async function addToList() {
const existingItem = list.value?.find(x =>
isDomainEquals(extractHostname(x.domain), extractHostname(newWebsiteForList.value!)),
);
Expand All @@ -129,12 +130,36 @@ function addToList() {
type: 'error',
});
} else {
const newLimit = new Restriction(
extractHostname(newWebsiteForList.value!),
time.value.hours,
time.value.minutes,
);
list.value?.push(newLimit);
// For notifications list, check/request permission
if (props.type === ListWithTime.Notifications) {
const hasPermission = await checkNotificationPermission();
if (!hasPermission) {
const granted = await requestNotificationPermission();
if (!granted) {
notification.notify({
title: 'Notification permission required',
text: 'Please enable notifications in your browser settings to add website notifications.',
type: 'error',
});
return;
}
}

const newNotification = new Notifications(
extractHostname(newWebsiteForList.value!),
time.value.hours,
time.value.minutes,
);
list.value?.push(newNotification);
} else {
const newLimit = new Restriction(
extractHostname(newWebsiteForList.value!),
time.value.hours,
time.value.minutes,
);
list.value?.push(newLimit);
}

save(list.value);
newWebsiteForList.value = '';
}
Expand Down
91 changes: 90 additions & 1 deletion src/components/Notifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,30 @@
@click="saveNotificationMessage()"
/>
</div>
<div class="settings-item">
<label class="setting-header">{{ t('testNotification.message', 'Test Notifications') }}</label>
<p class="description">
{{ t('testNotification.description', 'Click to test if notifications are working properly') }}
</p>
<input
type="button"
class="d-inline-block small-btn width"
value="Test Daily Notification"
@click="testDailyNotification()"
/>
<input
type="button"
class="d-inline-block small-btn width ml-10"
value="Test Website Notification"
@click="testWebsiteNotification()"
/>
<input
type="button"
class="d-inline-block small-btn width ml-10"
value="Test Browser Native"
@click="testNativeNotification()"
/>
</div>
</div>
</template>

Expand All @@ -77,6 +101,7 @@ import PromoClearYouTube from '../components/PromoClearYouTube.vue';
import { ListWithTime } from '../utils/enums';
import Browser from 'webextension-polyfill';
import { Messages } from '../utils/messages';
import { requestNotificationPermission, checkNotificationPermission, useNotification, NotificationType } from '../functions/useNotification';

const { t } = useI18n();

Expand Down Expand Up @@ -123,12 +148,76 @@ async function handleDate(modelData: Time) {
}

async function onChange(storageParam: StorageParams, target: any) {
if (target != null) await save(storageParam, target.checked);
if (target != null) {
// If enabling daily notifications, request permission first
if (storageParam === StorageParams.DAILY_NOTIFICATION && target.checked) {
const hasPermission = await checkNotificationPermission();
if (!hasPermission) {
const granted = await requestNotificationPermission();
if (!granted) {
// Permission denied, don't enable the setting
showDailyNotification.value = false;
alert('Notification permission is required to enable notifications. Please enable notifications in your browser settings.');
return;
}
}
}
await save(storageParam, target.checked);
}
}

async function save(storageParam: StorageParams, value: any) {
if (value != undefined) await settingsStorage.saveValue(storageParam, value);
}

async function testDailyNotification() {
console.log('[testDailyNotification] Testing daily notification');
const title = 'Test Daily Notification';
const message = 'This is a test of the daily summary notification system. If you see this, notifications are working!';

const result = await useNotification(NotificationType.DailySummaryNotification, title, message);
console.log('[testDailyNotification] Test notification result:', result);
}

async function testWebsiteNotification() {
console.log('[testWebsiteNotification] Testing website notification');
const title = 'Test Website Notification';
const message = 'This is a test of the website notification system. If you see this, website notifications are working!';

const result = await useNotification(NotificationType.WebSiteNotification, title, message);
console.log('[testWebsiteNotification] Test notification result:', result);
}

async function testNativeNotification() {
console.log('[testNativeNotification] Testing native browser notification');

try {
if ('Notification' in window) {
const permission = await Notification.requestPermission();
console.log('[testNativeNotification] Permission:', permission);

if (permission === 'granted') {
const notification = new Notification('Native Test Notification', {
body: 'This is a native browser notification test. If you see this, your browser notifications work!',
icon: '/src/assets/icons/48x48.png'
});

notification.onclick = () => {
console.log('[testNativeNotification] Notification clicked');
notification.close();
};

console.log('[testNativeNotification] Native notification created:', notification);
} else {
console.log('[testNativeNotification] Permission denied');
}
} else {
console.log('[testNativeNotification] Notifications not supported');
}
} catch (error) {
console.error('[testNativeNotification] Error:', error);
}
}
</script>

<style scoped>
Expand Down
82 changes: 79 additions & 3 deletions src/functions/useNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,89 @@ export async function useNotification(
title: string,
message: string,
): Promise<string> {
// Check if we have notification permissions
const hasPermission = await checkNotificationPermission();
console.log('[useNotification] Permission check result:', hasPermission);

if (!hasPermission) {
console.warn('[useNotification] Notification permission not granted');
return '';
}

await Browser.notifications.clear(notificationType);
console.log('[useNotification] Cleared existing notifications');

await new Promise(res => setTimeout(res, 3 * SECOND));
return Browser.notifications.create(notificationType, {

const notificationOptions = {
type: 'basic',
title: title,
message: message,
iconUrl: Browser.runtime.getURL('128x128.png'),
isClickable: false,
});
isClickable: true, // Make it clickable so it's more visible
requireInteraction: true, // Keep it visible until user interacts
};

console.log('[useNotification] Creating notification with options:', notificationOptions);

try {
const notificationId = await Browser.notifications.create(notificationType, notificationOptions);
console.log('[useNotification] Notification created successfully with ID:', notificationId);

// Add event listeners to track notification events
Browser.notifications.onShown?.addListener((id) => {
if (id === notificationId) {
console.log('[useNotification] Notification shown:', id);
}
});

Browser.notifications.onClosed?.addListener((id, byUser) => {
if (id === notificationId) {
console.log('[useNotification] Notification closed:', id, 'by user:', byUser);
}
});

return notificationId;
} catch (error) {
console.error('[useNotification] Error creating notification:', error);
return '';
}
}

export async function checkNotificationPermission(): Promise<boolean> {
try {
// Check if we have the notifications permission
const permission = await Browser.permissions.contains({ permissions: ['notifications'] });
console.log('[checkNotificationPermission] Extension has notifications permission:', permission);

// Also check if the browser allows notifications at all
if (typeof Notification !== 'undefined') {
console.log('[checkNotificationPermission] Browser Notification API available');
console.log('[checkNotificationPermission] Browser notification permission:', Notification.permission);

// If browser permission is 'default', request it
if (Notification.permission === 'default') {
console.log('[checkNotificationPermission] Requesting browser notification permission...');
const browserPermission = await Notification.requestPermission();
console.log('[checkNotificationPermission] Browser permission result:', browserPermission);
}
} else {
console.warn('[checkNotificationPermission] Browser Notification API not available');
}

return permission;
} catch (error) {
console.error('[checkNotificationPermission] Error checking notification permission:', error);
return false;
}
}

export async function requestNotificationPermission(): Promise<boolean> {
try {
const granted = await Browser.permissions.request({ permissions: ['notifications'] });
return granted;
} catch (error) {
console.error('Error requesting notification permission:', error);
return false;
}
}
10 changes: 8 additions & 2 deletions src/functions/useNotificationList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ export function useNotificationList() {
if (item != undefined) {
const date = tab.days.find(x => x.date == todayLocalDate());
if (date != undefined) {
if (date.summary != 0 && (date.summary == item.time || date.summary % item.time == 0)) {
// Check if we've exceeded the threshold and haven't shown notification for this interval yet
const currentInterval = Math.floor(date.summary / item.time);
const previousSummary = date.summary - 1; // Previous second
const previousInterval = Math.floor(previousSummary / item.time);

// Show notification when we cross into a new interval (threshold exceeded)
if (date.summary >= item.time && currentInterval > previousInterval) {
log(
`Time for notification: website ${url} time ${item.time} summary time ${date.summary}`,
`Notification threshold exceeded: website ${url} threshold ${item.time}s, current time ${date.summary}s, interval ${currentInterval}`,
);
return true;
}
Expand Down
18 changes: 16 additions & 2 deletions src/jobs/daily-summary-notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@ import { NotificationType, useNotification } from '../functions/useNotification'
import { getMessagesFromLocale } from '../plugins/i18n';

export async function dailySummaryNotification() {
console.log('[dailySummaryNotification] Starting daily summary notification check');

const showDailyNotification = (await Settings.getInstance().getSetting(
StorageParams.DAILY_NOTIFICATION,
)) as boolean;

console.log('[dailySummaryNotification] Daily notification enabled:', showDailyNotification);

if (showDailyNotification) {
const data = await useWebUsageSummaryForDay();
if (data == null) return;
console.log('[dailySummaryNotification] Usage data:', data);

if (data == null) {
console.log('[dailySummaryNotification] No usage data available, skipping notification');
return;
}

const title = `${
getMessagesFromLocale()['todayUsageTime']['message']
Expand All @@ -29,6 +38,11 @@ export async function dailySummaryNotification() {
messageWithMostVisitedWebsite,
].join('\n');

return await useNotification(NotificationType.DailySummaryNotification, title, message);
console.log('[dailySummaryNotification] Sending notification:', { title, message });
const result = await useNotification(NotificationType.DailySummaryNotification, title, message);
console.log('[dailySummaryNotification] Notification result:', result);
return result;
} else {
console.log('[dailySummaryNotification] Daily notifications disabled, skipping');
}
}
11 changes: 10 additions & 1 deletion src/jobs/sheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ async function rescheduleJobs(): Promise<void> {
const dailySummaryNotificationTime = (await Settings.getInstance().getSetting(
StorageParams.DAILY_SUMMARY_NOTIFICATION_TIME,
)) as number;
console.log('[schedule-jobs] Daily notification time setting (seconds):', dailySummaryNotificationTime);

await Browser.alarms.clear(JobId.DailySummaryNotification);
const nextTime = getNextTimeOfDay(dailySummaryNotificationTime * SECOND);
log(`[schedule-jobs] ${JobId.DailySummaryNotification} start time ${new Date(nextTime)}`);
console.log(`[schedule-jobs] ${JobId.DailySummaryNotification} next trigger time: ${new Date(nextTime)}`);
console.log(`[schedule-jobs] Current time: ${new Date()}`);
console.log(`[schedule-jobs] Time until next alarm: ${Math.round((nextTime - Date.now()) / 1000 / 60)} minutes`);

Browser.alarms.create(JobId.DailySummaryNotification, {
when: nextTime,
periodInMinutes: DAY_MINUTES,
Expand All @@ -53,6 +58,10 @@ async function rescheduleJobs(): Promise<void> {
when: startOfTomorrow().getTime(),
periodInMinutes: DAY_MINUTES,
});

// Verify the alarm was created
const createdAlarm = await Browser.alarms.get(JobId.DailySummaryNotification);
console.log('[schedule-jobs] Created alarm:', createdAlarm);
}

async function createAlarmIfMissing(
Expand Down
9 changes: 7 additions & 2 deletions src/tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,17 @@ async function mainTracker(
if (tab.favicon == '' && activeTab.favIconUrl != undefined)
tab.setFavicon(activeTab.favIconUrl);

if (await useNotificationList().isNeedToShowNotification(activeDomain, tab)) {
const shouldShowNotification = await useNotificationList().isNeedToShowNotification(activeDomain, tab);
console.log(`[tracker] Website notification check for ${activeDomain}:`, shouldShowNotification);

if (shouldShowNotification) {
const message = (await Settings.getInstance().getSetting(
StorageParams.NOTIFICATION_MESSAGE,
)) as string;
const title = `${activeDomain} notification`;
await useNotification(NotificationType.WebSiteNotification, title, message);
console.log(`[tracker] Sending website notification for ${activeDomain}:`, { title, message });
const result = await useNotification(NotificationType.WebSiteNotification, title, message);
console.log(`[tracker] Website notification result for ${activeDomain}:`, result);
}

tab.incSummaryTime();
Expand Down