Skip to content

Conversation

@jnsdls
Copy link
Member

@jnsdls jnsdls commented Aug 27, 2025

Add reason code type into team capabilities response

This PR enhances the team capabilities response by:

  • Adding a ReasonCode type with specific values like "free_limit_exceeded", "subscription_required", etc.
  • Creating an EnabledWithReason<T> generic type that includes a reason code when a capability is disabled
  • Updating all service capability types to use this new pattern
  • Improving the authorization flow to provide more specific error messages based on the reason code
  • Adding different HTTP status codes and error messages for various scenarios (402 for payment-related issues, 403 for other restrictions)

These changes allow for more detailed feedback to users when a service is disabled, with specific guidance based on the reason (e.g., directing to billing page for payment issues or support for enterprise features).


PR-Codex overview

This PR introduces a reasonCode for team capabilities responses, enhancing the error handling for service access based on subscription status. It modifies the type definitions and adds specific error messages based on different reasons for service unavailability.

Detailed summary

  • Added reasonCode to websockets and storage capabilities.
  • Introduced new ReasonCode type with specific reasons.
  • Updated EnabledWithReason type to include reasonCode.
  • Refactored authorize function to handle specific error cases based on reasonCode.
  • Renamed isServiceEnabledForTeam to getServiceDisabledReason, returning ReasonCode instead of a boolean.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features
    • Capabilities now include detailed enablement status with reason codes (free_limit_exceeded, subscription_required, invoice_past_due, enterprise_plan_required, other).
    • Added a websockets capability with connection/subscription limits inside team capabilities.
    • Authorization responses are more specific: tailored 402/403 statuses with contextual team info and direct guidance links (upgrade, billing, invoices, support).
  • Chores
    • Added a patch changeset documenting the capability reason codes and schema alignment.

@vercel
Copy link

vercel bot commented Aug 27, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
docs-v2 Ready Ready Preview Comment Aug 27, 2025 5:12pm
nebula Ready Ready Preview Comment Aug 27, 2025 5:12pm
thirdweb_playground Ready Ready Preview Comment Aug 27, 2025 5:12pm
thirdweb-www Ready Ready Preview Comment Aug 27, 2025 5:12pm
wallet-ui Ready Ready Preview Comment Aug 27, 2025 5:12pm

@changeset-bot
Copy link

changeset-bot bot commented Aug 27, 2025

🦋 Changeset detected

Latest commit: 463f5c8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@thirdweb-dev/service-utils Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 27, 2025

Walkthrough

Adds a ReasonCode type and EnabledWithReason, updates TeamCapabilities to use it (including nested websockets), changes authorize() to return specific reason-based 402/403 responses, and updates dashboard stubs and service mocks and a changeset documenting the API-type addition.

Changes

