Skip to content
Merged
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
29 changes: 18 additions & 11 deletions src/background/controller/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '../../shared/db/types';
import { getSettings } from '../../shared/preferences';
import { getIsoDate, getMinutesInMs } from '../../shared/utils/dates-helper';
import { isInvalidUrl } from '../../shared/utils/url';
import { isInvalidUrl, isDomainAllowedByUser } from '../../shared/utils/url';
import { setActiveTabRecord } from '../tables/state';
import { ActiveTimelineRecordDao, createNewActiveRecord } from './active';
import { updateTimeOnBadge } from './badge';
Expand Down Expand Up @@ -49,17 +49,19 @@ const handleAndCollectDomainIgnoredInfo = async (
currentTimelineRecord: TimelineRecord | null,
focusedActiveTab: chrome.tabs.Tab | null,
): Promise<boolean> => {
const isDomainIgnored = preferences.ignoredHosts.includes(
currentTimelineRecord?.hostname ?? '',
);
if (!isDomainIgnored) {

const hostname = currentTimelineRecord?.hostname ?? '';

const isDomainAllowed = isDomainAllowedByUser(hostname, preferences.allowedHosts, preferences.ignoredHosts)

if (isDomainAllowed) {
await handlePageLimitExceed(
preferences.limits,
focusedActiveTab,
currentTimelineRecord,
);
}
return isDomainIgnored;
return isDomainAllowed;
};

const getTabStatus = (
Expand Down Expand Up @@ -104,7 +106,7 @@ export const handleStateChange = async (
await activeTimeline.set(currentTimelineRecord);
}

const isDomainIgnored = await handleAndCollectDomainIgnoredInfo(
const isDomainAllowed = await handleAndCollectDomainIgnoredInfo(
preferences,
currentTimelineRecord,
focusedActiveTab,
Expand All @@ -113,23 +115,23 @@ export const handleStateChange = async (
await updateTimeOnBadge(
focusedActiveTab,
currentTimelineRecord,
preferences.displayTimeOnBadge && !isDomainIgnored,
preferences.displayTimeOnBadge && isDomainAllowed,
);

if (
isNotFocused ||
isImpossiblyLongEvent ||
isInvalidUrl(focusedActiveTab?.url)
) {
await commitTabActivity(await activeTimeline.get());
await commitTabActivity(await activeTimeline.get(), preferences);
return;
}

if (
focusedActiveTab &&
currentTimelineRecord?.url !== focusedActiveTab?.url
) {
await commitTabActivity(await activeTimeline.get());
await commitTabActivity(await activeTimeline.get(), preferences);
await createNewActiveRecord(
timestamp,
focusedActiveTab,
Expand All @@ -138,11 +140,16 @@ export const handleStateChange = async (
}
};

async function commitTabActivity(currentTimelineRecord: TimelineRecord | null) {
async function commitTabActivity(currentTimelineRecord: TimelineRecord | null, preferences: Preferences) {
if (!currentTimelineRecord) {
return;
}

const isDomainAllowed = isDomainAllowedByUser(currentTimelineRecord.hostname, preferences.allowedHosts, preferences.ignoredHosts);
if (!isDomainAllowed) {
return;
}

const currentIsoDate = getIsoDate(new Date());

await saveTimelineRecord(currentTimelineRecord, currentIsoDate);
Expand Down
31 changes: 24 additions & 7 deletions src/background/services/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,24 @@ const emitSuccessSyncStats = async (
await callback({
result: 'ok',
});
const lastUpdateDateTime = preferences.lastUpdateStats?.Datetime;

let lastUpdateDateTime = DateTime.fromJSDate(new Date());
if (preferences.lastUpdateStats?.Datetime) {
lastUpdateDateTime = DateTime.fromISO(preferences.lastUpdateStats?.Datetime);
}

await chrome.action.setTitle({
title:
"Codealike time tracker. You're authenticated to Codealike. Last bundle of stats sent " +
lastUpdateDateTime ?? DateTime.now() + '.',
lastUpdateDateTime.toLocaleString(DateTime.DATETIME_SHORT) + '.',
});
await chrome.action.setBadgeText({
text: '',
});

await setSettings({
lastUpdateStats: {
Datetime: DateTime.fromJSDate(new Date()),
Datetime: new Date().toJSON(),
Status: 'OK',
},
});
Expand All @@ -58,18 +64,23 @@ const emitFailedSyncStats = async (
await callback({
result: 'failed',
});
const lastUpdateDateTime = preferences.lastUpdateStats?.Datetime;

let lastUpdateDateTime = DateTime.fromJSDate(new Date());
if (preferences.lastUpdateStats?.Datetime) {
lastUpdateDateTime = DateTime.fromISO(preferences.lastUpdateStats?.Datetime);
}
await chrome.action.setTitle({
title:
'Codealike time tracker. An error happened trying to send Web Activity ' +
lastUpdateDateTime ?? DateTime.now() + '.',
lastUpdateDateTime.toLocaleString(DateTime.DATETIME_SHORT) + '.',
});
await chrome.action.setBadgeText({
text: '',
});

await setSettings({
lastUpdateStats: {
Datetime: DateTime.fromJSDate(new Date()),
Datetime: new Date().toJSON(),
Status: 'NOK',
},
});
Expand Down Expand Up @@ -135,7 +146,13 @@ const sendWebActivity = async (
}

const { records, states } = transformTimelineInWebActivity(timeline);
const result = await sendStats(userToken, records, states);
let result = null;
try {
result = await sendStats(userToken, records, states);
}
catch(err) {
console.log(err);
}

if (result) {
await Promise.all([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { twMerge } from 'tailwind-merge';
import { Button, ButtonType } from '../../../blocks/Button';
import { Icon, IconType } from '../../../blocks/Icon';
import { Input } from '../../../blocks/Input';
import { Panel, PanelBody, PanelHeader } from '../../../blocks/Panel';
import { PanelBody } from '../../../blocks/Panel';
import { assertDomainIsValid } from '../../../shared/utils/domains';
import { usePopupContext } from '../../hooks/PopupContext';

Expand Down Expand Up @@ -65,8 +65,7 @@ export const IgnoredDomainSetting: React.FC = () => {
}, [setDomainsListExpanded]);

return (
<Panel>
<PanelHeader>Blacklist domains</PanelHeader>
<div className="p-2">
<PanelBody className="flex flex-col gap-2">
<p>You can hide unwanted websites to keep dashboards clean.</p>
<div className="flex justify-between items-end gap-2">
Expand Down Expand Up @@ -111,6 +110,6 @@ export const IgnoredDomainSetting: React.FC = () => {
</div>
</div>
</PanelBody>
</Panel>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as React from 'react';
import { twMerge } from 'tailwind-merge';

import { Button, ButtonType } from '../../../blocks/Button';
import { Icon, IconType } from '../../../blocks/Icon';
import { Input } from '../../../blocks/Input';
import { PanelBody } from '../../../blocks/Panel';
import { assertDomainIsValid } from '../../../shared/utils/domains';
import { usePopupContext } from '../../hooks/PopupContext';

export const WhitelistDomainSetting: React.FC = () => {
const { settings, updateSettings } = usePopupContext();
const [ allowedHosts, setAllowedHosts] = React.useState<string[]>(
settings.allowedHosts ?? []
);
const [ newAllowedHost, setNewAllowedHost] = React.useState<string>('');
const [isAllowedHostsListExpanded, setAllowedHostListExpanded] =
React.useState<boolean>(false);

const handleAddWhitelistDomain = React.useCallback(() => {
try {
assertDomainIsValid(newAllowedHost);
setAllowedHosts((prev) => {
const newAllowedHostList = Array.from(
new Set([...prev, newAllowedHost])
);

updateSettings({
allowedHosts: newAllowedHostList,
});

return newAllowedHostList;
});

setNewAllowedHost('');
} catch (_) {
//
}
}, [newAllowedHost, updateSettings]);

const handleRemoveAllowedHost = React.useCallback(
(host: string) => {
setAllowedHosts((prev) => {
const newAllowedHostList = prev.filter((h) => h !== host);

updateSettings({
allowedHosts: newAllowedHostList,
});

return newAllowedHostList;
});
},
[setAllowedHosts, updateSettings]
);

const handleAddtoAllowedHostChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setNewAllowedHost(e.target.value);
},
[]
);

const handleAllowHostsListExpanded = React.useCallback(() => {
setAllowedHostListExpanded((prev) => !prev);
}, [setAllowedHostListExpanded]);

return (
<div className="p-2">
<PanelBody className="flex flex-col gap-2">
<p>You can add only the domains you wish to track.</p>
<div className="flex justify-between items-end gap-2">
<label className="flex flex-col gap-1 w-full">
Domain
<Input
placeholder="e.g. google.com"
value={newAllowedHost}
onChange={handleAddtoAllowedHostChange}
/>
</label>
<Button
className="h-fit py-2 px-4 border-2 border-solid border-transparent"
buttonType={ButtonType.Primary}
onClick={handleAddWhitelistDomain}
>
Add
</Button>
</div>
<div className="flex flex-col gap-2">
<a
href="#"
className="text-blue-500"
onClick={handleAllowHostsListExpanded}
>
View all whitelisted domains
</a>
<div className={twMerge('hidden', isAllowedHostsListExpanded && 'block')}>
{!allowedHosts.length && (
<p className="text-gray-500">No whitelisted domains</p>
)}
{allowedHosts.map((domain) => (
<div key={domain} className="flex items-center gap-2">
<Icon
type={IconType.Close}
className="hover:text-neutral-400 cursor-pointer"
onClick={() => handleRemoveAllowedHost(domain)}
/>
<span>{domain}</span>
</div>
))}
</div>
</div>
</PanelBody>
</div>
);
};
15 changes: 13 additions & 2 deletions src/popup/hooks/PopupContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,25 @@ export const PopupContextProvider: React.FC = ({ children }) => {

const filterDomainsFromStore = React.useCallback(
(store: Record<string, number>) => {
const filteredStore = Object.fromEntries(
let filteredStore = Object.fromEntries(
Object.entries(store).filter(
([key]) => !settings.ignoredHosts.includes(key),
),
);

// Handling whitelisting of hosts
const allowedHosts = settings.allowedHosts ?? [];
if (allowedHosts.length > 0) {
filteredStore = Object.fromEntries(
Object.entries(filteredStore).filter(
([key]) => allowedHosts.includes(key),
),
);
}

return filteredStore;
},
[settings.ignoredHosts],
[settings.ignoredHosts, settings.allowedHosts],
);

const filteredStore = React.useMemo(
Expand Down
38 changes: 37 additions & 1 deletion src/popup/pages/PreferencesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
import * as React from 'react';
import {FC} from 'react';

import { Panel, PanelBody, PanelHeader } from './../../blocks/Panel';
import { Input } from './../../blocks/Input';


import {IgnoredDomainSetting} from '../components/IgnoredDomainsSetting/IgnoredDomainSetting';
import { WhitelistDomainSetting } from '../components/WhitelistDomainsSetting/WhitelistDomainSetting';
import {UserTokenSetting} from "../components/UserTokenSetting/UserTokenSetting";

export const PreferencesPage: FC = () => {
const [isWhitelistShown, hideWhitelist] = React.useState<boolean>(true);

const toggle = React.useCallback(() => {
hideWhitelist((prev) => !prev);
}, [hideWhitelist]);

return (
<div className="flex flex-col">
<UserTokenSetting/>
<IgnoredDomainSetting/>
<Panel>
<PanelHeader>Domain Management</PanelHeader>
<div className="flex gap-x-2">
<div className="flex">
<Input type="radio" name="rdnDomain" id="rdnWhitelist"
className="mt-0.5 rounded-full text-blue-600"
checked={isWhitelistShown}
onChange={toggle}
/>
<label htmlFor="rdnWhitelist" className="text-sm ml-1 text-gray-500 ms-2 dark:text-gray-400">Whitelist</label>
</div>

<div className="flex">
<Input type="radio" name="rdnDomain" id="rdnBlacklist"
className="mt-0.5 rounded-full text-blue-600"
checked={!isWhitelistShown}
onChange={toggle}
/>
<label htmlFor="rdnBlacklist" className="text-sm ml-1 text-gray-500 ms-2 dark:text-gray-400">Blacklist</label>
</div>
</div>

<PanelBody>
{(isWhitelistShown ? <WhitelistDomainSetting />: <IgnoredDomainSetting />)}
</PanelBody>
</Panel>
</div>
);
};
5 changes: 3 additions & 2 deletions src/shared/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,16 @@ export interface LogMessage {
export interface Preferences {
connectionStatus: ConnectionStatus;
userToken?: string;
ignoredHosts: string[];
ignoredHosts: string[]; //urls that are be blacklisted
allowedHosts?: string[]; //urls that are to be whitelisted
limits: Record<string, number>;
displayTimeOnBadge: boolean;
lastUpdateStats?: Statistics;
}

export interface Statistics {
Status: 'OK' | 'NOK';
Datetime: DateTime;
Datetime: string;
}

export enum ConnectionStatus {
Expand Down
Loading