Skip to content

Add optimal syncing - #36

Open
timeowilliams wants to merge 4 commits into
mainfrom
add-optimal-syncing
Open

Add optimal syncing#36
timeowilliams wants to merge 4 commits into
mainfrom
add-optimal-syncing

Conversation

@timeowilliams

@timeowilliams timeowilliams commented Apr 21, 2025

Copy link
Copy Markdown
Member

Description

Additional context


What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

Before submitting the PR, please make sure you do the following

Summary by CodeRabbit

  • New Features

    • Added real-time synchronization of site time tracking and deep work hours using a WebSocket connection.
    • Introduced a built-in WebSocket server that forwards activity data to a backend API.
    • Added automation for building and rebuilding Electron native modules.
    • Enabled sending user data from the renderer to the backend via IPC.
  • Improvements

    • Enhanced Electron app stability with robust management of the WebSocket server during app and system events.
    • Updated build configurations for compatibility with Node.js 20 and improved output structure.
    • Improved packaging reliability with automatic unpacking of native modules.
    • Refined preload script to support context isolation and secure IPC communication.
  • Developer Experience

    • Added type definitions and runtime libraries for WebSocket support.
    • Refined build scripts and dependency management for smoother development and deployment.
    • Added lifecycle logging in the login component for debugging purposes.

@timeowilliams timeowilliams self-assigned this Apr 21, 2025
@coderabbitai

coderabbitai Bot commented Apr 21, 2025

Copy link
Copy Markdown

Walkthrough

This update introduces a robust WebSocket-based synchronization mechanism for site time tracking and deep work hours in an Electron application. The changes add a dedicated WebSocket server process, client logic for efficient delta updates, and lifecycle management to ensure reliability across app and system events. The build and packaging configurations are enhanced to support native modules and multiple entry points. Several scripts and dependencies are added or updated to automate builds, handle Electron native module rebuilding, and support WebSocket communication. The preload and renderer layers are updated to enable secure IPC for user data, and new configuration files are introduced to streamline the build process for main, preload, and renderer processes.

Changes

File(s) Change Summary
package.json Added build and postinstall scripts for Vite and Electron native module rebuilding; added/updated dependencies for WebSocket support and type definitions.
src/main.ts Refactored store initialization to be asynchronous; added WebSocket server management via a child process; updated app lifecycle event handling; added IPC listener for user data.
src/optiSync.ts Introduced OptiSyncClient class for WebSocket-based synchronization of site time trackers and deep work hours; exports a singleton client instance.
src/productivityUtils.ts Integrated optiSync.sendUpdates to send tracker updates and deep work hours after modifications.
src/server.ts Added a WebSocket server that forwards sync updates to a backend API and sends acknowledgments to clients.
src/wsServer.ts New entry to start the WebSocket server, handle shutdown, and process IPC messages from the parent process.
vite.main.config.ts, vite.main.config.mts, vite.preload.config.ts, vite.preload.config.mts, vite.renderer.config.mts Updated and added Vite configs for multi-entry builds, Node 20 targeting, output directories, external dependencies, and dependency optimization.
forge.config.ts Added AutoUnpackNativesPlugin for native modules; updated Vite plugin config paths and minor config tweaks.
src/preload.ts Extended the exposed API to include sendUserToBackend for secure IPC; imported ipcRenderer.
src/renderer/src/Login.tsx Added lifecycle hooks to log the window.electron object and user data for debugging.
src/renderer/src/lib/utils.ts Updated global type declarations for window.electron; refactored sendUserToBackend to use the new IPC method.
README.md Added a new section describing the app's WebSocket synchronization feature and setup instructions.

Sequence Diagram(s)

sequenceDiagram
    participant Renderer
    participant Preload
    participant Main
    participant WS_Server
    participant Backend

    Renderer->>Preload: window.electron.sendUserToBackend(user)
    Preload->>Main: IPC 'user-data' with user
    Main->>Main: Log received user data

    Renderer->>Main: updateSiteTimeTracker()
    Main->>OptiSyncClient: optiSync.sendUpdates(trackers, deepWorkHours)
    OptiSyncClient->>WS_Server: WebSocket SYNC_UPDATE (delta)
    WS_Server->>Backend: HTTP POST /api/v1/activity/persist
    Backend-->>WS_Server: HTTP 2xx (success)
    WS_Server->>OptiSyncClient: WebSocket SYNC_ACK
Loading

Suggested labels

released

Poem

🐇
A WebSocket hop, a server spun,
Trackers sync as work gets done.
With builds and scripts, the code’s renewed,
IPC now safely queued.
From Electron’s warren, updates fly,
As rabbits cheer and data spry!
🥕

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (8)
src/wsServer.ts (1)

6-10: Implement actual message handling logic.

The current implementation only logs messages from the parent process without taking any action. This should be replaced with actual message handling logic to control the server's behavior.

