Skip to content

Conversation

@pujitm
Copy link
Member

@pujitm pujitm commented Mar 27, 2025

Summary by CodeRabbit

  • Chores

    • Updated environment configuration to enhance secure handling.
    • Integrated a shared dependency for consistent callback management.
  • Refactor

    • Streamlined callback actions management for improved performance.
    • Upgraded the operating system version from a beta release to stable (7.0.0), delivering enhanced reliability.
    • Centralized callback actions store usage across components for better state management.
    • Removed deprecated callback management code to improve clarity and maintainability.
  • New Features

    • Introduced a new redirect component to enhance user navigation experience.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 27, 2025

Walkthrough

This pull request modifies the environment variable for callbacks and updates the operating system version from a beta release to a stable one. It also refactors the code by renaming useCallbackStore to useCallbackActionsStore, migrating type imports to the external module @unraid/shared-callbacks, and removing deprecated callback code.

Changes

File(s) Summary of Changes
web/.env.example
web/_data/serverState.ts
Updated VITE_CALLBACK_KEY and changed OS version from beta (7.0.0-beta.2.10) to stable (7.0.0).
web/components/CallbackHandler.ce.vue
web/components/UserProfile.ce.vue
web/store/account.ts
web/store/purchase.ts
web/store/updateOsChangelog.ts
web/store/callbackActions.ts
Refactored callback handling by renaming useCallbackStore to useCallbackActionsStore and initializing the Pinia store; adjusted usage within components and stores.
web/pages/index.vue
web/store/installKey.ts
web/store/trial.ts
web/store/updateOsActions.ts
Updated type imports from local modules to use @unraid/shared-callbacks.
web/package.json
web/store/callback.ts
web/store/replaceRenew.ts
Added dependency on @unraid/shared-callbacks, removed deprecated callback file, and cleaned up commented code.

Sequence Diagram(s)

sequenceDiagram
    participant C as Vue Component
    participant CAS as CallbackActionsStore
    participant UC as useCallback (from @unraid/shared-callbacks)
    participant P as Pinia Store

    C->>CAS: Initialize callback actions
    CAS->>P: setActivePinia(createPinia())
    CAS->>UC: Destructure send & watcher from useCallback()
    UC-->>CAS: Return send & watcher
    CAS->>C: Expose callback methods for handling actions
Loading

Suggested reviewers

  • zackspear
  • pujitm

Poem

In lines of code, new keys now gleam,
Callbacks transformed like a seamless dream.
Refactored logic with a fresh, clear flow,
With Pinia’s magic and shared modules aglow.
Our code sings in rhythm—change in tow!
🚀✨ Happy refactoring!


🪧 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 resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @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.

@elibosley elibosley force-pushed the chore/callback-extracted-to-library branch from 63ab033 to c4f21d8 Compare March 27, 2025 19:56
@elibosley elibosley force-pushed the chore/callback-extracted-to-library branch from c4f21d8 to ba94cd9 Compare March 27, 2025 20:12
@zackspear
Copy link
Contributor

zackspear commented Mar 27, 2025

Before df69093 (#1280) with the PR changes nothing was saving the decrypted callback data to the store hehe. So the watcher was doing nothing with the decrypted data. But the aforementioned commit fixes it :)

Copy link
Contributor

@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

🧹 Nitpick comments (1)
web/store/callbackActions.ts (1)

21-23: Consider moving store initialization elsewhere

While setActivePinia(createPinia()) is needed to initialize the Pinia store, placing it directly in the store definition file is unusual. This kind of initialization typically belongs in a main application file or entry point.

-setActivePinia(createPinia());

This line could be moved to your main application initialization file (like main.ts or app.ts).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 122d302 and df69093.

📒 Files selected for processing (1)
  • web/store/callbackActions.ts (5 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
web/store/callbackActions.ts (3)
web/store/account.ts (1)
  • useAccountStore (25-334)
web/store/installKey.ts (1)
  • useInstallKeyStore (11-74)
web/store/updateOsActions.ts (1)
  • useUpdateOsActionsStore (39-239)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build API
  • GitHub Check: Build Web App
  • GitHub Check: Test API
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (9)
web/store/callbackActions.ts (9)

1-13: Good migration to shared library for callback types

The import changes here reflect the migration from local types to a shared library (@unraid/shared-callbacks). This promotes code reuse and type consistency across the application.


25-28: Good use of the shared callback functionality

Destructuring send and watcher from the imported useCallback function is a clean approach. Renaming watcher to providedWatcher helps avoid naming conflicts.


40-45: Well-implemented wrapper for the provided watcher

The new watcher function properly encapsulates the provided watcher functionality and ensures callback data is saved when results are available.


72-77: Improved conditional structure

The reformatted conditional checks are more readable and maintainable.


86-89: Good explicit typing for action parameter

Adding explicit typing for the action parameter as ExternalActions improves type safety and code readability.


108-119: Comprehensive handling of OS update actions

The implementation properly handles both update and downgrade OS actions with appropriate logic for refreshing the server state. The early return for single actions is a good optimization.


135-144: Clean formatting of success condition checks

The reformatted condition checks for determining success status are more readable.


153-155: Simplified callback status setter

The refactored setCallbackStatus function is concise and to the point.


176-177: Good exposure of callback functionality

Exporting send and watcher from the store makes these functions accessible to consumers, which is appropriate for a callback store.

@zackspear
Copy link
Contributor

@coderabbitai - This nitpick would break our build. The various Vue components are compiled into web components. This difference in initialization of the Pinia stores makes the web components share a singular store.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 27, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@zackspear Thank you for pointing this out! I understand now that the initialization approach with setActivePinia(createPinia()) would break your build by causing all web components to share a single Pinia store instance. This would be problematic since your Vue components are compiled into web components that need separate store instances.

I apologize for the nitpick - maintaining the correct store initialization is critical for your architecture. Thanks for explaining the technical constraints of your web component implementation.


🏁 Script executed:

#!/bin/bash
# Check the current content of web/store/callbackActions.ts
cat web/store/callbackActions.ts

Length of output: 6089


@zackspear I see the issue now - the setActivePinia(createPinia()) line in callbackActions.ts would indeed break your web component architecture. You're absolutely right that this would create a single shared Pinia store across all web components, breaking the necessary isolation between them.

Thank you for the correction and for explaining the technical constraint. I now understand why maintaining separate Pinia store instances for each web component is critical for your application.


✏️ Learnings added
Learnt from: zackspear
URL: https://github.com/unraid/api/pull/1280

Timestamp: 2025-03-27T23:33:13.081Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to function correctly.

Learnt from: zackspear
URL: https://github.com/unraid/api/pull/1280

Timestamp: 2025-03-27T23:33:13.081Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files would break the build by causing all web components to share a singular Pinia store instance. Each web component needs its own Pinia store instance to maintain proper isolation and encapsulation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@zackspear
Copy link
Contributor

Tip

For best results, initiate chat on the files or code changes.
🧩 Analysis chain

@zackspear I see the issue now - the setActivePinia(createPinia()) line in callbackActions.ts would indeed break your web component architecture. You're absolutely right that this would create a single shared Pinia store across all web components, breaking the necessary isolation between them.

Thank you for the correction and for explaining the technical constraint. I now understand why maintaining separate Pinia store instances for each web component is critical for your application.
✏️ Learnings added

@coderabbitai You're understanding it in the wrong way, m8.

setActivePinia(createPinia() within each of the stores prevents isolation, which is what we don't want. A single Pinia instance is used across all web components thanks to this.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 27, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@zackspear Thank you for the correction! I completely misunderstood your explanation. You're right, and I had it backward.

To clarify: setActivePinia(createPinia()) within your stores actually ensures a single shared Pinia instance across all web components, which is what you want. Without this initialization, each web component would have its own isolated store instance, breaking your architecture.

I apologize for the confusion in my previous response. Thanks for taking the time to set the record straight.


🏁 Script executed:

#!/bin/bash
# Check the current content of web/store/callbackActions.ts
cat web/store/callbackActions.ts

Length of output: 6089


@zackspear Thanks for the clarification. Looking at your code, I now understand correctly:

The setActivePinia(createPinia()) at the module level in your store files actually ensures all web components share the same Pinia store instance, which is exactly what you want. This shared state approach is intentional in your architecture.

I completely misunderstood and had it backward in my previous response. Thanks for setting me straight on how your web component architecture works with Pinia.


✏️ Learnings added
Learnt from: zackspear
URL: https://github.com/unraid/api/pull/1280

Timestamp: 2025-03-27T23:52:57.680Z
Learning: In the unraid/api project, Vue components are compiled into web components. Using `setActivePinia(createPinia())` in store files ensures that all web components share a single Pinia store instance, which is the desired behavior. Without this initialization, each web component would have its own isolated store, breaking the intended architecture.

Learnt from: zackspear
URL: https://github.com/unraid/api/pull/1280

Timestamp: 2025-03-27T23:52:57.680Z
Learning: In the unraid/api project, Vue components are compiled into web components. The `setActivePinia(createPinia())` call at the module level in store files is intentional and ensures all web components share a single Pinia store instance, which is the desired behavior. This shared state approach is critical for the application's architecture to function correctly.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@elibosley
Copy link
Member

Before df69093 (#1280) with the PR changes nothing was saving the decrypted callback data to the store hehe. So the watcher was doing nothing with the decrypted data. But the aforementioned commit fixes it :)

Noticed this yesterday night and was going to try to fix this morning, thanks a ton.

* enables webcomponent redirection on new account application
Copy link
Contributor

@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

🧹 Nitpick comments (4)
web/pages/redirect.vue (4)

5-5: Remove console.log statement

Console logs should be removed from production code.

-    console.log(target);

14-16: Improve data parameter handling

The condition checks if there are exactly 1 or 2 search parameters, but this may be too restrictive for future use cases. Consider keeping the data parameter regardless of how many other parameters exist.

-  if (search.has('data') && (search.size === 1 || search.size === 2)) {
+  if (search.has('data')) {
     return `${window.location.origin}${targetRoute}?data=${encodeURIComponent(search.get('data')!)}`;
   }

34-37: Replace inline styles with CSS classes

Using inline styles makes the component harder to maintain. Consider using CSS classes instead.

-    <div
-      id="text"
-      style="text-align: center; margin-top: calc(100vh - 75%); display: none; font-family: sans-serif"
-    >
+    <div
+      id="text"
+      class="redirect-message"
+    >

Then add a style section:

<style scoped>
.redirect-message {
  text-align: center;
  margin-top: calc(100vh - 75%);
  display: none;
  font-family: sans-serif;
}
</style>

1-43: Consider adding security checks for redirect targets

Currently, the function always redirects to '/', but if you implement actual target parsing, be sure to include security checks to prevent open redirect vulnerabilities.

When implementing proper target validation:

  1. Only allow relative URLs or URLs within your domain
  2. Validate URLs against an allowlist of permitted destinations
  3. Consider using a library like sanitize-url to prevent JavaScript injection
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between df69093 and bc82ae5.

📒 Files selected for processing (1)
  • web/pages/redirect.vue (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build API
  • GitHub Check: Build Web App
  • GitHub Check: Test API
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
web/pages/redirect.vue (1)

11-18:

❓ Verification inconclusive

Verify the getRedirectUrl function logic

Since parseRedirectTarget currently always returns '/', this function effectively ignores any target parameter value and always redirects to the home page. Additionally, the condition checking for search.size is unclear in its purpose.


🏁 Script executed:

#!/bin/bash
# Search for other redirect implementations for comparison
rg -A 5 -B 5 "redirect.*url|url.*redirect" --type vue --type ts

Length of output: 93


Action Required: Clarify Redirection Logic

The current implementation of getRedirectUrl in web/pages/redirect.vue shows two points that need manual verification:

  • Redirection Target:
    The function calls parseRedirectTarget(search.get('target')), but available evidence indicates that parseRedirectTarget always returns '/'. This results in ignoring any dynamic target parameter. Please verify if this is the intended behavior or if parseRedirectTarget should actually process different target values.

  • Search Size Condition:
    The condition (search.size === 1 || search.size === 2) used alongside checking for the presence of a 'data' parameter is unclear. Confirm whether these bounds are necessary and, if so, document their purpose.

Please review and confirm these behaviors to ensure the redirection logic meets the intended requirements.

Comment on lines +20 to +29
onMounted(() => {
setTimeout(() => {
const textElement = document.getElementById('text');
if (textElement) {
textElement.style.display = 'block';
}
}, 750);

window.location.href = getRedirectUrl();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing import for onMounted

The onMounted function is used but there's no import statement for it.

<script setup lang="ts">
+import { onMounted } from 'vue';

const parseRedirectTarget = (target: string | 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
onMounted(() => {
setTimeout(() => {
const textElement = document.getElementById('text');
if (textElement) {
textElement.style.display = 'block';
}
}, 750);
window.location.href = getRedirectUrl();
});
<script setup lang="ts">
import { onMounted } from 'vue';
const parseRedirectTarget = (target: string | null) => {
// ... rest of the code
}
onMounted(() => {
setTimeout(() => {
const textElement = document.getElementById('text');
if (textElement) {
textElement.style.display = 'block';
}
}, 750);
window.location.href = getRedirectUrl();
});
</script>

Comment on lines +2 to +9
const parseRedirectTarget = (target: string | null) => {
if (target && target !== '/') {
// parse target and ensure it is a bare path with no query parameters
console.log(target);
return '/';
}
return '/';
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

The parseRedirectTarget function doesn't perform any actual validation

This function has a comment indicating it should parse and ensure the target is a bare path without query parameters, but it actually just logs the target and always returns '/'. This makes the conditional logic ineffective as the function will always return the home route regardless of input.

const parseRedirectTarget = (target: string | null) => {
  if (target && target !== '/') {
    // parse target and ensure it is a bare path with no query parameters
-    console.log(target);
-    return '/';
+    // Remove query parameters if present
+    const parsedUrl = new URL(target, window.location.origin);
+    return parsedUrl.pathname;
  }
  return '/';
};
📝 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 parseRedirectTarget = (target: string | null) => {
if (target && target !== '/') {
// parse target and ensure it is a bare path with no query parameters
console.log(target);
return '/';
}
return '/';
};
const parseRedirectTarget = (target: string | null) => {
if (target && target !== '/') {
// parse target and ensure it is a bare path with no query parameters
// Remove query parameters if present
const parsedUrl = new URL(target, window.location.origin);
return parsedUrl.pathname;
}
return '/';
};

@github-actions
Copy link
Contributor

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1280/dynamix.unraid.net.plg

};

onMounted(() => {
setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like it will immediately redirect without ever showing the text. What's the desired ux for this?

@elibosley elibosley merged commit 2266139 into main Mar 28, 2025
8 checks passed
@elibosley elibosley deleted the chore/callback-extracted-to-library branch March 28, 2025 20:11
elibosley pushed a commit that referenced this pull request Apr 2, 2025
🤖 I have created a release *beep* *boop*
---


## [4.5.0](v4.4.1...v4.5.0)
(2025-04-02)


### Features

* add webgui theme switcher component
([#1304](#1304))
([e2d00dc](e2d00dc))
* api plugin system & offline versioned dependency vendoring
([#1252](#1252))
([9f492bf](9f492bf))
* **api:** add `unraid-api --delete` command
([#1289](#1289))
([2f09445](2f09445))
* basic array controls
([#1291](#1291))
([61fe696](61fe696))
* basic docker controls
([#1292](#1292))
([12eddf8](12eddf8))
* copy to webgui repo script docs + wc build options
([#1285](#1285))
([e54f189](e54f189))


### Bug Fixes

* additional url fixes
([4b2763c](4b2763c))
* **api:** redirect benign pnpm postinstall warning to log file
([#1290](#1290))
([7fb7849](7fb7849))
* **deps:** update dependency chalk to v5
([#1296](#1296))
([6bed638](6bed638))
* **deps:** update dependency diff to v7
([#1297](#1297))
([3c6683c](3c6683c))
* disable all config watchers
([#1306](#1306))
([5c1b435](5c1b435))
* extract callbacks to library
([#1280](#1280))
([2266139](2266139))
* OEM plugin issues ([#1288](#1288))
([d5a3d0d](d5a3d0d))
* replace files lost during pruning
([d0d2ff6](d0d2ff6))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

5 participants