Cohort / File(s) Summary
Changeset
.changeset/fuzzy-bars-wish.md
Adds a patch changeset entry for @thirdweb-dev/service-utils documenting the addition of a reason-code-aware capability type.
Capability types & schema
packages/service-utils/src/core/api.ts
Adds ReasonCode and EnabledWithReason<T>; refactors TeamCapabilities fields (rpc, insight, storage, nebula, bundler, embeddedWallets, engineCloud, pay, mcp, gateway) to use EnabledWithReason; adds nested insight.websockets capability; updates doc comment.
Authorization flow
packages/service-utils/src/core/authorize/index.ts
Replaces boolean gate with getServiceDisabledReason(scope, capabilities) returning `ReasonCode
Stubs & mocks
Dashboard stub
apps/dashboard/src/@/storybook/stubs.ts, Service mock
packages/service-utils/src/mocks.ts
Adds websockets configuration under capabilities.rpc with enabled: false, reasonCode: "enterprise_plan_required", and zeroed limits in stub and mock team responses.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Service as API Endpoint
  participant Auth as authorize()
  participant Caps as getServiceDisabledReason()
  participant Key as Key Validator

  Client->>Service: Request (team scope)
  Service->>Auth: authorize(request, scope, capabilities)
  rect rgba(230,240,255,0.5)
    note over Auth,Caps: Check capability state (EnabledWithReason)
    Auth->>Caps: Evaluate scope in capabilities
    Caps-->>Auth: ReasonCode | null
  end

  alt Disabled (ReasonCode)
    note right of Auth: Map reason → 402/403 + URL
    Auth-->>Service: Error { status: 402/403, code, message }
    Service-->>Client: Error response
  else Enabled (null)
    Auth->>Key: Validate API key / permissions
    Key-->>Auth: OK / Error
    alt Key OK
      Auth-->>Service: Proceed
      Service-->>Client: Success
    else Key Error
      Auth-->>Service: 401/403
      Service-->>Client: Error
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Add_reason_code_type_to_team_capabilities_response

🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

Copy link
Member Author

jnsdls commented Aug 27, 2025


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@jnsdls jnsdls marked this pull request as ready for review August 27, 2025 05:24
@jnsdls jnsdls requested a review from a team as a code owner August 27, 2025 05:24
@codecov
Copy link

codecov bot commented Aug 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.53%. Comparing base (e651d0a) to head (463f5c8).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7921   +/-   ##
=======================================
  Coverage   56.53%   56.53%           
=======================================
  Files         904      904           
  Lines       58592    58592           
  Branches     4143     4143           
=======================================
  Hits        33126    33126           
  Misses      25360    25360           
  Partials      106      106           
Flag Coverage Δ
packages 56.53% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Aug 27, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 64.06 KB (0%) 1.3 s (0%) 249 ms (+192.12% 🔺) 1.6 s
thirdweb (cjs) 357.05 KB (0%) 7.2 s (0%) 744 ms (+18.23% 🔺) 7.9 s
thirdweb (minimal + tree-shaking) 5.73 KB (0%) 115 ms (0%) 74 ms (+1269.28% 🔺) 188 ms
thirdweb/chains (tree-shaking) 526 B (0%) 11 ms (0%) 76 ms (+1390.73% 🔺) 86 ms
thirdweb/react (minimal + tree-shaking) 19.15 KB (0%) 383 ms (0%) 89 ms (+699.04% 🔺) 472 ms

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: 3

🧹 Nitpick comments (2)
packages/service-utils/src/core/api.ts (1)

60-67: Make disabled branch Partial to mirror real API payloads.

If the API omits capacity fields when a capability is disabled, the current intersection forces those fields to exist. Safer to require them only when enabled.

-type EnabledWithReason<T> = T &
-  ({ enabled: true } | { enabled: false; reasonCode: ReasonCode });
+type EnabledWithReason<T> =
+  | (T & { enabled: true })
+  | (Partial<T> & { enabled: false; reasonCode: ReasonCode });
packages/service-utils/src/core/authorize/index.ts (1)

148-189: Optional: deduplicate error message URLs to avoid drift.

Extract small helpers (billingUrl, invoicesUrl, supportUrl) or a map keyed by ReasonCode.

I can push a follow-up diff if you want the helpers added now.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e651d0a and a3cb5f3.

📒 Files selected for processing (5)
  • .changeset/fuzzy-bars-wish.md (1 hunks)
  • apps/dashboard/src/@/storybook/stubs.ts (1 hunks)
  • packages/service-utils/src/core/api.ts (1 hunks)
  • packages/service-utils/src/core/authorize/index.ts (3 hunks)
  • packages/service-utils/src/mocks.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose

Files:

  • packages/service-utils/src/mocks.ts
  • apps/dashboard/src/@/storybook/stubs.ts
  • packages/service-utils/src/core/authorize/index.ts
  • packages/service-utils/src/core/api.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)

Files:

  • packages/service-utils/src/mocks.ts
  • apps/dashboard/src/@/storybook/stubs.ts
  • packages/service-utils/src/core/authorize/index.ts
  • packages/service-utils/src/core/api.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/@/storybook/stubs.ts
🧬 Code graph analysis (1)
packages/service-utils/src/core/authorize/index.ts (1)
packages/service-utils/src/core/api.ts (3)
  • CoreServiceConfig (18-34)
  • TeamResponse (136-168)
  • ReasonCode (60-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Size
🔇 Additional comments (3)
packages/service-utils/src/core/api.ts (1)

74-81: LGTM: rpc.websockets is modeled with nested EnabledWithReason.

The nested gating reads clearly and aligns with the new pattern.

packages/service-utils/src/mocks.ts (1)

95-100: LGTM: stub reflects disabled websockets with a specific reasonCode.

Shape matches EnabledWithReason and helps exercise the 402/403 pathing.

apps/dashboard/src/@/storybook/stubs.ts (1)

77-82: LGTM: dashboard stub mirrors the new rpc.websockets capability.

Consistent with service-utils typing and mocks.

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: 1

♻️ Duplicate comments (1)
packages/service-utils/src/core/authorize/index.ts (1)

143-147: Pass serviceAction so sub-feature (e.g., rpc websockets) can return accurate reason codes.

Currently only top-level scope is checked; nested disables will be missed.

-  const disabledReason = getServiceDisabledReason(
-    serviceConfig.serviceScope,
-    teamAndProjectResponse.team.capabilities,
-  );
+  const disabledReason = getServiceDisabledReason(
+    serviceConfig.serviceScope,
+    teamAndProjectResponse.team.capabilities,
+    serviceConfig.serviceAction,
+  );
🧹 Nitpick comments (2)
packages/service-utils/src/core/api.ts (1)

60-67: Export EnabledWithReason for reuse across modules.

Other modules (e.g., mocks, consumers defining sub-capabilities) will likely need this utility type.

-export type ReasonCode =
+export type ReasonCode =
   | "free_limit_exceeded"
   | "subscription_required"
   | "invoice_past_due"
   | "enterprise_plan_required"
   | "other";
-type EnabledWithReason<T> = T &
+export type EnabledWithReason<T> = T &
   ({ enabled: true } | { enabled: false; reasonCode: ReasonCode });
packages/service-utils/src/core/authorize/index.ts (1)

148-189: Safely embed team slug in URLs and DRY the message prefix.

Use encodeURIComponent to avoid malformed URLs if slug ever contains unexpected chars, and centralize the slug to reduce repetition.

-  if (disabledReason) {
+  if (disabledReason) {
+    const teamSlug = encodeURIComponent(teamAndProjectResponse.team.slug);
     switch (disabledReason) {
       case "enterprise_plan_required": {
         return {
           authorized: false,
           errorCode: "ENTERPRISE_PLAN_REQUIRED",
-          errorMessage: `You currently do not have access to this feature. Please reach out to us to upgrade your plan to enable this feature: https://thirdweb.com/team/${teamAndProjectResponse.team.slug}/~/support`,
+          errorMessage: `You currently do not have access to this feature. Please reach out to us to upgrade your plan to enable this feature: https://thirdweb.com/team/${teamSlug}/~/support`,
           status: 402,
         };
       }
       case "free_limit_exceeded": {
         return {
           authorized: false,
           errorCode: "FREE_LIMIT_EXCEEDED",
-          errorMessage: `You have exceeded the free limit for this service. Find a plan that suits your needs to continue using this feature: https://thirdweb.com/team/${teamAndProjectResponse.team.slug}/~/billing?showPlans=true&highlight=growth`,
+          errorMessage: `You have exceeded the free limit for this service. Find a plan that suits your needs to continue using this feature: https://thirdweb.com/team/${teamSlug}/~/billing?showPlans=true&highlight=growth`,
           status: 402,
         };
       }
       case "subscription_required": {
         return {
           authorized: false,
           errorCode: "SUBSCRIPTION_REQUIRED",
-          errorMessage: `You need a subscription to use this feature. Find a plan that suits your needs to continue using this feature: https://thirdweb.com/team/${teamAndProjectResponse.team.slug}/~/billing?showPlans=true&highlight=growth`,
+          errorMessage: `You need a subscription to use this feature. Find a plan that suits your needs to continue using this feature: https://thirdweb.com/team/${teamSlug}/~/billing?showPlans=true&highlight=growth`,
           status: 402,
         };
       }
       case "invoice_past_due": {
         return {
           authorized: false,
           errorCode: "INVOICE_PAST_DUE",
-          errorMessage: `Please pay any outstanding invoices to continue using this feature: https://thirdweb.com/team/${teamAndProjectResponse.team.slug}/~/billing/invoices`,
+          errorMessage: `Please pay any outstanding invoices to continue using this feature: https://thirdweb.com/team/${teamSlug}/~/billing/invoices`,
           status: 402,
         };
       }
       default: {
         return {
           authorized: false,
           errorCode: "SERVICE_TEMPORARILY_DISABLED",
-          errorMessage: `Access to this feature is temporarily restricted. Please reach out to us to resolve this issue: https://thirdweb.com/team/${teamAndProjectResponse.team.slug}/~/support`,
+          errorMessage: `Access to this feature is temporarily restricted. Please reach out to us to resolve this issue: https://thirdweb.com/team/${teamSlug}/~/support`,
           status: 403,
         };
       }
     }
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a3cb5f3 and 463f5c8.

