From 60038149986e0aaa2d8be151de3333ba359df61b Mon Sep 17 00:00:00 2001 From: Timeo Williams Date: Sun, 3 Nov 2024 11:20:15 -0500 Subject: [PATCH 1/3] fix: support favicon for gmail --- src/main.ts | 60 +++++++++++++++++++ src/renderer/src/Analytics.tsx | 16 ++--- src/renderer/src/UnproductiveWebsites.tsx | 10 +--- src/renderer/src/lib/utils.ts | 16 +++++ ....timestamp-1729641953107-ae8ec9be8ef4b.mjs | 21 ------- 5 files changed, 82 insertions(+), 41 deletions(-) delete mode 100644 vite.renderer.config.mts.timestamp-1729641953107-ae8ec9be8ef4b.mjs diff --git a/src/main.ts b/src/main.ts index 3bdac78..b0f0bff 100644 --- a/src/main.ts +++ b/src/main.ts @@ -652,3 +652,63 @@ export function handleDailyReset() { } } } + +async function sendDailyEmail(username, date, deepWorkHours, siteTimeTrackers) { + if (!username || siteTimeTrackers.length === 0) { + console.log('No data to send in email.') + return + } + + const MIN_TIME_THRESHOLD = 60 + const filteredTrackers = siteTimeTrackers.filter( + (tracker) => tracker.timeSpent >= MIN_TIME_THRESHOLD + ) + + const emailData = { + username, + date, + deepWorkHours, + trackers: filteredTrackers.map((tracker) => ({ + title: tracker.title, + url: tracker.url, + timeSpent: tracker.timeSpent + })) + } + + try { + const response = await fetch(`${process.env.VITE_SERVER_URL_PROD}/api/v1/email/send-daily`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(emailData) + }) + console.log('Email sent status:', response.status) + } catch (error) { + console.error('Error sending email:', error) + } +} + + +async function checkAndSendMissedEmails() { + const lastEmailDate = dayjs(store.get(LAST_EMAIL_DATE_KEY, null) || dayjs().subtract(1, 'day')) + const today = dayjs().startOf('day') + + if (!lastEmailDate.isSame(today, 'day')) { + let dateToProcess = lastEmailDate.add(1, 'day') + + while (dateToProcess.isBefore(today) || dateToProcess.isSame(today, 'day')) { + const formattedDate = dateToProcess.format('YYYY-MM-DD') + console.log(`Sending missed email for date: ${formattedDate}`) + + const username = store.get('user')?.username + const deepWorkHours = store.get('deepWorkHours') + const siteTimeTrackers = store.get('siteTimeTrackers', []) + + await sendDailyEmail(username, formattedDate, deepWorkHours, siteTimeTrackers) + + // Update the last email date after each successful send + store.set(LAST_EMAIL_DATE_KEY, dateToProcess.toISOString()) + + dateToProcess = dateToProcess.add(1, 'day') + } + } +} diff --git a/src/renderer/src/Analytics.tsx b/src/renderer/src/Analytics.tsx index 77c73fd..1591af5 100644 --- a/src/renderer/src/Analytics.tsx +++ b/src/renderer/src/Analytics.tsx @@ -4,6 +4,7 @@ import { SiteTimeTracker, TrackerType, AppIcon } from './types' const BarChart = lazy(() => import('./BarChart')) import { IpcRendererEvent } from 'electron' import {Motion} from 'solid-motionone'; +import { getFavicon } from './lib/utils' const Analytics = () => { const [showDeepWork, setShowDeepWork] = createSignal(true) // State for toggle @@ -13,14 +14,12 @@ const Analytics = () => { // Function to fetch the icon data URL - const fetchAppIcon = async (iconPath?: string) => { + const fetchAppIcon = async (iconPath?: string): Promise => { try { const iconDataUrl = await window.electron.ipcRenderer.invoke('get-icon', iconPath) const iconUrl = iconDataUrl || 'https://cdn-icons-png.freepik.com/512/7022/7022186.png' - // Cache the fetched icon setIconCache({ ...iconCache(), [iconPath]: iconUrl }) - return iconUrl } catch (error) { console.error('Error fetching app icon:', error) @@ -28,11 +27,6 @@ const Analytics = () => { } } - // Function to fetch the favicon for websites - const fetchFavicon = (url: string) => { - return `https://www.google.com/s2/favicons?sz=64&domain=${new URL(url).hostname}` - } - const fetchSiteTrackers = () => { window?.electron?.ipcRenderer.send('fetch-site-trackers') } @@ -50,12 +44,12 @@ const Analytics = () => { console.warn('Skipping invalid tracker:', tracker) return null } - + let iconUrl = '' - + if (tracker.type === TrackerType.Website) { // Fetch the favicon for websites - iconUrl = fetchFavicon(tracker.url) + iconUrl = getFavicon(tracker.url) } else if (tracker.type === TrackerType.App && tracker.iconUrl) { // If tracker has a valid iconPath, fetch the app icon iconUrl = await fetchAppIcon(tracker.iconUrl) diff --git a/src/renderer/src/UnproductiveWebsites.tsx b/src/renderer/src/UnproductiveWebsites.tsx index 6a8f803..c508e18 100755 --- a/src/renderer/src/UnproductiveWebsites.tsx +++ b/src/renderer/src/UnproductiveWebsites.tsx @@ -2,20 +2,12 @@ import { createSignal, For, onMount, onCleanup } from 'solid-js' import { TextField, TextFieldInput } from './components/ui/text-field' import { Button } from './components/ui/button' import { IoRemoveCircleOutline, VsAdd } from './components/ui/icons' +import { getFavicon } from './lib/utils' const UnproductiveWebsites = () => { const [site, setSite] = createSignal('') const [unproductiveSites, setUnproductiveSites] = createSignal([]) - const getFavicon = (url: string) => { - try { - const domain = new URL(url).hostname - return `https://www.google.com/s2/favicons?domain=${domain}` - } catch (error) { - console.error('Invalid URL format:', url) - return '' - } - } onMount(() => { window.electron.ipcRenderer.send('fetch-unproductive-urls') // Request URLs from main process diff --git a/src/renderer/src/lib/utils.ts b/src/renderer/src/lib/utils.ts index 697751e..76cd1bd 100755 --- a/src/renderer/src/lib/utils.ts +++ b/src/renderer/src/lib/utils.ts @@ -18,3 +18,19 @@ export const stopActivityMonitoring = () => { console.log('Stopping activity monitoring') window?.electron.ipcRenderer.send('logout-user') } + + +export const getFavicon = (url: string): string => { + try { + const formattedUrl = url.startsWith('http://') ? url.replace('http://', 'https://') : url + const domain = new URL(formattedUrl).hostname + if (domain === 'mail.google.com') { + return 'https://ssl.gstatic.com/ui/v1/icons/mail/rfr/gmail.ico' + } else { + return `https://www.google.com/s2/favicons?sz=64&domain=${formattedUrl}` + } + } catch (error) { + console.error('Invalid URL format:', url) + return '' + } +} \ No newline at end of file diff --git a/vite.renderer.config.mts.timestamp-1729641953107-ae8ec9be8ef4b.mjs b/vite.renderer.config.mts.timestamp-1729641953107-ae8ec9be8ef4b.mjs deleted file mode 100644 index 9d31bae..0000000 --- a/vite.renderer.config.mts.timestamp-1729641953107-ae8ec9be8ef4b.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// vite.renderer.config.mts -import { defineConfig } from "file:///Users/timeo/deepFocus/deepWork/node_modules/vite/dist/node/index.js"; -import solidPlugin from "file:///Users/timeo/deepFocus/deepWork/node_modules/vite-plugin-solid/dist/esm/index.mjs"; -var vite_renderer_config_default = defineConfig({ - plugins: [solidPlugin()], - build: { - target: "esnext", - outDir: ".vite/renderer/main_window", - emptyOutDir: true, - rollupOptions: { - input: "index.html", - output: { - entryFileNames: "[name].js" - } - } - } -}); -export { - vite_renderer_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5yZW5kZXJlci5jb25maWcubXRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL1VzZXJzL3RpbWVvL2RlZXBGb2N1cy9kZWVwV29ya1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL3RpbWVvL2RlZXBGb2N1cy9kZWVwV29yay92aXRlLnJlbmRlcmVyLmNvbmZpZy5tdHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL3RpbWVvL2RlZXBGb2N1cy9kZWVwV29yay92aXRlLnJlbmRlcmVyLmNvbmZpZy5tdHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcblxuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZ1xuLy8gZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHt9KTtcblxuaW1wb3J0IHNvbGlkUGx1Z2luIGZyb20gJ3ZpdGUtcGx1Z2luLXNvbGlkJztcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW3NvbGlkUGx1Z2luKCldLFxuICBidWlsZDoge1xuICAgIHRhcmdldDogJ2VzbmV4dCcsXG4gICAgb3V0RGlyOiAnLnZpdGUvcmVuZGVyZXIvbWFpbl93aW5kb3cnLFxuICAgIGVtcHR5T3V0RGlyOiB0cnVlLFxuICAgIHJvbGx1cE9wdGlvbnM6IHtcbiAgICAgIGlucHV0OiAnaW5kZXguaHRtbCcsXG4gICAgICBvdXRwdXQ6IHtcbiAgICAgICAgZW50cnlGaWxlTmFtZXM6ICdbbmFtZV0uanMnLFxuICAgICAgfSxcbiAgICB9LFxuICB9LFxufSk7XG5cbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBbVMsU0FBUyxvQkFBb0I7QUFLaFUsT0FBTyxpQkFBaUI7QUFFeEIsSUFBTywrQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUyxDQUFDLFlBQVksQ0FBQztBQUFBLEVBQ3ZCLE9BQU87QUFBQSxJQUNMLFFBQVE7QUFBQSxJQUNSLFFBQVE7QUFBQSxJQUNSLGFBQWE7QUFBQSxJQUNiLGVBQWU7QUFBQSxNQUNiLE9BQU87QUFBQSxNQUNQLFFBQVE7QUFBQSxRQUNOLGdCQUFnQjtBQUFBLE1BQ2xCO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo= From ac124c0ff3e79c47bf7fb70cb4e749b74a90edfb Mon Sep 17 00:00:00 2001 From: Timeo Williams Date: Sun, 3 Nov 2024 13:18:14 -0500 Subject: [PATCH 2/3] feat: add support for tracking Orion browser --- src/productivityUtils.ts | 5 +++-- src/types.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/productivityUtils.ts b/src/productivityUtils.ts index f9d9a1d..f017a38 100755 --- a/src/productivityUtils.ts +++ b/src/productivityUtils.ts @@ -193,7 +193,7 @@ export function getActiveWindowApp(): Promise { export function getBrowserURL(browser: string): Promise { return new Promise((resolve, _reject) => { let script = `osascript -e 'tell application "${browser}" to get URL of active tab of front window'` - if (browser === 'Safari') { + if (browser === 'Safari' || browser === 'Orion') { script = `osascript -e 'tell application "${browser}" to get URL of front document'` } else if (browser.toLowerCase() === 'firefox') { script = ` @@ -249,6 +249,7 @@ export function isBrowser(appName: string): appName is browser { 'Opera', 'Safari', 'Firefox', - 'firefox' + 'firefox', + 'Orion' ].includes(appName) } diff --git a/src/types.ts b/src/types.ts index a5e611f..f2d20a3 100755 --- a/src/types.ts +++ b/src/types.ts @@ -177,6 +177,7 @@ export type browser = | 'Safari' | 'Firefox' | 'firefox' + | 'Orion' export interface WorkContext { type: 'URL' | 'appName' @@ -190,7 +191,6 @@ export interface AppIcon { iconPath: string } - export enum TrackerType { Website = 'website', App = 'app' From 661e83b4a8800b65b74516d0dd5f3b2d15927ede Mon Sep 17 00:00:00 2001 From: Timeo Williams Date: Sun, 3 Nov 2024 14:34:57 -0500 Subject: [PATCH 3/3] chore: add logging --- latest-mac.json | 2 +- src/types.ts | 106 ------------------------------------------------ src/utils.ts | 9 +++- 3 files changed, 8 insertions(+), 109 deletions(-) diff --git a/latest-mac.json b/latest-mac.json index f98ca04..e8cebc1 100644 --- a/latest-mac.json +++ b/latest-mac.json @@ -1,5 +1,5 @@ { - "url": "https://github.com/Tech-Nest-Ventures/deepFocus/releases/download/v.2.4.4/Deep.Focus.zip", + "url": "https://github.com/Tech-Nest-Ventures/deepFocus/releases/download/v2.4.4/Deep.Focus.zip", "name": "2.4.4", "notes": "Bug fixes and performance improvements.", "pub_date": "2024-11-02T12:00:00Z" diff --git a/src/types.ts b/src/types.ts index f2d20a3..bcea39e 100755 --- a/src/types.ts +++ b/src/types.ts @@ -53,110 +53,6 @@ export type DeepWorkHours = { Saturday: number Sunday: number } - -export type ExtendedResult = Result & { url?: string; siteTimeTracker?: SiteTimeTracker } - -export type Options = { - /** - Enable the accessibility permission check. _(macOS)_ - - Setting this to `false` will prevent the accessibility permission prompt on macOS versions 10.15 and newer. The `url` property won't be retrieved. - - @default true - */ - readonly accessibilityPermission: boolean - - /** - Enable the screen recording permission check. _(macOS)_ - - Setting this to `false` will prevent the screen recording permission prompt on macOS versions 10.15 and newer. The `title` property in the result will always be set to an empty string. - - @default true - */ - readonly screenRecordingPermission: boolean -} - -export type BaseOwner = { - /** - Name of the app. - */ - name: string - - /** - Process identifier - */ - processId: number - - /** - Path to the app. - */ - path: string -} - -export type BaseResult = { - /** - Window title. - */ - title: string - - /** - Window identifier. - - On Windows, there isn't a clear notion of a "Window ID". Instead it returns the memory address of the window "handle" in the `id` property. That "handle" is unique per window, so it can be used to identify them. [Read moreā€¦](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632597(v=vs.85).aspx#window_handle). - */ - id: number - - /** - Window position and size. - */ - bounds: { - x: number - y: number - width: number - height: number - } - - /** - App that owns the window. - */ - owner: BaseOwner - - /** - Memory usage by the window. - */ - memoryUsage: number -} - -// eslint-disable-next-line @typescript-eslint/naming-convention -export type MacOSOwner = { - /** - Bundle identifier. - */ - bundleId: string -} & BaseOwner - -// eslint-disable-next-line @typescript-eslint/naming-convention -export type MacOSResult = { - platform: 'macos' - - owner: MacOSOwner - - /** - URL of the active browser tab if the active window is Safari (includes Technology Preview), Chrome (includes Beta, Dev, and Canary), Edge (includes Beta, Dev, and Canary), Brave (includes Beta and Nightly), Mighty, Ghost Browser, WaveBox, Sidekick, Opera (includes Beta and Developer), or Vivaldi. - */ - url?: string -} & BaseResult - -export type LinuxResult = { - platform: 'linux' -} & BaseResult - -export type WindowsResult = { - platform: 'windows' -} & BaseResult - -export type Result = MacOSResult | LinuxResult | WindowsResult - export interface ElectronAPI { sendUserData: (user: { username: string @@ -184,8 +80,6 @@ export interface WorkContext { value: string } - - export interface AppIcon { appName: string iconPath: string diff --git a/src/utils.ts b/src/utils.ts index 7f0f83a..a87623f 100755 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,6 +9,7 @@ export function checkForUpdates(): void { const server = 'https://raw.githubusercontent.com/Tech-Nest-Ventures/deepFocus/main' const feedURL = `${server}/latest-mac.json` autoUpdater.setFeedURL({ url: feedURL, serverType: 'json' }) + log.info(autoUpdater.getFeedURL()) autoUpdater.checkForUpdates() @@ -24,6 +25,10 @@ export function checkForUpdates(): void { }) }) + autoUpdater.on('checking-for-update', () => { + log.info('Checking for update...') + }) + autoUpdater.on('update-not-available', () => { log.info('No update available.') @@ -45,8 +50,8 @@ export function checkForUpdates(): void { }) }) - autoUpdater.on('update-downloaded', async () => { - log.info('Update downloaded to:', autoUpdater.getFeedURL()) + autoUpdater.on('update-downloaded', async (event, releaseNotes, releaseDate, updateURL) => { + log.info('Update downloaded to:', releaseNotes, releaseDate, updateURL) const { response } = await dialog.showMessageBox({ type: 'info', title: 'Install Update',