// Handle process messages if needed
process.on('message', (message) => {
  // Handle any messages from the parent process
-  console.log('Received message:', message)
+  log.info('Received message from parent process:', message)
+  
+  if (typeof message === 'object' && message !== null) {
+    // Handle different message types
+    switch (message.type) {
+      case 'shutdown':
+        log.info('Shutdown command received')
+        process.exit(0)
+        break;
+      case 'restart':
+        log.info('Restart command received')
+        // Implement restart logic here
+        break;
+      default:
+        log.warn('Unknown message type:', message.type)
+    }
+  }
})
src/productivityUtils.ts (1)

166-166: Consider making the sync operation asynchronous.

The current implementation synchronously calls the sync operation, which could potentially impact performance if the sync process is slow or the network is unreliable.

-  optiSync.sendUpdates(timeTrackers, getDeepWorkHours());
+  // Run sync operation asynchronously to avoid blocking
+  Promise.resolve().then(() => {
+    try {
+      optiSync.sendUpdates(timeTrackers, getDeepWorkHours());
+    } catch (error) {
+      log.error('Failed to sync updates:', error);
+    }
+  });

Alternative implementation if the sendUpdates method already returns a Promise:

-  optiSync.sendUpdates(timeTrackers, getDeepWorkHours());
+  optiSync.sendUpdates(timeTrackers, getDeepWorkHours())
+    .catch(error => {
+      log.error('Failed to sync updates:', error);
+    });
src/server.ts (2)

35-41: Return negative acknowledgement to client on failure

When the forward fails you only log an error; the client remains unaware and may discard its buffered data.
Send a SYNC_NACK or an error message so the client can retry or fall back to HTTP.

-  log.error('OptiSync Server: Failed to forward to Railway', response.status)
+  const errorMsg = {
+    type: 'SYNC_NACK',
+    data: { status: response.status }
+  }
+  if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(errorMsg))
+  log.error('OptiSync Server: Failed to forward to Railway', response.status)

42-45: Include stack trace in error logs for easier debugging

log.error('Error processing message', err) only prints the generic object.
Use err instanceof Error ? err.stack : err to capture the full trace.

vite.main.config.ts (1)

19-26: Consider extracting the growing external list

Every added dependency must be duplicated here. Rollup allows a function:
external: (id) => builtinModules.includes(id) || /^node:/.test(id) || /^(electron|ws|conf|electron-store)$/.test(id)
This keeps the list maintainable as more modules are introduced.

src/optiSync.ts (1)

25-33: Reconnect strategy grows unbounded—cap the delay

setTimeout(() => this.connect(), 5000 * this.reconnectAttempts) yields 25 s after 5 tries but keeps growing if the socket opens then closes again.
Reset reconnectAttempts on every successful connection and failed attempt cap the delay:

- setTimeout(() => this.connect(), 5000 * this.reconnectAttempts)
+ const delay = Math.min(30_000, 5_000 * this.reconnectAttempts)
+ setTimeout(() => this.connect(), delay)
src/main.ts (2)

729-738: Variable shadowing hides module‑level deepWorkHours

const deepWorkHours = getDeepWorkHours() shadows the outer deepWorkHours object, which may confuse readers and tooling.
Rename the inner constant, e.g. currentHours.


472-475: Ensure graceful shutdown of persistence timers

Inside before-quit you stop the WebSocket server but not the persistenceInterval; residual async work can prevent Electron from exiting or log unhandled rejections.

app.on('before-quit', () => {
  isQuitting = true
  stopWebSocketServer()
+ stopPersistenceInterval()
})
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a70092b and 89fe61f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • package.json (5 hunks)
  • postcss.config.js (1 hunks)
  • src/main.ts (7 hunks)
  • src/optiSync.ts (1 hunks)
  • src/productivityUtils.ts (2 hunks)
  • src/server.ts (1 hunks)
  • src/wsServer.ts (1 hunks)
  • vite.main.config.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/productivityUtils.ts (2)
src/optiSync.ts (1)
  • optiSync (66-66)
src/main.ts (1)
  • getDeepWorkHours (481-484)
src/server.ts (1)
src/renderer/src/config.ts (1)
  • API_BASE_URL (3-3)
src/main.ts (1)
src/types.ts (4)
  • SiteTimeTracker (29-36)
  • User (2-8)
  • StoreSchema (10-28)
  • DeepWorkHours (38-46)
🔇 Additional comments (6)
postcss.config.js (1)

1-1: LGTM - Valid module format change.

The switch from ES module syntax to CommonJS module syntax is appropriate and aligns with the build system updates.

src/productivityUtils.ts (1)

11-12: LGTM!

These imports provide the necessary functionality for syncing time tracking data.

package.json (3)

11-12: LGTM!

The updated lint command and new build script for handling multiple Vite configurations are appropriate improvements.


30-30: LGTM!

Adding WebSocket type definitions is appropriate for the new WebSocket functionality.


60-61: LGTM!

The added dependencies for WebSocket functionality and HTTP fetching are appropriate for the new synchronization features.

Also applies to: 71-72, 82-86

src/main.ts (1)

98-103: join(__dirname, 'wsServer.js') likely breaks in production build

After Vite bundles code to .vite/build, runtime files live inside app.asar.
Use path.resolve(__dirname, 'wsServer.js') and ensure the file is included in the files section of package.json / extraFiles of electron‑builder.

Comment thread src/wsServer.ts
Comment on lines +1 to +10
import { startServer } from './server'

// Start the WebSocket server
startServer()

// Handle process messages if needed
process.on('message', (message) => {
// Handle any messages from the parent process
console.log('Received message:', message)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and proper logging for WebSocket server startup.

The WebSocket server lacks error handling and proper shutdown logic. Using console.log also bypasses the electron-log system used elsewhere in the application.

import { startServer } from './server'
+import log from 'electron-log/node.js'

// Start the WebSocket server
-startServer()
+try {
+  startServer()
+  log.info('WebSocket server started successfully')
+} catch (error) {
+  log.error('Failed to start WebSocket server:', error)
+}

// Handle process messages if needed
process.on('message', (message) => {
  // Handle any messages from the parent process
-  console.log('Received message:', message)
+  log.info('Received message from parent process:', message)
})

+// Handle graceful shutdown
+process.on('SIGINT', () => {
+  log.info('Shutting down WebSocket server...')
+  process.exit(0)
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { startServer } from './server'
// Start the WebSocket server
startServer()
// Handle process messages if needed
process.on('message', (message) => {
// Handle any messages from the parent process
console.log('Received message:', message)
})
import { startServer } from './server'
import log from 'electron-log/node.js'
// Start the WebSocket server
try {
startServer()
log.info('WebSocket server started successfully')
} catch (error) {
log.error('Failed to start WebSocket server:', error)
}
// Handle process messages if needed
process.on('message', (message) => {
// Handle any messages from the parent process
log.info('Received message from parent process:', message)
})
// Handle graceful shutdown
process.on('SIGINT', () => {
log.info('Shutting down WebSocket server...')
process.exit(0)
})

Comment thread src/productivityUtils.ts
}
timeTrackers.push(tracker)
}
optiSync.sendUpdates(timeTrackers, getDeepWorkHours());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling around sync operations.

The sync operation might fail for various reasons (e.g., network issues, server unavailability), and currently there's no error handling.

-  optiSync.sendUpdates(timeTrackers, getDeepWorkHours());
+  try {
+    optiSync.sendUpdates(timeTrackers, getDeepWorkHours());
+  } catch (error) {
+    log.error('Failed to sync updates:', error);
+    // Consider adding a retry mechanism or queueing system for failed sync attempts
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
optiSync.sendUpdates(timeTrackers, getDeepWorkHours());
try {
optiSync.sendUpdates(timeTrackers, getDeepWorkHours());
} catch (error) {
log.error('Failed to sync updates:', error);
// Consider adding a retry mechanism or queueing system for failed sync attempts
}

Comment thread package.json
Comment on lines +85 to +86
"world-countries": "^5.1.0",
"ws": "^8.18.1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify security implications of WebSocket implementation.

While the WebSocket dependencies are appropriate, it's important to ensure that the implementation follows security best practices.


🌐 Web query:

What are the security best practices for WebSocket servers in Electron applications?

💡 Result:

Security Best Practices for WebSocket Servers in Electron Applications

Ensuring secure WebSocket communication in Electron applications requires careful consideration of both WebSocket-specific and Electron-specific security risks. Below are best practices derived from current, reputable sources.

Use Secure WebSocket Protocol (WSS)

  • Always use wss:// (WebSocket Secure) instead of ws:// to encrypt data in transit, mitigating risks such as man-in-the-middle attacks[1][2][4][7].

Input and Output Validation

  • Validate all incoming data from clients before processing it to prevent injection attacks (such as SQL and OS command injection)[1][2][7].
  • Equally important, validate server-sent data on the client side. Never assign received content directly to the DOM or evaluate it as code; instead, treat all data as untrusted and parse JSON using JSON.parse()[1][2][7].

Authentication and Authorization

  • Require authentication before establishing a WebSocket connection. Do not rely on the WebSocket protocol itself for this; instead, authenticate users before initiating the handshake (e.g., by passing a session token or using secure cookies)[4][5][7][9].
  • Implement authorization controls to ensure that only permitted users can perform sensitive actions or access restricted data via WebSockets[1][5][7].

Origin and CSRF Protection

  • Validate the Origin header on the server to allow connections only from trusted domains, preventing cross-site WebSocket hijacking[4].
  • Use session-specific tokens (such as CSRF tokens) that must be included in connection requests, making it harder for attackers to forge WebSocket requests[4].

Rate Limiting and Resource Controls

  • Apply rate limiting to limit the number of requests or messages per user/IP per time window. This helps mitigate Denial of Service (DoS) and brute-force attacks[2][4][5][7].
  • Restrict the number of concurrent connections and enforce reasonable message size limits to prevent resource exhaustion attacks[4][5].

Avoid Dangerous Tunneling

  • Do not tunnel arbitrary, sensitive TCP services (such as databases) through WebSockets. This can escalate attacks, especially in case of cross-site scripting (XSS) vulnerabilities, enabling attackers to pivot through your application[1][2][7].

Enforce Secure Electron Configuration

  • In Electron, always disable nodeIntegration and enable contextIsolation in renderer processes that display remote or untrusted content to prevent attackers from executing native Node.js code via web content[8][10].
  • Define a strict Content Security Policy (CSP) to restrict what scripts can run and what resources can be loaded in your app[8].
  • Enable process sandboxing, do not disable web security, and make use of Electron’s permission request handlers for any session loading remote content[8][10].

Patch and Test Regularly

  • Keep Electron, dependencies, and your server libraries up-to-date to protect against known vulnerabilities[8][10].
  • Regularly test your WebSocket endpoints and Electron configuration for misconfigurations and vulnerabilities using static analysis and pentesting tools[10].

Summary Table: WebSocket Security Measures for Electron Apps

Security Measure WebSocket Electron
Use Secure Protocol wss:// only -
Input/Output Validation Validate all data -
Authentication/Authorization Before connection -
Origin Header/CSRF Tokens Check Origin, use tokens -
Rate Limiting/Resource Controls Yes -
Avoid Dangerous Tunneling Yes -
Disable Node Integration in Renderer - Yes
Enable Context Isolation - Yes
Strict Content Security Policy - Yes
Keep Dependencies Updated Yes Yes

Blockquote:

"Always use the WebSocket Secure (wss) protocol instead of WebSocket (ws) to ensure data exchanged between the client and server is encrypted...Only allow authenticated users to open a WebSocket connection...On the server side, validate the origin of incoming WebSocket connections. Only allow connections from trusted domains by checking the Origin header. Reject connections from unexpected or unauthorized origins." [4]

Implementing these practices will help ensure the security of your WebSocket server within an Electron application, safeguarding your users and data against a wide range of common and advanced threats.

Citations:


Review WebSocket security implementation

Please verify that your use of the ws dependency follows Electron and WebSocket security best practices:

  • Use wss:// with a valid TLS certificate to encrypt all traffic.
  • Authenticate clients before the WebSocket handshake (e.g., session tokens or secure cookies).
  • Validate the Origin header against a trusted whitelist to prevent cross‐site hijacking.
  • Sanitize and validate every incoming message; never execute or inject untrusted data.
  • Enforce rate limits, maximum message sizes and connection limits to guard against DoS.
  • In your Electron BrowserWindow: disable nodeIntegration and enable contextIsolation.
  • Define a strict Content Security Policy for any loaded web content.
  • Keep Electron, ws, and all related dependencies up‑to‑date and perform regular security audits.

Audit these locations:

  • package.json (lines 85–86: "ws": "^8.18.1")
  • Your WebSocket server setup in the main process (e.g., src/main.js)
  • Your client–side WebSocket handling in renderer scripts (e.g., src/renderer.js)

Comment thread src/server.ts Outdated
Comment on lines +8 to +10
const wss = new WebSocketServer({ port: 3001 })
const API_BASE_URL = 'https://backend-production-5eec.up.railway.app'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid hard‑coding port & backend URL; read from env / shared config instead

Binding the server to port 3001 and duplicating the backend URL string couples runtime behaviour to the source and makes it difficult to run multiple instances, e2e tests or change environments (staging, dev, prod).
Reuse the existing API_BASE_URL from renderer/src/config.ts (or move it to a common config.ts) and read the port from process.env.PORT with a sensible default.

-const wss = new WebSocketServer({ port: 3001 })
-const API_BASE_URL = 'https://backend-production-5eec.up.railway.app'
+const PORT = Number(process.env.OPTI_SYNC_PORT ?? 3001)
+const wss = new WebSocketServer({ port: PORT })
+import { API_BASE_URL } from './config'              // <- shared constant

Committable suggestion skipped: line range outside the PR's diff.

Comment thread src/server.ts Outdated
Comment on lines +25 to +27
date: dayjs().format('dddd'),
url: t.url.slice(0, 200),
title: t.title.slice(0, 100),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

date & today should be an ISO calendar date, not just weekday

dayjs().format('dddd') returns strings like "Wednesday".
This loses year/week context and will overwrite previous weeks’ data. Prefer an ISO date (YYYY‑MM‑DD) and let the backend derive weekday if necessary.

- date: dayjs().format('dddd'),
+ date: dayjs().format('YYYY-MM-DD'),
...
- today: dayjs().format('dddd'),
+ today: dayjs().format('YYYY-MM-DD'),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
date: dayjs().format('dddd'),
url: t.url.slice(0, 200),
title: t.title.slice(0, 100),
date: dayjs().format('YYYY-MM-DD'),
url: t.url.slice(0, 200),
title: t.title.slice(0, 100),

Comment thread src/server.ts Outdated
Comment on lines +23 to +33
dailyData: trackers.map((t: any) => ({
username: 'user', // Updated dynamically in main.ts
date: dayjs().format('dddd'),
url: t.url.slice(0, 200),
title: t.title.slice(0, 100),
timeSpent: t.timeSpent
})),
deepWorkHours,
today: dayjs().format('dddd'),
username: 'user'
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Do not hard‑code "user"; pass the real username from the client

The payload always transmits username: 'user', so data for every account will be merged under the same identifier on the server.
Either include username inside the SYNC_UPDATE message or fetch it from a secure process‑wide store and inject it here.

-username: 'user'
+username: msg.data.username   // or another reliable source

I've started inspecting src/server.ts to confirm how usernames are handled. Fetching the relevant lines…


🏁 Script executed:

#!/bin/bash
# Locate the mapping that hard-codes "user" and inspect surrounding context
rg -n "dailyData" -R src
sed -n '1,200p' src/server.ts | nl -ba

Length of output: 561


🏁 Script executed:

#!/bin/bash
# Locate server.ts in the repository
fd server.ts

# Show occurrences of hard‑coded username in src/
rg -n "username: 'user'" -C3 src

# Inspect where SYNC_UPDATE messages are handled
rg -n "SYNC_UPDATE" -C3 src

# Check for any use of msg.data.username or similar
rg -n "data.username" -C3 src

Length of output: 1692


Pass the real username through the SYNC_UPDATE payload

The server currently hard‑codes username: 'user' in two places, causing all accounts’ data to be merged under the same identifier. Since neither msg.data.username nor a secure store is used, you must propagate the actual username from the client.

• In src/server.ts (around lines 23–33), replace both hard‑coded fields:

- username: 'user', // Updated dynamically in main.ts
+ username: msg.data.username- username: 'user'
+ username: msg.data.username

• In src/optiSync.ts (where SYNC_UPDATE is sent), include the username in the payload:

this.ws.send(
  JSON.stringify({
    type: 'SYNC_UPDATE',
-   data: { trackers: deltaTrackers, deepWorkHours }
+   data: { trackers: deltaTrackers, deepWorkHours, username: this.username }
  })
)

Ensure that this.username (or another reliable source) is set on the client before invoking SYNC_UPDATE.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dailyData: trackers.map((t: any) => ({
username: 'user', // Updated dynamically in main.ts
date: dayjs().format('dddd'),
url: t.url.slice(0, 200),
title: t.title.slice(0, 100),
timeSpent: t.timeSpent
})),
deepWorkHours,
today: dayjs().format('dddd'),
username: 'user'
})
dailyData: trackers.map((t: any) => ({
username: msg.data.username,
date: dayjs().format('dddd'),
url: t.url.slice(0, 200),
title: t.title.slice(0, 100),
timeSpent: t.timeSpent
})),
deepWorkHours,
today: dayjs().format('dddd'),
username: msg.data.username
})

Comment thread src/optiSync.ts
Comment on lines +41 to +63
sendUpdates(trackers: SiteTimeTracker[], deepWorkHours: DeepWorkHours) {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return

// Compute delta: new or updated trackers
const deltaTrackers = trackers.filter((t) => {
const last = this.lastSyncTrackers.find((lt) => lt.title === t.title && lt.url === t.url)
return (
!last ||
last.timeSpent !== t.timeSpent ||
last.lastActiveTimestamp !== t.lastActiveTimestamp
)
})

if (deltaTrackers.length > 0) {
this.ws.send(
JSON.stringify({
type: 'SYNC_UPDATE',
data: { trackers: deltaTrackers, deepWorkHours }
})
)
this.lastSyncTrackers = [...trackers] // Update last synced state
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Queue updates while offline to avoid data loss

sendUpdates silently returns when ws is not open, dropping activity data accumulated during downtime.
Maintain an in‑memory queue and flush on reconnect.

Comment thread src/optiSync.ts
Comment on lines +1 to +3
import { WebSocket } from 'ws'
import { SiteTimeTracker, DeepWorkHours } from './types'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use the global WebSocket in renderer instead of the ws package

The ws library is typically server‑side; bundling it into the renderer adds ~150 KB and native addons for optional deps.
Electron’s renderer & main contexts already provide the WHATWG/WebSocket implementation.

-import { WebSocket } from 'ws'
+// eslint-disable-next-line no-restricted-globals
+const WebSocketImpl: typeof WebSocket =
+  typeof WebSocket !== 'undefined' ? WebSocket : (await import('ws')).WebSocket

Committable suggestion skipped: line range outside the PR's diff.

Comment thread src/main.ts
Comment on lines 725 to 733
if (!persistenceInterval && !isSystemSuspended) {
persistenceInterval = setInterval(
async () => {
const today = dayjs().format('dddd') as keyof DeepWorkHours; // "Wednesday"
const username = user.username;
const deepWorkHours = getDeepWorkHours();
const MIN_TIME_THRESHOLD = 10;
const today = dayjs().format('dddd') as keyof DeepWorkHours // "Wednesday"
const username = user.username
const deepWorkHours = getDeepWorkHours()
const MIN_TIME_THRESHOLD = 10

const filteredTrackers = currentSiteTimeTrackers.filter(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Null‑safety: user may be null when accessing user.username

startPersistenceInterval runs on app.on('ready'), before the user necessarily logged in.
Accessing user.username throws. Guard or early‑return until a user is available.

- const username = user.username
+ if (!user) return
+ const username = user.username
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!persistenceInterval && !isSystemSuspended) {
persistenceInterval = setInterval(
async () => {
const today = dayjs().format('dddd') as keyof DeepWorkHours; // "Wednesday"
const username = user.username;
const deepWorkHours = getDeepWorkHours();
const MIN_TIME_THRESHOLD = 10;
const today = dayjs().format('dddd') as keyof DeepWorkHours // "Wednesday"
const username = user.username
const deepWorkHours = getDeepWorkHours()
const MIN_TIME_THRESHOLD = 10
const filteredTrackers = currentSiteTimeTrackers.filter(
if (!persistenceInterval && !isSystemSuspended) {
persistenceInterval = setInterval(
async () => {
const today = dayjs().format('dddd') as keyof DeepWorkHours // "Wednesday"
if (!user) return
const username = user.username
const deepWorkHours = getDeepWorkHours()
const MIN_TIME_THRESHOLD = 10
const filteredTrackers = currentSiteTimeTrackers.filter(

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
vite.main.config.ts (1)

1-51: 💡 Verification agent

🧩 Analysis chain

This file appears to be obsolete and should be removed

The forge.config.ts file now references vite.main.config.mts instead of this file. Both files contain nearly identical configurations, with this one having more inline comments. To prevent confusion and maintenance issues, consider removing this file since it's being replaced by the .mts version.

This file should be removed as it's been replaced by vite.main.config.mts.


🏁 Script executed:

#!/bin/bash
# Check if both files exist and compare their content
echo "Checking if both configuration files exist..."
if [[ -f "vite.main.config.ts" && -f "vite.main.config.mts" ]]; then
  echo "Both files exist. Comparing content..."
  # Compare content ignoring comments and whitespace
  diff -w <(grep -v "\/\/" vite.main.config.ts) <(grep -v "\/\/" vite.main.config.mts)
  echo "These files have similar functionality but forge.config.ts now references vite.main.config.mts"
else
  echo "One or both files don't exist."
fi

# Check for references to these files in the codebase
echo "Checking for references to vite.main.config.ts..."
grep -r "vite.main.config.ts" --include="*.{ts,js,json}" . || echo "No references found"

echo "Checking for references to vite.main.config.mts..."
grep -r "vite.main.config.mts" --include="*.{ts,js,json}" . || echo "No references found"

Length of output: 1083


Continuing verification to confirm file duplication and references:


🏁 Script executed:

#!/bin/bash
set -e

echo "1) Listing vite.main.config files:"
fd -e ts -e mts vite.main.config || find . -maxdepth 2 -type f \( -name "vite.main.config.ts" -o -name "vite.main.config.mts" \)
echo

echo "2) Comparing content (ignoring comments/blanks):"
grep -v '^\s*//' vite.main.config.ts | grep -v '^\s*$' > /tmp/config_ts.clean
grep -v '^\s*//' vite.main.config.mts | grep -v '^\s*$' > /tmp/config_mts.clean
if diff -w /tmp/config_ts.clean /tmp/config_mts.clean; then
  echo "→ No differences between configs"
else
  echo "→ Detected differences"
fi
echo

echo "3) Searching for references to both files:"
rg "vite\.main\.config\.(ts|mts)" -g "*.{ts,mts,js,json}" || echo "→ No references found"
echo

echo "4) Inspecting forge.config.ts for any implicit references:"
if [[ -f "forge.config.ts" ]]; then
  sed -n '1,200p' forge.config.ts
else
  echo "→ forge.config.ts not found"
fi

Length of output: 4774


Remove duplicate config and update build scripts

vite.main.config.ts and vite.main.config.mts provide identical behavior (the only differences are inline comments), but your forge.config.ts and your package.json build scripts point to different files. To clean this up:

• In package.json (under the “build” script), replace references to vite.main.config.ts with vite.main.config.mts.

"build": "vite build --config vite.main.config.ts && \
           vite build --config vite.renderer.config.mts && \
           vite build --config vite.preload.config.ts",

should become:

"build": "vite build --config vite.main.config.mts && \
           vite build --config vite.renderer.config.mts && \
           vite build --config vite.preload.config.ts",

• Once the build scripts are updated, remove vite.main.config.ts from the repo to avoid confusion and maintenance overhead.

🧹 Nitpick comments (6)
vite.main.config.mts (1)

6-50: Well-structured configuration with good separation of concerns

The configuration properly sets up multiple entry points (main.ts and wsServer.ts) with appropriate externals for Node.js modules and WebSocket dependencies. The Node 20 target aligns well with modern Electron versions.

Consider the following enhancements:

  • Add source maps for development builds to improve debugging
  • Consider conditional emptyOutDir based on environment to avoid stale files
  • Add more descriptive comments for excluded dependencies
export default defineConfig({
  build: {
    target: 'node20',
    emptyOutDir: false,
    outDir: '.vite/build',
+   sourcemap: process.env.NODE_ENV === 'development' ? 'inline' : false,
    lib: {
      entry: {
        main: 'src/main.ts',
        wsServer: 'src/wsServer.ts'
      },
      formats: ['cjs']
    },
vite.preload.config.ts (1)

4-10: Appropriate preload configuration added

Good addition of explicit configuration for the preload script, aligning with the Node 20 target used in other configurations.

Consider these optional enhancements:

  1. Explicitly specify the output format (e.g., 'cjs')
  2. Add external dependencies if the preload script uses any
  3. Consider source maps for development builds
export default defineConfig({
  build: {
    target: 'node20',
    outDir: '.vite/build/preload',
-   emptyOutDir: false
+   emptyOutDir: false,
+   sourcemap: process.env.NODE_ENV === 'development' ? 'inline' : false,
+   lib: {
+     formats: ['cjs']
+   }
  }
})
src/renderer/src/Login.tsx (2)

34-42: Remove debug logging code before production release.

These lifecycle hooks are being used to log the Electron API object for debugging purposes. While useful during development, this verbose logging should be removed before shipping to production.

-  onMount(() => {
-    console.log("window.electron:", window.electron);
-    console.log("Available methods:", Object.keys(window.electron || {}));
-  });
-  
-  // Alternatively, you can use createEffect if you need reactivity
-  createEffect(() => {
-    console.log("window.electron (from createEffect):", window.electron);
-  });

66-66: Remove debug logging code before production release.

This additional logging of the user object should be removed before shipping to production to avoid exposing sensitive user information in console logs.

-      console.log('User is ', user)
src/preload.ts (1)

40-46: Enhanced window.electron API with sendUserToBackend method.

The exposed Electron API has been improved by adding a dedicated method for sending user data to the main process via IPC, which simplifies the renderer code and centralizes IPC communication logic.

However, consider adding type safety to the user parameter:

- sendUserToBackend: (user: any) => {
+ sendUserToBackend: (user: { id: string; username: string; [key: string]: any }) => {
src/renderer/src/lib/utils.ts (1)

17-22: Good practice keeping old implementation as reference.

Keeping the old implementation as a comment can help during the transition but should be removed once the new implementation is fully tested and stable.

- // export const sendUserToBackend = (user: User) => {
- //   console.log('Sending user data to backend:', user)
- //   const sanitizedUser = JSON.parse(JSON.stringify(user))
- //   console.log('window is ', window)
- //   window?.electron.ipcRenderer.send('login-user', sanitizedUser)
- // }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89fe61f and b82c0b8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • forge.config.ts (3 hunks)
  • package.json (4 hunks)
  • src/main.ts (9 hunks)
  • src/preload.ts (3 hunks)
  • src/renderer/src/Login.tsx (3 hunks)
  • src/renderer/src/lib/utils.ts (2 hunks)
  • src/server.ts (1 hunks)
  • src/wsServer.ts (1 hunks)
  • vite.main.config.mts (1 hunks)
  • vite.main.config.ts (1 hunks)
  • vite.preload.config.mts (1 hunks)
  • vite.preload.config.ts (1 hunks)
  • vite.renderer.config.mts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • vite.renderer.config.mts
  • vite.preload.config.mts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/wsServer.ts
  • src/server.ts
  • package.json
  • src/main.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/renderer/src/lib/utils.ts (1)
src/preload.ts (1)
  • ElectronAPI (6-21)
🔇 Additional comments (8)
forge.config.ts (4)

8-8: Good addition of AutoUnpackNativesPlugin

Adding this plugin is essential for handling native modules like 'ws', 'bufferutil', and 'utf-8-validate' that are used for WebSocket functionality.


62-63: Appropriate migration to .mts file extensions

Updating the config file extensions from .ts to .mts reflects the proper use of ES modules in TypeScript, which aligns with modern JavaScript practices.

Also applies to: 67-68


19-20: Minor whitespace fix in osxSign.identity

Good cleanup of extra whitespace.


74-75: Config file extension update for renderer

Consistent update of the renderer config file extension.

src/renderer/src/Login.tsx (1)

2-2: Import statement updated to include lifecycle hooks.

The import statement has been expanded to include onMount and createEffect lifecycle hooks from 'solid-js', which will be used for debugging the Electron API exposure.

src/preload.ts (1)

3-3: Import updated to include ipcRenderer.

The import statement now includes ipcRenderer which is necessary for IPC communication between renderer and main processes.

src/renderer/src/lib/utils.ts (2)

5-11: Improved type safety with global Window interface extension.

Adding proper TypeScript type definitions for the window.electron property improves type safety and developer experience by providing autocomplete and type checking for Electron API usage throughout the renderer code.


27-27: Updated to use the new preload-exposed API method.

The implementation now correctly uses the dedicated sendUserToBackend method exposed by the preload script instead of directly using ipcRenderer.send, which follows Electron's recommended security practices for context isolation.

Comment on lines 30 to 33
export const stopActivityMonitoring = () => {
console.log('Stopping activity monitoring')
window?.electron.ipcRenderer.send('logout-user')
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Inconsistent IPC pattern for stopActivityMonitoring.

While sendUserToBackend has been updated to use the new preload-exposed method, stopActivityMonitoring still uses the direct ipcRenderer.send approach. For consistency, this should also be updated to use a dedicated method exposed through the preload script.

export const stopActivityMonitoring = () => {
  console.log('Stopping activity monitoring')
-  window?.electron.ipcRenderer.send('logout-user')
+  window?.electron.logoutUser() // This method needs to be added to preload.ts
}

🏁 Script executed:

#!/bin/bash
# Check for direct ipcRenderer usage across the renderer code
echo "Checking for direct ipcRenderer usage in renderer code:"
rg "ipcRenderer\.send" src/renderer/src/

Length of output: 2097


🏁 Script executed:

#!/bin/bash
# Display contents of utils.ts to verify sendUserToBackend implementation
sed -n '1,200p' src/renderer/src/lib/utils.ts

Length of output: 1537


🏁 Script executed:

#!/bin/bash
# Locate the ElectronAPI interface and check for sendUserToBackend/logoutUser methods
echo "🔍 Inspecting ElectronAPI in preload:"
rg -n "interface ElectronAPI" -A 20

echo -e "\n🔍 Checking for sendUserToBackend in preload:"
rg -n "sendUserToBackend" -A 5

echo -e "\n🔍 Checking for logoutUser in preload:"
rg -n "logoutUser" -A 5

Length of output: 5850


Standardize IPC for stopping activity monitoring

We still have a direct ipcRenderer.send('logout-user') call in stopActivityMonitoring, whereas sendUserToBackend now uses a preload‐exposed API. Let’s align these patterns:

src/preload.ts
– In your api object (and ElectronAPI interface), add a logoutUser method:

logoutUser: (): void => {
  console.log('Preload: Logging out user')
  ipcRenderer.send('logout-user')
},

Ensure logoutUser is declared on ElectronAPI and exposed via contextBridge.exposeInMainWorld('electron', api).

src/renderer/src/lib/utils.ts
– Update stopActivityMonitoring to use the new API:

export const stopActivityMonitoring = () => {
  console.log('Stopping activity monitoring')
- window?.electron.ipcRenderer.send('logout-user')
+ window?.electron.logoutUser()
}

This keeps all renderer‐to‐main communications encapsulated in the preload layer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
README.md (1)

48-55: Enhance Demo section with example logs
Right now the demo line is very brief. Consider including a short log snippet or screenshot so users can verify that OptiSync is running as expected. For example:

#### Demo
```bash
2025-04-15T10:32:20.123Z [optiSync] Connected to ws://localhost:3001
2025-04-15T10:32:20.456Z [optiSync] Sent delta update: {"site":"github.com","duration":120}
This makes the sync feature more tangible to newcomers.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b82c0b8ab1f8722ee3f824a947199c06ad33ccfd and 6569914356009ee316ad5595776ae5aaac43e7b5.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `README.md` (1 hunks)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread README.md
Comment on lines +48 to 55
# deepFocus with OptiSync
Electron app with WebSocket sync for productivity tracking.
## Setup
1. `pnpm install`
2. `pnpm run start`
## Demo
Logs show WebSocket sync of SiteTimeTracker updates.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Inconsistent heading level, branding, and missing Table of Contents entry
The new section is introduced as a top‐level H1 (# deepFocus with OptiSync) with lowercase “d” and isn’t linked in the existing Table of Contents. To maintain hierarchy and branding consistency, and to ensure discoverability, please:

  1. Rename to a level‑3 heading under “Getting Started” (e.g. ### DeepFocus with OptiSync), matching the project’s DeepFocus casing.
  2. Adjust sub‑headings to fit (e.g. #### Setup, #### Demo).
  3. Add a corresponding entry to the TOC.

Proposed diff:

@@ -16,6 +16,7 @@
 - [Getting Started](#getting-started)
+- [DeepFocus with OptiSync](#deepfocus-with-optisync)

 ## Getting Started
@@ -48,8 +49,9 @@
-# deepFocus with OptiSync
-Electron app with WebSocket sync for productivity tracking.
+### DeepFocus with OptiSync
+Electron app with WebSocket synchronization for productivity tracking.

-## Setup
-1. `pnpm install`
-2. `pnpm run start`
+#### Setup
+```bash
+pnpm install
+pnpm run start
+```

-## Demo
-Logs show WebSocket sync of SiteTimeTracker updates.
+#### Demo
+Logs show WebSocket synchronization of SiteTimeTracker updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant