Skip to content

Move minecraft protocol into worker! #319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 91 commits into
base: next
Choose a base branch
from
Open

Move minecraft protocol into worker! #319

wants to merge 91 commits into from

Conversation

zardoy
Copy link
Owner

@zardoy zardoy commented Mar 26, 2025

PR Type

+10% perf on high load servers (1k packets from server) like hypixel: packet serialization / deserialization now happens in its own thread
Enhancement, Bug fix


Description

  • Introduced a new protocolWorker module for handling Minecraft protocol tasks.

    • Added protocolMain.ts and protocolWorker.ts for managing protocol-related operations.
    • Implemented a worker-based architecture for offloading protocol handling.
  • Refactored authentication flow to streamline Microsoft account integration.

    • Moved microsoftAuthflow logic to protocolWorker/microsoftAuthflow.
    • Introduced authFlowMainThread and getAuthData for improved token management.
  • Improved version handling and utility functions.

    • Moved toMajorVersion utility to a more appropriate location.
    • Enhanced version parsing and comparison logic.
  • Removed unused or redundant code for better maintainability.

    • Cleaned up legacy authentication logic and unused variables.
    • Simplified packet validation and logging mechanisms.

Changes walkthrough 📝

Relevant files
Enhancement
9 files
simpleUtils.ts
Added `toMajorVersion` utility function                                   
+6/-0     
worldrendererCommon.ts
Updated imports to include `toMajorVersion`                           
+1/-2     
index.ts
Refactored connection logic and removed unused code           
+35/-87 
microsoftAuthflow.ts
Refactored Microsoft authentication flow                                 
+62/-67 
protocolMain.ts
Added main protocol handling logic                                             
+186/-0 
protocolWorker.ts
Introduced worker for protocol-related tasks                         
+177/-0 
minecraftData.ts
Updated imports for `toMajorVersion`                                         
+1/-1     
utils.ts
Removed `toMajorVersion` and cleaned up utilities               
+0/-6     
AppStatusProvider.tsx
Updated imports for refactored authentication flow             
+1/-1     
Bug fix
1 files
mc-protocol.ts
Simplified packet validation logic                                             
+4/-10   
Configuration changes
1 files
protocolWorkerGlobals.ts
Added global configurations for protocol worker                   
+4/-0     

Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Summary by CodeRabbit

    • New Features

      • Enhanced connection performance and stability with improved background processing and an alternative ping mechanism.
      • Enabled default protocol optimizations for smoother interactions.
      • Introduced a new protocol worker for managing Minecraft connections, enhancing modularity and responsiveness.
      • Added a new method for obtaining the protocol client, improving connection management.
      • Added a new function for converting version strings to major version format.
    • Refactor

      • Streamlined authentication and connection logic by removing legacy Microsoft authentication components.
      • Improved asynchronous communication between main processes and background workers for more responsive operation.
      • Consolidated import statements for better readability and organization.
      • Added a new property for protocol worker optimization settings.
      • Updated method signatures to allow for asynchronous handling of responses.

    @zardoy zardoy changed the base branch from nextgen-physics to next April 11, 2025 23:57
    @zardoy
    Copy link
    Owner Author

    zardoy commented Apr 11, 2025

    /deploy

    @zardoy
    Copy link
    Owner Author

    zardoy commented Apr 11, 2025

    /deploy

    Copy link

    Deployed to Vercel Preview: https://prismarine-l8h0tc7ws-zaro.vercel.app
    Playground
    Storybook

    Copy link

    Deployed to Vercel Preview: https://prismarine-fhqbc3qqq-zaro.vercel.app
    Playground
    Storybook

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 3

    🧹 Nitpick comments (11)
    src/protocolWorker/protocolWorkerGlobals.ts (1)

    1-1: Consider removing or documenting commented code

    This commented code doesn't seem to serve a purpose. If it's important for understanding the module, please add an explanatory comment; otherwise, consider removing it.

    src/protocolWorker/protocolWorker.ts (4)

    14-15: Address the TODO.

    A TODO comment mentions using another strategy by sending all events. If this is still planned, consider creating an issue or backlog item to track it, ensuring it doesn’t get overlooked.


    104-121: Avoid potential concurrency pitfalls when modifying globals.

    The registeredChannels array is globally tracked, and updates happen in the call(...) method. While workers run single-threaded, you may wish to encapsulate channel management within a separate class or function to avoid accidental side effects and maintain clearer state management.


    123-134: Consider a standard ping approach.

    pingProxy manually sends a string message and listens for a “pong” event. Ensure that incoming messages of the same format do not cause collisions or security issues. Alternatively, a standard keepalive approach may reduce the complexity of custom ping/pong logic.


    298-299: Address important TODO regarding socket close events.

    A comment notes that the socket’s close event is never triggered. Investigate whether this stems from the underlying proxy or the environment. This may lead to resource leaks or unexpected behavior.

    src/index.ts (2)

    124-127: Refactor away from reduce().

    The pipeline warns against Array#reduce() at line 124 (unicorn/no-array-reduce). Consider using a simple loop, forEach, or Object.fromEntries to construct serverSettingsQs:

    -const serverSettingsQs = serverSettingsQsRaw.map(x => x.split(':')).reduce<Record<string, string>>((acc, [key, value]) => {
    -  acc[key] = JSON.parse(value)
    -  return acc
    -}, {})
    +const serverSettingsQsEntries = serverSettingsQsRaw.map(x => x.split(':')).map(([k, v]) => [k, JSON.parse(v)])
    +const serverSettingsQs = Object.fromEntries(serverSettingsQsEntries)
    🧰 Tools
    🪛 GitHub Actions: CI

    [warning] 124-124: Array#reduce() is not allowed unicorn/no-array-reduce


    491-493: Remove the empty block or add a comment.

    The pipeline flags the empty else {} statement here (no-empty). Consider removing it entirely or documenting why it is intentionally empty.

    } else {
    -}
    +// else condition intentionally omitted for future extension ...
    }
    🧰 Tools
    🪛 GitHub Check: build-and-deploy

    [failure] 491-491:
    Empty block statement

    🪛 ESLint

    [error] 491-493: Empty block statement.

    (no-empty)

    🪛 GitHub Actions: CI

    [error] 491-491: Empty block statement no-empty

    renderer/viewer/lib/workerProxy.ts (1)

    1-1: Clarify the return type instead of using void in a union.
    The usage of void | Promise<any> can be confusing and is flagged by static analysis. Consider using something like Promise<void> | Promise<any> or returning undefined in synchronous cases to avoid ambiguity.

    -export function createWorkerProxy<T extends Record<string, (...args: any[]) => void | Promise<any>>> (handlers: T, channel?: MessagePort): { __workerProxy: T } {
    +export function createWorkerProxy<T extends Record<string, (...args: any[]) => Promise<any> | undefined>> (handlers: T, channel?: MessagePort): { __workerProxy: T } {
    🧰 Tools
    🪛 Biome (1.9.4)

    [error] 1-1: void is confusing inside a union type.

    Unsafe fix: Use undefined instead.

    (lint/suspicious/noConfusingVoidType)

    src/protocolWorker/protocolMain.ts (1)

    52-61: Terminate the Worker explicitly when done.
    You have logic to terminate the worker when the bot ends or if bot is not available, but there’s no fallback for unexpected errors or if the client is reinitialized multiple times. Consider adding robust cleanup or referencing the Worker in a higher-level lifecycle.

    src/protocolWorker/microsoftAuthflow.ts (2)

    20-20: Consider returning strongly-typed data structures.
    The function signature returns an object with a shallow type but sets everything to any. You may benefit from specifying structured interfaces to make the code more maintainable.


    115-125: Ensure the message event listener is cleaned up.
    This function attaches a 'message' event listener on the worker but doesn’t remove it upon completing the auth flow. If called multiple times, it may cause memory leaks or unexpected behavior.

    worker.addEventListener('message', function handler({ data }) {
      if (data.type === 'authFlow') {
        authData.setConnectingVersion(data.version)
        resolve()
    +   worker.removeEventListener('message', handler)
      }
    })
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 70557a6 and 980779c.

    📒 Files selected for processing (15)
    • .eslintrc.json (1 hunks)
    • renderer/viewer/lib/simpleUtils.ts (1 hunks)
    • renderer/viewer/lib/workerProxy.ts (3 hunks)
    • renderer/viewer/lib/worldrendererCommon.ts (1 hunks)
    • src/index.ts (9 hunks)
    • src/mineflayer/mc-protocol.ts (1 hunks)
    • src/mineflayer/plugins/ping.ts (1 hunks)
    • src/optionsStorage.ts (1 hunks)
    • src/protocolWorker/microsoftAuthflow.ts (6 hunks)
    • src/protocolWorker/protocolMain.ts (1 hunks)
    • src/protocolWorker/protocolWorker.ts (1 hunks)
    • src/protocolWorker/protocolWorkerGlobals.ts (1 hunks)
    • src/react/AppStatusProvider.tsx (1 hunks)
    • src/shims/minecraftData.ts (1 hunks)
    • src/utils.ts (0 hunks)
    💤 Files with no reviewable changes (1)
    • src/utils.ts
    🧰 Additional context used
    🧬 Code Graph Analysis (5)
    src/protocolWorker/protocolWorker.ts (3)
    renderer/viewer/lib/workerProxy.ts (1)
    • createWorkerProxy (1-19)
    src/mineflayer/websocket-core.ts (1)
    • getWebsocketStream (17-54)
    src/mineflayer/minecraft-protocol-extra.ts (1)
    • validatePacket (57-78)
    renderer/viewer/lib/simpleUtils.ts (1)
    renderer/viewer/lib/worldrendererCommon.ts (1)
    • version (155-157)
    src/index.ts (2)
    src/parseServerAddress.ts (1)
    • parseServerAddress (3-46)
    src/protocolWorker/protocolMain.ts (1)
    • getProtocolClientGetter (32-211)
    src/protocolWorker/protocolMain.ts (7)
    src/protocolWorker/protocolWorker.ts (1)
    • PROXY_WORKER_TYPE (36-135)
    src/connect.ts (1)
    • ConnectOptions (13-42)
    src/parseServerAddress.ts (1)
    • ParsedServerAddress (48-54)
    src/protocolWorker/microsoftAuthflow.ts (2)
    • getAuthData (20-113)
    • authFlowMainThread (115-164)
    src/appStatus.ts (1)
    • setLoadingScreenStatus (5-35)
    renderer/viewer/lib/workerProxy.ts (1)
    • useWorkerProxy (32-76)
    src/appParams.ts (1)
    • appQueryParams (77-86)
    src/protocolWorker/microsoftAuthflow.ts (5)
    src/connect.ts (1)
    • ConnectOptions (13-42)
    src/react/SignInMessageProvider.tsx (1)
    • signInMessageState (6-12)
    src/react/serversStorage.ts (1)
    • updateAuthenticatedAccountData (82-86)
    src/react/NotificationProvider.tsx (1)
    • showNotification (22-37)
    src/appStatus.ts (1)
    • setLoadingScreenStatus (5-35)
    🪛 GitHub Actions: CI
    src/index.ts

    [warning] 124-124: Array#reduce() is not allowed unicorn/no-array-reduce


    [error] 491-491: Empty block statement no-empty

    🪛 Biome (1.9.4)
    renderer/viewer/lib/workerProxy.ts

    [error] 1-1: void is confusing inside a union type.

    Unsafe fix: Use undefined instead.

    (lint/suspicious/noConfusingVoidType)


    [error] 63-63: Do not add then to an object.

    (lint/suspicious/noThenProperty)

    🔇 Additional comments (16)
    renderer/viewer/lib/simpleUtils.ts (1)

    36-41: Good implementation of the relocated toMajorVersion utility.

    The function provides a clean implementation for extracting major version information and the comment clearly indicates its limitation regarding snapshot versions.

    src/optionsStorage.ts (1)

    69-69: Good addition of the protocol worker configuration option.

    Adding protocolWorkerOptimisation: true as a default setting aligns with the PR objective of enhancing performance through worker-based architecture for Minecraft protocol tasks. Setting it to true by default ensures users immediately benefit from the 10% performance improvement on high-load servers.

    .eslintrc.json (1)

    202-203: LGTM: ESLint rule addition for protocol worker compatibility.

    Disabling the unicorn/relative-url-style rule is appropriate for this codebase, especially when working with protocol-related code that may need flexibility in URL formatting.

    src/react/AppStatusProvider.tsx (1)

    7-7: Correctly updated import path to reflect new module structure.

    The import path update for getProxyDetails aligns with the PR's architecture changes, moving Microsoft authentication flow logic to the new protocol worker module.

    src/protocolWorker/protocolWorkerGlobals.ts (1)

    3-6: Good setup for Worker environment

    Appropriate polyfills for the web worker environment. The ESLint disable is properly explained, and the TypeScript type assertion is necessary to handle the Node.js compatibility expectations in a browser environment.

    src/shims/minecraftData.ts (1)

    2-2: Import path update looks good

    The import path for toMajorVersion has been properly updated to come from the new location in renderer/viewer/lib/simpleUtils. This aligns with the PR's objective of reorganizing utility functions.

    src/mineflayer/plugins/ping.ts (2)

    2-2: Good addition of worker protocol import

    The import of getProtocolWorkerChannel is correctly added to support the new worker-based architecture for handling Minecraft protocol tasks.


    8-11: Well-implemented worker-based ping fallback

    This code correctly implements a fallback for ping functionality when the WebSocket connection isn't available, leveraging the new worker-based architecture. The conditional check is appropriate, and the null coalescing operator handles the case when the protocol worker channel isn't available.

    renderer/viewer/lib/worldrendererCommon.ts (2)

    17-17: Consolidated imports correctly

    The import statement has been correctly updated to import both chunkPos and toMajorVersion from the same module, following good practices for import organization.


    525-525: Successfully using the relocated utility function

    The toMajorVersion function is correctly used here after its relocation to the simpleUtils module, ensuring consistent version handling throughout the application.

    src/mineflayer/mc-protocol.ts (1)

    10-12: Reintroduce or confirm removal of packet validation logic.

    Previously, packet validation calls were removed in favor of simply updating lastPacketTime. If packet validation was deliberately dropped, confirm that security and stability requirements are still met. Otherwise, consider re-adding a lightweight validation step or verifying that the trust boundary for packets is acceptable.

    src/protocolWorker/protocolWorker.ts (3)

    1-8: Confirm Node modules availability in a Worker environment.

    This file imports net from Node, which may not be natively available in web workers. Ensure that the bundling environment supports or mocks such modules, or consider limiting import usage to avoid runtime issues in browsers.


    137-173: Evaluate user prompt flow for Microsoft authentication.

    This section offloads and awaits external messages to complete the MS authentication flow. Consider error handling for timeouts in case the main thread never sends 'authflowResult', preventing a potential indefinite wait.


    44-103: 🛠️ Refactor suggestion

    Check for lost local state with skipWriteLog.

    When data.name is 'write', skipWriteLog is set to true and never reset to false. This one-time override may unintentionally disable logging for all subsequent writes.

     if (data.name === 'write') {
    -  skipWriteLog = true
    +  skipWriteLog = true
    +  setTimeout(() => {
    +    skipWriteLog = false
    +  }, 0)

    Likely an incorrect or invalid review comment.

    src/protocolWorker/protocolMain.ts (1)

    78-152:

    ❓ Verification inconclusive

    Validate the usage of global window properties.
    This block relies on multiple global properties like window.stopPacketsProcessing, window.skipPackets, window.packetTimings, etc. If they are not guaranteed to exist or be valid across different environments, it can lead to runtime errors. Ensure they are declared and maintained carefully or encapsulate them in a local state management structure.


    🏁 Script executed:

    #!/bin/bash
    # Searching for definitions of window.stopPacketsProcessing, window.skipPackets, window.packetTimings
    rg -A 5 'window\.(stopPacketsProcessing|skipPackets|packetTimings)'

    Length of output: 3437


    Validate and encapsulate global window properties usage

    The logic in src/protocolWorker/protocolMain.ts (and related usage in protocolWorker.ts) appears to rely on several global window properties which are lazily assigned (e.g. packetTimings). While the current checks (e.g. ensuring packetTimings exists before assignment and safe guarding array accesses for stopPacketsProcessing and skipPackets) prevent immediate runtime errors in a browser environment, please review the following points:

    • Verify that all global properties (such as stopPacketsProcessing, skipPackets, packetTimings, lastStatsUpdate, and packetsThreadBlocking) are intentionally kept on the global window object. This is especially crucial if the code might be executed in non-browser contexts.
    • Consider encapsulating these properties within a dedicated state management module or object. This can improve maintainability and reduce risks associated with implicit global state reliance.
    • Confirm that any external code or initialization logic guarantees that these properties are set as expected across all environments.

    Please review these suggestions to ensure robust initialization and avoid any unintended side effects.

    src/protocolWorker/microsoftAuthflow.ts (1)

    116-146: Secure usage of tokens.
    Ensure that this code never logs or accidentally discloses sensitive tokens or credentials (particularly in production). Review data flows carefully to prevent PII leakage.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Apr 12, 2025

    /deploy

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 1

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 980779c and b50215b.

    📒 Files selected for processing (1)
    • rsbuild.config.ts (2 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: build-and-deploy
    🔇 Additional comments (1)
    rsbuild.config.ts (1)

    228-229: Good sequence adjustment for service worker generation.

    Adding the patchWorkerImport() call before generateSW() ensures all worker scripts have their import paths properly patched before the service worker is generated, which is essential for the protocol worker to function correctly.

    Copy link

    Deployed to Vercel Preview: https://prismarine-doyllmug7-zaro.vercel.app
    Playground
    Storybook

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 4

    🔭 Outside diff range comments (2)
    src/protocolWorker/protocol.worker.ts (1)

    281-321: 🛠️ Refactor suggestion

    Global state and net.Socket prototype modifications
    wrapClientSocket updates net.Socket.prototype.handleStringMessage and regards a global lastKnownKickReason. If multiple Worker instances or clients run concurrently, these shared modifications can produce race conditions.

    src/protocolWorker/protocolMain.ts (1)

    212-212: 💡 Verification agent

    ❓ Verification inconclusive

    Address build pipeline error
    CI logs indicate "NotFound('worker-rspack-loader')". Confirm that worker-rspack-loader is installed in dependencies and configured in your build.


    🏁 Script executed:

    #!/bin/bash
    # Checking for references to worker-rspack-loader in package.json
    rg 'worker-rspack-loader' package.json

    Length of output: 38


    Action Required: Missing Dependency 'worker-rspack-loader'

    • The search in package.json did not reveal any references to worker-rspack-loader, which likely explains the CI error ("NotFound('worker-rspack-loader')").
    • Please confirm whether this loader is required:
      • If it is needed, add it to your dependencies (e.g., via npm install worker-rspack-loader or the appropriate package manager) and verify that your build (rspack) configuration properly references it.
      • If it isn’t needed, remove or update any build configuration or code references to it in src/protocolWorker/protocolMain.ts (line 212) and related files.
    ♻️ Duplicate comments (1)
    rsbuild.config.ts (1)

    264-277: 🛠️ Refactor suggestion

    The worker import path patching has potential edge cases.

    The function effectively replaces static import paths with dynamic ones based on the current URL, which is good for deployment flexibility. However, there are some concerns:

    1. The regex replacement is brittle and assumes a specific format for import paths
    2. The check for multiple importScripts calls may not work as expected - match?.length counts capture groups, not matches
    3. No directory existence check before trying to read from it
    const patchWorkerImport = () => {
    +   if (!fs.existsSync('./dist/static/js/async')) {
    +       console.warn('Directory ./dist/static/js/async does not exist, skipping worker import patching')
    +       return
    +   }
        const workerFiles = fs.readdirSync('./dist/static/js/async').filter(x => x.endsWith('.js'))
        let patched = false
        for (const file of workerFiles) {
            const filePath = `./dist/static/js/async/${file}`
            const content = fs.readFileSync(filePath, 'utf8')
    -       const match = content.match(/importScripts\([^)]+\)/)
    -       if (match?.length > 1) throw new Error('Multiple importScripts found in ' + filePath)
    +       const matches = content.match(/importScripts\([^)]+\)/g) || []
    +       if (matches.length > 1) throw new Error('Multiple importScripts found in ' + filePath)
    -       const newContent = content.replace(/importScripts\(\w+\.\w+/, "'/'+location.pathname.split('/').slice(0, -4).join('/')")
    +       const newContent = content.replace(/importScripts\((['"])([^'"]+)(['"])\)/, 
    +           "importScripts($1" + "/'+location.pathname.split('/').slice(0, -4).join('/')+'/$3)")
            fs.writeFileSync(filePath, newContent, 'utf8')
    -       patched = true
    +       patched = matches.length > 0
        }
        if (!patched) throw new Error('No importScripts found in any worker files')
    }
    🧹 Nitpick comments (10)
    renderer/rsbuildSharedConfig.ts (1)

    64-72: Good implementation for worker handling in single file builds.

    This change correctly sets up the worker-rspack-loader to handle .worker.js and .worker.ts files when building in single file mode. The inline: "no-fallback" option ensures these workers are properly bundled inline, which aligns with the PR objective of moving Minecraft protocol handling to a separate worker thread.

    Consider adding a brief comment explaining why this specific loader configuration was chosen and what "no-fallback" implies for browser compatibility, to help future maintainers understand the design decisions.

    src/protocolWorker/protocol.worker.ts (3)

    14-18: Optional: Consider a uniform approach for event redirection
    There's a TODO comment to eventually send "all events" rather than enumerating them. One approach is to automatically proxy every emitted event from the Minecraft protocol instead of a hardcoded list.


    78-85: Heads-up about skipWriteLog usage
    Overriding the client.write method for packet logging, gated behind skipWriteLog, may obscure important debugging data if skipWriteLog remains true longer than intended. Consider resetting it automatically or offering a manual toggle to avoid losing valuable logs.


    175-223: Add handling for invalid PEM strings
    Currently, if restoreData encounters an invalid or corrupted PEM, it won’t throw. Consider a try/catch around importKey to handle failures gracefully and notify the main thread or user of invalid keys.

    src/index.ts (3)

    180-181: Validate parsed server address
    If connectOptions.server is malformed or empty, parseServerAddress might fail. Add checks to handle invalid results to prevent runtime crashes.


    194-194: Caution with detailed logs
    Printing serverParsed.serverIpFull is helpful for debugging, but consider omitting sensitive or internal addresses in production logs for privacy or security compliance.


    522-522: Remove commented code
    // lastPacket = fullBuffer.toString() is commented out. If no longer needed, removing it would improve clarity and maintainability.

    src/protocolWorker/protocolMain.ts (3)

    19-30: Consider built-in clone or structuredClone
    copyPrimitiveValues selectively clones some properties. If a full deep clone is needed, using structuredClone or a library might reduce boilerplate and handle more data types.


    52-62: Ensure coordinated Worker teardown
    Terminating the worker via a setTimeout callback tied to bot.on('end') or the absence of bot can become tricky if multiple bots are created. Evaluate a more robust teardown to avoid worker leaks.


    162-179: Performance overhead of JSON serialization
    Forwarding method calls through JSON.stringify/parse is valid, but repeated large data calls can degrade performance. Consider more direct message passing or Transferable objects if needed.

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between b50215b and df2a337.

    📒 Files selected for processing (5)
    • renderer/rsbuildSharedConfig.ts (1 hunks)
    • rsbuild.config.ts (3 hunks)
    • src/index.ts (9 hunks)
    • src/protocolWorker/protocol.worker.ts (1 hunks)
    • src/protocolWorker/protocolMain.ts (1 hunks)
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    src/protocolWorker/protocol.worker.ts (3)
    renderer/viewer/lib/workerProxy.ts (1)
    • createWorkerProxy (1-19)
    src/mineflayer/websocket-core.ts (1)
    • getWebsocketStream (17-54)
    src/mineflayer/minecraft-protocol-extra.ts (1)
    • validatePacket (57-78)
    🪛 GitHub Actions: CI
    rsbuild.config.ts

    [error] 207-207: ENOENT: no such file or directory, scandir './dist/single'

    src/protocolWorker/protocolMain.ts

    [error] 1-1: Build error: Failed to resolve loader: worker-rspack-loader in /home/runner/work/minecraft-web-client/minecraft-web-client, error: NotFound("worker-rspack-loader")

    🔇 Additional comments (7)
    src/protocolWorker/protocol.worker.ts (1)

    137-173:

    ❓ Verification inconclusive

    Confirm compatibility of Web Crypto in worker
    The protocolMicrosoftAuth.authenticate flow and restoreData rely on window.crypto. Double-check that your Worker environment supports these Web Crypto APIs and that sensitive key material isn’t improperly exposed.


    🏁 Script executed:

    #!/bin/bash
    # Checking references to 'crypto' usage in worker code
    rg -A 3 'window[.]crypto[.]'

    Length of output: 621


    Ensure Worker Compatibility for Web Crypto APIs

    The authentication flow (via protocolMicrosoftAuth.authenticate and restoreData) depends on Web Crypto. We found that the worker code in src/protocolWorker/protocol.worker.ts invokes Web Crypto using window.crypto.subtle for key import/export. In a traditional Web Worker, however, the global object is typically self (or globalThis), not window. Please verify that your target Worker environment provides the expected Web Crypto APIs under the given reference. If not, consider updating the code to use self.crypto or globalThis.crypto to avoid potential runtime issues and ensure that sensitive key material remains secure.

    src/index.ts (4)

    76-76: Smooth usage of reactive updates
    Importing subscribe from 'valtio' fits well with the rest of the reactive state management. This looks good.


    98-98: Centralizing protocol logic
    Importing getProtocolClientGetter is a neat way to offload protocol tasks to the Worker. This promotes a cleaner architecture by separating connection details from the main thread.


    189-190: Check proxy address validity
    Similar to server parsing, ensure connectOptions.proxy is valid. If it's malformed or not set, proxyParsed.host or proxyParsed.port may be undefined, causing connection errors.


    452-457: Clarify conditional returns in get client()
    When certain conditions are met, the getter returns undefined instead of a protocol client. Potentially document or handle the scenario to avoid confusion in code expecting a client instance.

    src/protocolWorker/protocolMain.ts (2)

    13-14: Strong typing for protocol worker
    Defining protocolWorkerChannel with a type referencing PROXY_WORKER_TYPE is a good way to maintain type safety when communicating via messages.


    193-197: Conditional authentication flow
    Invoking authFlowMainThread only if authData is present keeps the worker flexible for both online and offline scenarios. Good approach for modular and optional Microsoft auth.

    zardoy and others added 3 commits April 12, 2025 04:16
    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 0

    ♻️ Duplicate comments (1)
    rsbuild.config.ts (1)

    266-282: 🛠️ Refactor suggestion

    The worker import path patching implementation has improved but still has potential issues

    The implementation addresses some of the concerns from previous reviews but still has potential issues:

    1. The regex replacement logic in line 274-275 is still brittle and may not handle all import path formats.
    2. The function doesn't handle the case where there are no worker files at all.
    3. The patched flag logic is improved but the error message doesn't provide enough context.

    Consider implementing a more robust solution:

    const patchWorkerImport = () => {
        if (!fs.existsSync('./dist/static/js/async')) {
            console.warn('No async directory found, skipping worker import patching')
            return
        }
        const workerFiles = fs.readdirSync('./dist/static/js/async').filter(x => x.endsWith('.js'))
        if (workerFiles.length === 0) {
            console.warn('No worker files found, skipping worker import patching')
            return
        }
        let patched = false
        for (const file of workerFiles) {
            const filePath = `./dist/static/js/async/${file}`
            const content = fs.readFileSync(filePath, 'utf8')
            const matches = content.match(/importScripts\([^)]+\)/g) || []
            if (matches.length > 1) throw new Error('Multiple importScripts found in ' + filePath)
    -       const newContent = content.replace(/importScripts\(\w+\.\w+/,
    -           "importScripts('/'+location.pathname.split('/').slice(0, -4).join('/')")
    +       const newContent = content.replace(/importScripts\((['"])([^'"]+)(['"])\)/, 
    +           "importScripts($1" + "/'+location.pathname.split('/').slice(0, -4).join('/')+'/$3)")
            if (newContent !== content) {
                fs.writeFileSync(filePath, newContent, 'utf8')
                patched = true
            }
        }
        if (!patched) throw new Error('No importScripts patched in any worker files. This might indicate a change in the worker code structure.')
    }

    Additionally, consider adding debug logging to help with troubleshooting:

    console.log(`Patching ${workerFiles.length} worker files in ./dist/static/js/async`)
    🧹 Nitpick comments (1)
    rsbuild.config.ts (1)

    212-217: Add error handling for empty async directory check

    Good addition of directory existence check before attempting to read it. This prevents build failures when the directory doesn't exist and aligns with a previous review comment.

    However, the current implementation could be more robust with better error messages.

    Consider providing more context in the error message to help developers understand the issue:

    if (fs.existsSync('./dist/static/js/async')) {
        const asyncFiles = fs.readdirSync('./dist/static/js/async')
        if (asyncFiles.length > 0) {
    -       throw new Error('dist/static/js/async must be empty. Ensure workers are imported & built correctly.')
    +       throw new Error('dist/static/js/async must be empty for single file builds. Found files: ' + asyncFiles.join(', '))
        }
    }
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 4a949d3 and 475d990.

    📒 Files selected for processing (1)
    • rsbuild.config.ts (3 hunks)
    🧰 Additional context used
    🪛 GitHub Actions: CI
    rsbuild.config.ts

    [error] 207-207: Failed to build. ENOENT: no such file or directory, scandir './dist/single'

    🔇 Additional comments (1)
    rsbuild.config.ts (1)

    234-235: Worker import patching added before service worker generation

    Good placement of the patchWorkerImport() function call before the service worker generation to ensure worker imports are correctly patched.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Apr 12, 2025

    /protocol

    @zardoy
    Copy link
    Owner Author

    zardoy commented Apr 12, 2025

    /deploy

    Copy link

    Deployed to Vercel Preview: https://prismarine-r0lk6nejb-zaro.vercel.app
    Playground
    Storybook

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 1

    ♻️ Duplicate comments (1)
    src/index.ts (1)

    414-414: Explicit type added for password variable.

    Adding explicit string | null typing to the password variable improves type safety and code clarity.

    🧹 Nitpick comments (1)
    src/index.ts (1)

    467-467: Remove commented out authentication code.

    The authentication code is commented out since it's now handled in the protocol worker. It's best practice to remove commented code rather than leaving it in the codebase.

    -  // auth: connectOptions.authenticatedAccount ?  : undefined,
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between d9f1efd and 1fe6239.

    📒 Files selected for processing (3)
    • src/index.ts (9 hunks)
    • src/optionsStorage.ts (1 hunks)
    • src/utils.ts (0 hunks)
    💤 Files with no reviewable changes (1)
    • src/utils.ts
    🚧 Files skipped from review as they are similar to previous changes (1)
    • src/optionsStorage.ts
    🔇 Additional comments (8)
    src/index.ts (8)

    76-76: Added Valtio subscription support.

    The new import for subscribe from Valtio is properly integrated and will be used later for chunk loading progress tracking.


    98-98: Successfully integrated protocol worker architecture.

    Adding the import for getProtocolClientGetter aligns with the PR's primary objective of moving protocol handling to a worker thread for improved performance.


    181-182: Variable naming improved for clarity.

    Renaming from parsedServer to serverParsed creates more consistent naming patterns throughout the codebase, improving readability.


    190-191: Variable naming improved for clarity.

    Similar to the server variable renaming, changing from parsedProxy to proxyParsed creates more consistent naming patterns throughout the codebase.


    195-195: Simplified server logging.

    Using serverParsed.serverIpFull for logging consolidates the host and port information into a single, pre-formatted string, which improves code readability.


    439-440: Protocol logic successfully moved to worker thread.

    The complex client creation logic has been refactored to use the new getProtocolClientGetter function, which offloads protocol handling to a dedicated worker thread. This should provide the expected 10% performance improvement on high-load servers mentioned in the PR objectives.


    461-466: Client getter logic provides proper fallback paths.

    The updated client getter supports multiple scenarios: direct streams, singleplayer, p2p, replays, viewer connections, and worker optimizations. The flow control looks correct, providing the worker-based client when appropriate or falling back to undefined for special connection types.


    535-536:

    ❓ Verification inconclusive

    Validate removal of packet logging.

    The packet logging has been commented out as part of the protocol worker refactoring. This appears to be intentional since the protocol is now handled in the worker thread, but verify that you still have sufficient logging for troubleshooting if needed.


    🏁 Script executed:

    #!/bin/bash
    # Search for other packet logging in the protocol worker
    echo "Checking for packet logging in protocol worker files:"
    fd -e ts -e js --exec grep -l "packet" {} \; | grep -i "protocol"
    echo "Checking for debug or logging functions in protocol worker:"
    fd -e ts -e js --exec grep -l "log\|debug\|console" {} \; | grep -i "protocol"

    Length of output: 667


    Validate packet logging removal and confirm logging coverage

    The assignment of lastPacket in src/index.ts has been commented out:

        // lastPacket = fullBuffer.toString()

    Packet logging now lives in your protocol worker modules, but please verify that you still have enough visibility for troubleshooting:

    • Files referencing “packet” in the protocol worker area:
    – src/protocolWorker/protocolMain.ts
    – src/protocolWorker/protocol.worker.ts
    – src/mineflayer/mc-protocol.ts

    • Files containing other log/debug calls in protocol-related code:
    – src/protocolWorker/protocolMain.ts
    – src/protocolWorker/protocol.worker.ts
    – src/protocolWorker/microsoftAuthflow.ts
    – src/mineflayer/minecraft-protocol-extra.ts

    Ensure these logs surface the details you need when diagnosing protocol issues. If you require more granular packet dumps or error tracing, consider reintroducing targeted logging in the worker.

    @@ -242,14 +236,13 @@ export async function connect (connectOptions: ConnectOptions) {
    })
    }
    }
    let lastPacket = undefined as string | undefined
    const lastPacket = undefined as string | undefined
    Copy link

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Verify the impact of lastPacket removal.

    The lastPacket variable is now a constant with undefined value, and its assignment at line 535 is commented out. However, it's still referenced in the onPossibleErrorDisconnect function (lines 241-243), which will never enter the conditional block.

    -const lastPacket = undefined as string | undefined
    +// Remove this constant since it's no longer used

    And update the error handling function:

    const onPossibleErrorDisconnect = () => {
    -  if (lastPacket && bot?._client && bot._client.state !== states.PLAY) {
    +  if (bot?._client && bot._client.state !== states.PLAY) {
    -    appStatusState.descriptionHint = `Last Server Packet: ${lastPacket}`
    +    appStatusState.descriptionHint = `Disconnected before entering play state`
      }
    }
    📝 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
    const lastPacket = undefined as string | undefined
    // In src/index.ts around line 239, remove the unused `lastPacket` declaration:
    // Remove this constant since it's no longer used
    // …later in the same file, update the error‐disconnect handler:
    const onPossibleErrorDisconnect = () => {
    if (bot?._client && bot._client.state !== states.PLAY) {
    appStatusState.descriptionHint = `Disconnected before entering play state`
    }
    }

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 4

    ♻️ Duplicate comments (2)
    src/protocolWorker/protocolMain.ts (2)

    50-50: ⚠️ Potential issue

    Undefined bot reference.

    The code checks bot?._client but the bot variable is not declared or imported in this file. This may cause errors or rely on a global variable.

    If bot is intended to be a global variable, explicitly access it through globalThis.bot or window.bot. Alternatively, pass bot as a parameter to functions that need it.

    -    if (bot?._client) return bot._client
    +    if (globalThis.bot?._client) return globalThis.bot._client

    57-65: ⚠️ Potential issue

    Unclear purpose of setTimeout and undefined bot references.

    The setTimeout is used without a delay parameter (defaults to 0) and references the bot variable that isn't defined in this scope.

    -    setTimeout(() => {
    +    // Execute after the current call stack completes
    +    queueMicrotask(() => {
    -      if (bot) {
    +      if (globalThis.bot) {
    -        bot.on('end', () => {
    +        globalThis.bot.on('end', () => {
               worker.terminate()
             })
           } else {
             worker.terminate()
           }
    -    })
    +    })
    🧹 Nitpick comments (4)
    src/protocolWorker/protocolMain.ts (4)

    11-12: Consider addressing the TypeScript error instead of suppressing it.

    The @ts-expect-error annotation suppresses type-checking errors instead of addressing the underlying issue. This could potentially hide actual problems in the codebase.

    Consider properly typing the worker import or documenting the reason for the error suppression if it's unavoidable.


    22-33: Improve the copyPrimitiveValues utility function.

    The current implementation has several limitations:

    1. Uses any typing instead of more specific types
    2. Doesn't handle arrays
    3. Doesn't pass ignoreKeys parameter to recursive calls
    -const copyPrimitiveValues = (obj: any, deep = false, ignoreKeys: string[] = []) => {
    +const copyPrimitiveValues = <T extends Record<string, unknown>>(obj: T, deep = false, ignoreKeys: string[] = []): Partial<T> => {
    -  const copy = {} as Record<string, any>
    +  const copy = {} as Partial<T>
       for (const key in obj) {
         if (ignoreKeys.includes(key)) continue
         if (typeof obj[key] === 'object' && obj[key] !== null && deep) {
    -      copy[key] = copyPrimitiveValues(obj[key])
    +      if (Array.isArray(obj[key])) {
    +        copy[key] = (obj[key] as unknown[]).map(item => 
    +          typeof item === 'object' && item !== null 
    +            ? copyPrimitiveValues(item as Record<string, unknown>, deep, ignoreKeys)
    +            : item
    +        ) as T[Extract<keyof T, string>]
    +      } else {
    +        copy[key] = copyPrimitiveValues(obj[key] as Record<string, unknown>, deep, ignoreKeys) as T[Extract<keyof T, string>]
    +      }
         } else if (typeof obj[key] === 'number' || typeof obj[key] === 'string' || typeof obj[key] === 'boolean') {
           copy[key] = obj[key]
         }
       }
       return copy
     }

    175-175: JSON serialization may lose data for complex objects.

    Using JSON.parse(JSON.stringify(args)) can cause data loss for values that don't serialize well to JSON (e.g., functions, symbols, undefined, recursive structures).

    Consider using a more robust serialization method that handles complex objects better, or document the limitations of what types of arguments can be passed to these methods.

    -            args: JSON.parse(JSON.stringify(args))
    +            // Only pass primitive values and simple objects that can be serialized
    +            args: args.map(arg => {
    +              if (arg === undefined || typeof arg === 'function' || typeof arg === 'symbol') {
    +                console.warn('Non-serializable value detected:', typeof arg);
    +                return null; // Or some other placeholder value
    +              }
    +              return arg;
    +            })

    203-211: Remove or document commented-out code.

    Commented-out code adds noise and can confuse future developers. If this is kept for reference, add a comment explaining why.

    Either remove the commented-out code or add a comment explaining its purpose:

         return eventEmitter
    -    // return new Proxy(eventEmitter, {
    -    //   get (target, prop) {
    -    //     if (!(prop in target)) {
    -    //       // console.warn(`Accessing non-existent property "${String(prop)}" on event emitter`)
    -    //     }
    -    //     const value = target[prop]
    -    //     return typeof value === 'function' ? value.bind(target) : value
    -    //   }
    -    // })
    +    /* 
    +     * Proxy implementation removed for performance reasons.
    +     * It was originally used to bind methods and provide warnings for non-existent properties.
    +     */
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 1fe6239 and cd84acb.

    📒 Files selected for processing (1)
    • src/protocolWorker/protocolMain.ts (1 hunks)

    Comment on lines +138 to +153
    if (!window.lastStatsUpdate) {
    window.lastStatsUpdate = Date.now()
    setInterval(() => {
    // Sort by total processing time
    window.packetsThreadBlocking = Object.entries(window.packetTimings)
    .sort(([, a], [, b]) => b.total - a.total)
    .reduce((acc, [key, value]) => {
    acc[key] = value
    return acc
    }, {})

    // Reset timings for next interval
    window.packetTimings = {}
    window.lastStatsUpdate = Date.now()
    }, 1000)
    }
    Copy link

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Interval created inside event handler may lead to multiple intervals.

    The interval for updating packet statistics is created inside the event handler, which means it could be created multiple times if multiple packets are received before the first interval executes.

    -          // Update packetsThreadBlocking every second
    -          if (!window.lastStatsUpdate) {
    -            window.lastStatsUpdate = Date.now()
    -            setInterval(() => {
    -              // Sort by total processing time
    -              window.packetsThreadBlocking = Object.entries(window.packetTimings)
    -                .sort(([, a], [, b]) => b.total - a.total)
    -                .reduce((acc, [key, value]) => {
    -                  acc[key] = value
    -                  return acc
    -                }, {})
    -
    -              // Reset timings for next interval
    -              window.packetTimings = {}
    -              window.lastStatsUpdate = Date.now()
    -            }, 1000)
    -          }

    Move this setup code outside the event handler, possibly at the top of the file or in an initialization function:

    // Add at the top level of the file
    // Initialize packet statistics tracking
    if (!window.packetTimingsIntervalSet) {
      window.packetTimingsIntervalSet = true;
      window.lastStatsUpdate = Date.now();
      window.packetTimings = {};
      
      setInterval(() => {
        // Sort by total processing time
        window.packetsThreadBlocking = Object.entries(window.packetTimings || {})
          .sort(([, a], [, b]) => b.total - a.total)
          .reduce((acc, [key, value]) => {
            acc[key] = value
            return acc
          }, {});
    
        // Reset timings for next interval
        window.packetTimings = {};
        window.lastStatsUpdate = Date.now();
      }, 1000);
    }

    Comment on lines +157 to +162
    if (data.type === 'properties') {
    // eslint-disable-next-line guard-for-in
    for (const property in data.properties) {
    eventEmitter[property] = data.properties[property]
    }
    }
    Copy link

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    For-in loop without hasOwnProperty check risks prototype pollution.

    The ESLint rule is disabled, but it's better to properly guard against prototype pollution by using hasOwnProperty checks or alternative methods.

          if (data.type === 'properties') {
    -        // eslint-disable-next-line guard-for-in
    -        for (const property in data.properties) {
    +        for (const property in data.properties) {
    +          if (Object.prototype.hasOwnProperty.call(data.properties, property)) {
                eventEmitter[property] = data.properties[property]
    +          }
             }
           }

    Alternatively, you can use:

    Object.keys(data.properties).forEach(property => {
      eventEmitter[property] = data.properties[property];
    });

    Comment on lines +86 to +90
    if (window.stopPacketsProcessing === true || (Array.isArray(window.stopPacketsProcessing) && window.stopPacketsProcessing.includes(packetMeta.name))) {
    if (window.skipPackets && !window.skipPackets.includes(packetMeta.name)) {
    return
    }
    }
    Copy link

    Choose a reason for hiding this comment

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

    💡 Verification agent

    🧩 Analysis chain

    Undeclared global variables create implicit dependencies.

    The code references window.stopPacketsProcessing and window.skipPackets which aren't declared in this file, creating implicit dependencies.

    Consider defining these global variables somewhere in the codebase or using a more explicit approach to feature flags. Run this script to check if these variables are defined elsewhere:


    🏁 Script executed:

    #!/bin/bash
    # Search for declarations of stopPacketsProcessing and skipPackets
    echo "Searching for stopPacketsProcessing declarations:"
    rg -A 2 "window\.stopPacketsProcessing|stopPacketsProcessing\s*=" 
    
    echo -e "\nSearching for skipPackets declarations:"
    rg -A 2 "window\.skipPackets|skipPackets\s*="

    Length of output: 1183


    Define and Declare Window Globals

    The properties window.stopPacketsProcessing and window.skipPackets are used in two files without ever being declared, leading to implicit dependencies and potential runtime/type errors. Please explicitly declare and initialize these globals:

    • src/protocolWorker/protocolMain.ts (lines 86–90)
    • src/protocolWorker/protocol.worker.ts

    Suggested fixes:

    1. Create or update a global declarations file (e.g. src/global.d.ts) and augment the Window interface:
    declare global {
      interface Window {
        /** When true or an array of packet names, stops processing matching packets */
        stopPacketsProcessing: boolean | string[];
        /** List of packet names to skip when stopPacketsProcessing is active */
        skipPackets: string[];
      }
    }
    
    export {};
    1. Initialize defaults at application startup (e.g. in your entry point):
    window.stopPacketsProcessing = false;
    window.skipPackets = [];

    Or pass these flags explicitly (e.g. via constructor arguments or a config object) instead of relying on undeclared globals.

    Comment on lines +78 to +80
    const eventEmitter = new EventEmitter() as any
    eventEmitter.version = this.version

    Copy link

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Unclear this context in createMinecraftProtocolClient.

    The this.version reference is ambiguous as it depends on how the function is called and what this references at that point.

         const eventEmitter = new EventEmitter() as any
    -    eventEmitter.version = this.version
    +    // Explicitly document where this.version comes from and ensure it exists
    +    eventEmitter.version = this.version || null
    📝 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
    const eventEmitter = new EventEmitter() as any
    eventEmitter.version = this.version
    const eventEmitter = new EventEmitter() as any
    // Explicitly document where this.version comes from and ensure it exists
    eventEmitter.version = this.version || null

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 2

    ♻️ Duplicate comments (5)
    src/protocolWorker/protocolMain.ts (5)

    46-47: ⚠️ Potential issue

    Undefined bot reference
    This code checks bot?._client but doesn't declare bot here. If it's intended to be a global, explicitly read globalThis.bot or pass bot as an argument to avoid confusion and potential runtime errors.

    -    if (bot?._client) return bot._client
    +    if (globalThis.bot?._client) return globalThis.bot._client

    75-77: ⚠️ Potential issue

    Unclear this context in createMinecraftProtocolClient.

    The this.version reference is ambiguous as it depends on how the function is called and what this references at that point.

         const eventEmitter = new EventEmitter() as any
    -    eventEmitter.version = this.version
    +    // Explicitly document where this.version comes from and ensure it exists
    +    eventEmitter.version = this.version || null

    83-87: ⚠️ Potential issue

    Define and Declare Window Globals

    The properties window.stopPacketsProcessing and window.skipPackets are used without ever being declared, leading to implicit dependencies and potential runtime/type errors.

    +// Add explicit type checking
    +if (typeof window.stopPacketsProcessing !== 'undefined' && 
        (window.stopPacketsProcessing === true || 
         (Array.isArray(window.stopPacketsProcessing) && 
          window.stopPacketsProcessing.includes(packetMeta.name)))) {

    135-150: ⚠️ Potential issue

    Interval created inside event handler may lead to multiple intervals.

    The interval for updating packet statistics is created inside the event handler, which means it could be created multiple times if multiple packets are received before the first interval executes.

    Move this setup code outside the event handler, possibly at the top of the file or in an initialization function:

    // Add at the top level of the file
    // Initialize packet statistics tracking
    if (!window.packetTimingsIntervalSet) {
      window.packetTimingsIntervalSet = true;
      window.lastStatsUpdate = Date.now();
      window.packetTimings = {};
      
      setInterval(() => {
        // Sort by total processing time
        window.packetsThreadBlocking = Object.entries(window.packetTimings || {})
          .sort(([, a], [, b]) => b.total - a.total)
          .reduce((acc, [key, value]) => {
            acc[key] = value
            return acc
          }, {});
    
        // Reset timings for next interval
        window.packetTimings = {};
        window.lastStatsUpdate = Date.now();
      }, 1000);
    }

    154-159: 🛠️ Refactor suggestion

    For-in loop without hasOwnProperty check risks prototype pollution.

    The ESLint rule is disabled, but it's better to properly guard against prototype pollution by using hasOwnProperty checks or alternative methods.

          if (data.type === 'properties') {
    -        // eslint-disable-next-line guard-for-in
    -        for (const property in data.properties) {
    +        for (const property in data.properties) {
    +          if (Object.prototype.hasOwnProperty.call(data.properties, property)) {
                eventEmitter[property] = data.properties[property]
    +          }
             }
           }

    Alternatively, you can use:

    Object.keys(data.properties).forEach(property => {
      eventEmitter[property] = data.properties[property];
    });
    🧹 Nitpick comments (2)
    src/protocolWorker/protocolMain.ts (2)

    19-30: Consider adding type safety to copyPrimitiveValues function

    The utility function is useful, but it could benefit from better type safety. The any return type and parameter should be more specific.

    -const copyPrimitiveValues = (obj: any, deep = false, ignoreKeys: string[] = []) => {
    +const copyPrimitiveValues = <T extends Record<string, unknown>>(obj: T, deep = false, ignoreKeys: string[] = []): Partial<T> => {
    -  const copy = {} as Record<string, any>
    +  const copy = {} as Partial<T>

    199-209: Remove commented code

    There's commented-out code that appears to be a Proxy implementation. Clean up the codebase by removing or documenting why this code is kept.

        return eventEmitter
    -    // return new Proxy(eventEmitter, {
    -    //   get (target, prop) {
    -    //     if (!(prop in target)) {
    -    //       // console.warn(`Accessing non-existent property "${String(prop)}" on event emitter`)
    -    //     }
    -    //     const value = target[prop]
    -    //     return typeof value === 'function' ? value.bind(target) : value
    -    //   }
    -    // })
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between cd84acb and 139ee9a.

    📒 Files selected for processing (1)
    • src/protocolWorker/protocolMain.ts (1 hunks)
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    src/protocolWorker/protocolMain.ts (6)
    src/protocolWorker/protocol.worker.ts (1)
    • PROXY_WORKER_TYPE (36-135)
    src/connect.ts (1)
    • ConnectOptions (13-42)
    src/parseServerAddress.ts (1)
    • ParsedServerAddress (48-54)
    src/protocolWorker/microsoftAuthflow.ts (2)
    • getAuthData (20-113)
    • authFlowMainThread (115-164)
    src/appStatus.ts (1)
    • setLoadingScreenStatus (5-35)
    renderer/viewer/lib/workerProxy.ts (1)
    • useWorkerProxy (32-76)
    🔇 Additional comments (2)
    src/protocolWorker/protocolMain.ts (2)

    1-18: Nice module structure and imports setup

    The imports are well-organized and the module exports a clear interface for accessing the protocol worker channel. Good practice to expose this as a singleton through a getter function.


    193-197: Good authentication flow integration

    The integration with the Microsoft authentication flow is well-implemented. Good use of the authFlowMainThread function with the event emitter for handling logins.

    Comment on lines +166 to +180
    const redirectMethodsToWorker = (names: string[]) => {
    for (const name of names) {
    // eslint-disable-next-line @typescript-eslint/no-loop-func
    eventEmitter[name] = async (...args: any[]) => {
    protocolWorkerChannel?.call({
    name,
    args: JSON.parse(JSON.stringify(args))
    })

    if (name === 'write') {
    eventEmitter.emit('writePacket', ...args)
    }
    }
    }
    }
    Copy link

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Improve JSON serialization and handle errors in redirectMethodsToWorker

    Using JSON.parse(JSON.stringify(args)) for cloning arguments will fail with circular references, functions, and undefined values. Also, errors during worker communication are not handled.

    eventEmitter[name] = async (...args: any[]) => {
      try {
    -    protocolWorkerChannel?.call({
    +    await protocolWorkerChannel?.call({
          name,
    -      args: JSON.parse(JSON.stringify(args))
    +      args: args.map(arg => 
    +        typeof arg === 'object' && arg !== null ? 
    +        structuredClone(arg) : arg)
        })
    +  } catch (error) {
    +    console.error(`Error calling worker method ${name}:`, error);
    +    eventEmitter.emit('error', error);
    +  }
    
      if (name === 'write') {
        eventEmitter.emit('writePacket', ...args)
      }
    }
    📝 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
    const redirectMethodsToWorker = (names: string[]) => {
    for (const name of names) {
    // eslint-disable-next-line @typescript-eslint/no-loop-func
    eventEmitter[name] = async (...args: any[]) => {
    protocolWorkerChannel?.call({
    name,
    args: JSON.parse(JSON.stringify(args))
    })
    if (name === 'write') {
    eventEmitter.emit('writePacket', ...args)
    }
    }
    }
    }
    const redirectMethodsToWorker = (names: string[]) => {
    for (const name of names) {
    // eslint-disable-next-line @typescript-eslint/no-loop-func
    eventEmitter[name] = async (...args: any[]) => {
    try {
    await protocolWorkerChannel?.call({
    name,
    args: args.map(arg =>
    typeof arg === 'object' && arg !== null
    ? structuredClone(arg)
    : arg
    )
    })
    } catch (error) {
    console.error(`Error calling worker method ${name}:`, error)
    eventEmitter.emit('error', error)
    }
    if (name === 'write') {
    eventEmitter.emit('writePacket', ...args)
    }
    }
    }
    }

    Comment on lines +97 to +107
    const patchUint8Arrays = (obj: any) => {
    for (const key in obj) {
    if (obj[key] instanceof Uint8Array) {
    obj[key] = Buffer.from(obj[key])
    } else if (typeof obj[key] === 'object' && obj[key] !== null) {
    patchUint8Arrays(obj[key])
    }
    }
    }
    patchUint8Arrays(packetData)
    }
    Copy link

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Improve the recursive patchUint8Arrays function with explicit checks

    The recursive function lacks explicit checks for circular references, which could lead to stack overflow errors with certain data structures.

    -const patchUint8Arrays = (obj: any) => {
    +const patchUint8Arrays = (obj: any, visited = new Set()) => {
    +  if (visited.has(obj)) return; // Prevent circular reference issues
    +  visited.add(obj);
       for (const key in obj) {
         if (obj[key] instanceof Uint8Array) {
           obj[key] = Buffer.from(obj[key])
         } else if (typeof obj[key] === 'object' && obj[key] !== null) {
    -      patchUint8Arrays(obj[key])
    +      patchUint8Arrays(obj[key], visited)
         }
       }
     }
    📝 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
    const patchUint8Arrays = (obj: any) => {
    for (const key in obj) {
    if (obj[key] instanceof Uint8Array) {
    obj[key] = Buffer.from(obj[key])
    } else if (typeof obj[key] === 'object' && obj[key] !== null) {
    patchUint8Arrays(obj[key])
    }
    }
    }
    patchUint8Arrays(packetData)
    }
    const patchUint8Arrays = (obj: any, visited = new Set()) => {
    if (visited.has(obj)) return; // Prevent circular reference issues
    visited.add(obj);
    for (const key in obj) {
    if (obj[key] instanceof Uint8Array) {
    obj[key] = Buffer.from(obj[key])
    } else if (typeof obj[key] === 'object' && obj[key] !== null) {
    patchUint8Arrays(obj[key], visited)
    }
    }
    }
    patchUint8Arrays(packetData)

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

    Successfully merging this pull request may close these issues.

    1 participant