📒 Files selected for processing (5)
  • .changeset/fuzzy-bars-wish.md (1 hunks)
  • apps/dashboard/src/@/storybook/stubs.ts (1 hunks)
  • packages/service-utils/src/core/api.ts (1 hunks)
  • packages/service-utils/src/core/authorize/index.ts (3 hunks)
  • packages/service-utils/src/mocks.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .changeset/fuzzy-bars-wish.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/service-utils/src/mocks.ts
  • apps/dashboard/src/@/storybook/stubs.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose

Files:

  • packages/service-utils/src/core/api.ts
  • packages/service-utils/src/core/authorize/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)

Files:

  • packages/service-utils/src/core/api.ts
  • packages/service-utils/src/core/authorize/index.ts
🧬 Code graph analysis (1)
packages/service-utils/src/core/authorize/index.ts (1)
packages/service-utils/src/core/api.ts (3)
  • CoreServiceConfig (18-34)
  • TeamResponse (136-168)
  • ReasonCode (60-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: Size
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: Lint Packages
  • GitHub Check: Unit Tests
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
packages/service-utils/src/core/api.ts (2)

56-59: Doc tweak LGTM.

Wording update is clear and accurate.


74-81: Modeling for rpc.websockets is good; ensure auth path enforces sub-capability gating.

Types now allow websockets to be disabled while rpc is enabled. Verify authorize() checks nested websockets when serviceAction indicates it. See my suggested fix in authorize/index.ts.

packages/service-utils/src/core/authorize/index.ts (2)

4-4: Type-only import LGTM.

Keeps it out of runtime bundles.


225-266: Update getServiceDisabledReason call and verify new capability fields

The change to add an optional serviceAction parameter is a breaking change. You must update the existing invocation in packages/service-utils/src/core/authorize/index.ts (around line 143) to pass the new argument, and ensure the new rpc.websockets, mcp, and gateway capability fields actually exist on your TeamResponse["capabilities"] type.

• In authorize/index.ts, change:

const disabledReason = getServiceDisabledReason(
  serviceConfig.serviceScope,
  teamAndProjectResponse.team.capabilities,
);

to something like:

const disabledReason = getServiceDisabledReason(
  serviceConfig.serviceScope,
  teamAndProjectResponse.team.capabilities,
  serviceConfig.serviceAction,  // new param
);

• Confirm that
teamCapabilities.rpc.websockets (with its .enabled and .reasonCode) is defined in your TeamResponse.capabilities type.
teamCapabilities.mcp and teamCapabilities.gateway exist and follow the same shape (enabled, reasonCode).
– Adjust your type definitions or property names if any of these fields are missing or named differently.

Please verify those call sites and type definitions to avoid build errors or missing checks.

@jnsdls jnsdls merged commit d4a44f5 into main Aug 27, 2025
24 checks passed
@jnsdls jnsdls deleted the Add_reason_code_type_to_team_capabilities_response branch August 27, 2025 18:46
@joaquim-verges joaquim-verges mentioned this pull request Aug 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Dashboard Involves changes to the Dashboard. packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants