From 8af4588440dced138026da100053cb5cc7152ee9 Mon Sep 17 00:00:00 2001 From: David Messinger Date: Mon, 26 Feb 2024 09:22:30 -0500 Subject: [PATCH 1/3] adjusting versions --- package-lock.json | 20 ++++++++------------ package.json | 4 ++-- static/manifest.json | 15 ++++----------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 27919a5..6fed2db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7774,6 +7774,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -11019,15 +11020,13 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-node": { "version": "1.8.2", @@ -12291,8 +12290,7 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", - "dev": true, - "requires": {} + "dev": true }, "eslint-plugin-prettier": { "version": "4.2.1", @@ -12364,8 +12362,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "requires": {} + "dev": true }, "eslint-plugin-sort-keys-fix": { "version": "1.1.2", @@ -13760,8 +13757,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "29.2.0", @@ -15168,6 +15164,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -15664,8 +15661,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", - "dev": true, - "requires": {} + "dev": true }, "supports-color": { "version": "7.2.0", diff --git a/package.json b/package.json index 1730d1d..b35ba24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codealike-plugin", - "version": "2.0.2", + "version": "2.0.4", "description": "Track activity while coding.", "private": true, "scripts": { @@ -26,7 +26,7 @@ "contributors": [ "Andre Garcia (https://www.linkedin.com/in/andre-castro-garcia/)" ], - "license": "GPL3", + "license": "MIT", "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^2.0.2", "@types/chrome": "^0.0.203", diff --git a/static/manifest.json b/static/manifest.json index 634c105..aaea938 100644 --- a/static/manifest.json +++ b/static/manifest.json @@ -15,16 +15,12 @@ }, "content_scripts": [ { - "matches": [ - "" - ], - "js": [ - "content.bundle.js" - ] + "matches": [""], + "js": ["content.bundle.js"] } ], "minimum_chrome_version": "90", - "version": "2.0.1", + "version": "2.0.4", "description": "Track activity while coding.", "permissions": [ "idle", @@ -34,10 +30,7 @@ "alarms", "webNavigation" ], - "host_permissions": [ - "http://*/*", - "https://*/*" - ], + "host_permissions": ["http://*/*", "https://*/*"], "background": { "service_worker": "background.bundle.js" }, From 712fd0ed0e8709b18fedfefeae575b550a4af21b Mon Sep 17 00:00:00 2001 From: ritesh-cs Date: Fri, 12 Apr 2024 13:14:30 +0530 Subject: [PATCH 2/3] Add whitelisting feature --- src/background/controller/index.ts | 29 +++-- .../IgnoredDomainSetting.tsx | 7 +- .../WhitelistDomainSetting.tsx | 115 ++++++++++++++++++ src/popup/hooks/PopupContext.tsx | 15 ++- src/popup/pages/PreferencesPage.tsx | 38 +++++- src/shared/db/types.ts | 3 +- src/shared/preferences/index.ts | 1 + src/shared/utils/url.ts | 23 +++- static/manifest.json | 2 +- 9 files changed, 212 insertions(+), 21 deletions(-) create mode 100644 src/popup/components/WhitelistDomainsSetting/WhitelistDomainSetting.tsx diff --git a/src/background/controller/index.ts b/src/background/controller/index.ts index 2b568ee..27d57c5 100644 --- a/src/background/controller/index.ts +++ b/src/background/controller/index.ts @@ -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'; @@ -49,17 +49,19 @@ const handleAndCollectDomainIgnoredInfo = async ( currentTimelineRecord: TimelineRecord | null, focusedActiveTab: chrome.tabs.Tab | null, ): Promise => { - 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 = ( @@ -104,7 +106,7 @@ export const handleStateChange = async ( await activeTimeline.set(currentTimelineRecord); } - const isDomainIgnored = await handleAndCollectDomainIgnoredInfo( + const isDomainAllowed = await handleAndCollectDomainIgnoredInfo( preferences, currentTimelineRecord, focusedActiveTab, @@ -113,7 +115,7 @@ export const handleStateChange = async ( await updateTimeOnBadge( focusedActiveTab, currentTimelineRecord, - preferences.displayTimeOnBadge && !isDomainIgnored, + preferences.displayTimeOnBadge && isDomainAllowed, ); if ( @@ -121,7 +123,7 @@ export const handleStateChange = async ( isImpossiblyLongEvent || isInvalidUrl(focusedActiveTab?.url) ) { - await commitTabActivity(await activeTimeline.get()); + await commitTabActivity(await activeTimeline.get(), preferences); return; } @@ -129,7 +131,7 @@ export const handleStateChange = async ( focusedActiveTab && currentTimelineRecord?.url !== focusedActiveTab?.url ) { - await commitTabActivity(await activeTimeline.get()); + await commitTabActivity(await activeTimeline.get(), preferences); await createNewActiveRecord( timestamp, focusedActiveTab, @@ -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); diff --git a/src/popup/components/IgnoredDomainsSetting/IgnoredDomainSetting.tsx b/src/popup/components/IgnoredDomainsSetting/IgnoredDomainSetting.tsx index 5ac3a0d..64a51c3 100644 --- a/src/popup/components/IgnoredDomainsSetting/IgnoredDomainSetting.tsx +++ b/src/popup/components/IgnoredDomainsSetting/IgnoredDomainSetting.tsx @@ -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'; @@ -65,8 +65,7 @@ export const IgnoredDomainSetting: React.FC = () => { }, [setDomainsListExpanded]); return ( - - Blacklist domains +

You can hide unwanted websites to keep dashboards clean.

@@ -111,6 +110,6 @@ export const IgnoredDomainSetting: React.FC = () => {
-
+ ); }; diff --git a/src/popup/components/WhitelistDomainsSetting/WhitelistDomainSetting.tsx b/src/popup/components/WhitelistDomainsSetting/WhitelistDomainSetting.tsx new file mode 100644 index 0000000..4892106 --- /dev/null +++ b/src/popup/components/WhitelistDomainsSetting/WhitelistDomainSetting.tsx @@ -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( + settings.allowedHosts ?? [] + ); + const [ newAllowedHost, setNewAllowedHost] = React.useState(''); + const [isAllowedHostsListExpanded, setAllowedHostListExpanded] = + React.useState(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) => { + setNewAllowedHost(e.target.value); + }, + [] + ); + + const handleAllowHostsListExpanded = React.useCallback(() => { + setAllowedHostListExpanded((prev) => !prev); + }, [setAllowedHostListExpanded]); + + return ( +
+ +

You can add only the domains you wish to track.

+
+ + +
+
+ + View all whitelisted domains + +
+ {!allowedHosts.length && ( +

No whitelisted domains

+ )} + {allowedHosts.map((domain) => ( +
+ handleRemoveAllowedHost(domain)} + /> + {domain} +
+ ))} +
+
+
+
+ ); +}; diff --git a/src/popup/hooks/PopupContext.tsx b/src/popup/hooks/PopupContext.tsx index 3076f8a..a9b2b96 100644 --- a/src/popup/hooks/PopupContext.tsx +++ b/src/popup/hooks/PopupContext.tsx @@ -31,14 +31,25 @@ export const PopupContextProvider: React.FC = ({ children }) => { const filterDomainsFromStore = React.useCallback( (store: Record) => { - 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( diff --git a/src/popup/pages/PreferencesPage.tsx b/src/popup/pages/PreferencesPage.tsx index 3a81c40..4633116 100644 --- a/src/popup/pages/PreferencesPage.tsx +++ b/src/popup/pages/PreferencesPage.tsx @@ -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(true); + + const toggle = React.useCallback(() => { + hideWhitelist((prev) => !prev); + }, [hideWhitelist]); + return (
- + + Domain Management +
+
+ + +
+ +
+ + +
+
+ + + {(isWhitelistShown ? : )} + +
); }; diff --git a/src/shared/db/types.ts b/src/shared/db/types.ts index aad7186..211ab26 100644 --- a/src/shared/db/types.ts +++ b/src/shared/db/types.ts @@ -56,7 +56,8 @@ 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; displayTimeOnBadge: boolean; lastUpdateStats?: Statistics; diff --git a/src/shared/preferences/index.ts b/src/shared/preferences/index.ts index 826ecab..62096a3 100644 --- a/src/shared/preferences/index.ts +++ b/src/shared/preferences/index.ts @@ -1,6 +1,7 @@ import { ConnectionStatus, Preferences } from '../db/types'; export const DEFAULT_PREFERENCES: Preferences = { + allowedHosts: [], //whitelisted domains connectionStatus: ConnectionStatus.Disconnected, displayTimeOnBadge: true, ignoredHosts: [], diff --git a/src/shared/utils/url.ts b/src/shared/utils/url.ts index f7daa00..f9ec3d1 100644 --- a/src/shared/utils/url.ts +++ b/src/shared/utils/url.ts @@ -8,7 +8,28 @@ export const isInvalidUrl = (url: string | undefined): url is undefined => { return ( !url || ['chrome', 'about', 'opera', 'edge', 'coccoc', 'yabro'].some((broName) => - url.startsWith(broName) + url.startsWith(broName), ) ); }; + +export const isDomainAllowedByUser = ( + currentHost: string | undefined, + allowedHosts: string[] | undefined, + ignoreHosts: string[] | undefined, +) => { + if (!currentHost) { + return false; + } + + let isAllowed = true; + if (allowedHosts && allowedHosts?.length > 0) { + isAllowed = allowedHosts.includes(currentHost); + } + + let isBlocked = false; + if (ignoreHosts && ignoreHosts?.length > 0) { + isBlocked = ignoreHosts.includes(currentHost); + } + return isAllowed && !isBlocked; +}; diff --git a/static/manifest.json b/static/manifest.json index aaea938..8d7a2d9 100644 --- a/static/manifest.json +++ b/static/manifest.json @@ -20,7 +20,7 @@ } ], "minimum_chrome_version": "90", - "version": "2.0.4", + "version": "2.1.0", "description": "Track activity while coding.", "permissions": [ "idle", From 7e8f7dde05f0e310ac13467f26bcc4299ec14e52 Mon Sep 17 00:00:00 2001 From: ritesh-cs Date: Mon, 15 Apr 2024 12:59:42 +0530 Subject: [PATCH 3/3] Wrap the sendStats function inside a try catch block to trap error --- src/background/services/stats.ts | 31 ++++++++++++++++++++++++------- src/shared/db/types.ts | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/background/services/stats.ts b/src/background/services/stats.ts index 929b88c..988ff48 100644 --- a/src/background/services/stats.ts +++ b/src/background/services/stats.ts @@ -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', }, }); @@ -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', }, }); @@ -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([ diff --git a/src/shared/db/types.ts b/src/shared/db/types.ts index 211ab26..e31518c 100644 --- a/src/shared/db/types.ts +++ b/src/shared/db/types.ts @@ -65,7 +65,7 @@ export interface Preferences { export interface Statistics { Status: 'OK' | 'NOK'; - Datetime: DateTime; + Datetime: string; } export enum ConnectionStatus {