- 
                Notifications
    
You must be signed in to change notification settings  - Fork 5.5k
 
Adding interactive Connect functionality to Connect docs #16414
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
Conversation
- Consolidated all functionality into GlobalConnectProvider, TokenGenerationDemo, and AccountConnectionDemo - Removed multiple smaller components that had overlapping code - Simplified the component hierarchy - Reduced code duplication - Maintained the same functionality in the quickstart docs
| 
           The latest updates on your projects. Learn more about Vercel for Git ↗︎ 3 Skipped Deployments
  | 
    
          
WalkthroughThis update introduces a set of interactive demo components and supporting utilities for the Managed Auth quickstart documentation. New React components provide live demonstrations for generating Connect tokens, connecting accounts, and using Connect Links, all managed within a shared global context provider. Supporting modules include API client utilities, code snippet generators, and a custom code block component with syntax highlighting and copy-to-clipboard functionality. Backend API endpoints were added for token generation and account detail retrieval, with robust CORS and security validation. The documentation page was refactored to use these interactive components, and custom PrismJS styles were integrated for improved code display. Changes
 Sequence Diagram(s)sequenceDiagram
    participant User
    participant DocsPage
    participant GlobalConnectProvider
    participant TokenGenerationDemo
    participant AccountConnectionDemo
    participant ConnectLinkDemo
    participant API_Server
    participant Pipedream_API
    User->>DocsPage: Loads Managed Auth Quickstart
    DocsPage->>GlobalConnectProvider: Wraps all demo components
    User->>TokenGenerationDemo: Clicks "Generate Token"
    TokenGenerationDemo->>GlobalConnectProvider: generateToken()
    GlobalConnectProvider->>API_Server: POST /api-demo-connect/token
    API_Server->>Pipedream_API: Auth + Create Connect Token
    Pipedream_API-->>API_Server: Returns Connect Token
    API_Server-->>GlobalConnectProvider: Returns Connect Token
    GlobalConnectProvider-->>TokenGenerationDemo: Updates tokenData
    User->>AccountConnectionDemo: Clicks "Connect Account"
    AccountConnectionDemo->>GlobalConnectProvider: connectAccount()
    GlobalConnectProvider->>Pipedream_API: Initiate Account Connection (SDK)
    Pipedream_API-->>GlobalConnectProvider: Returns Account Info
    GlobalConnectProvider-->>AccountConnectionDemo: Updates connectedAccount
    User->>ConnectLinkDemo: Views/copies Connect Link URL
    Suggested labels
 Suggested reviewers
 Poem
 Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
 docs-v2/components/GlobalConnectProvider.jsxOops! Something went wrong! :( ESLint: 8.57.1 Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
 🚧 Files skipped from review as they are similar to previous changes (1)
 ⏰ Context from checks skipped due to timeout of 90000ms (3)
 ✨ Finishing Touches
 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit: 
 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)
 Other keywords and placeholders
 CodeRabbit Configuration File (
 | 
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Nitpick comments (15)
docs-v2/pages/api/demo-connect/utils.js (1)
51-60: Consider tightening referer validationThe current implementation allows referers that include "/docs/connect/" regardless of origin domain. This could potentially be exploited if an attacker can set up a domain with this path.
- if ( - referer && - !ALLOWED_ORIGINS.some((allowed) => referer.startsWith(allowed)) && - !referer.includes("/docs/connect/") - ) { + if ( + referer && + !ALLOWED_ORIGINS.some((allowed) => + referer.startsWith(allowed) || + (referer.includes("/docs/connect/") && referer.startsWith(allowed)) + ) + ) {docs-v2/pages/api/demo-connect/accounts/[id].js (2)
11-41: Consider improved error handling and loggingWhile the overall implementation is solid, the error handling could be improved. The current implementation masks all errors with a generic message, which might make debugging difficult.
try { // Initialize the Pipedream SDK client const pd = createBackendClient({ environment: process.env.PIPEDREAM_PROJECT_ENVIRONMENT || "development", credentials: { clientId: process.env.PIPEDREAM_CLIENT_ID, clientSecret: process.env.PIPEDREAM_CLIENT_SECRET, }, projectId: process.env.PIPEDREAM_PROJECT_ID, }); // Fetch the specific account by ID const accountDetails = await pd.getAccountById(id); // Return the account details return res.status(200).json(accountDetails); } catch (err) { + // Log the error for server-side debugging + console.error(`Failed to fetch account details for ID ${id}:`, err); return res.status(500).json({ error: "Failed to fetch account details", }); }
14-18: Consider adding ID format validationThe code checks if the ID exists but doesn't validate its format or content. Adding format validation would improve security and error handling.
if (!id) { return res.status(400).json({ error: "Account ID is required", }); } + + // Validate ID format (assuming it should be a UUID or specific format) + const idPattern = /^[a-f0-9\-]+$/i; // Example pattern for UUID-like IDs + if (!idPattern.test(id)) { + return res.status(400).json({ + error: "Invalid account ID format", + }); + }docs-v2/components/TokenGenerationDemo.jsx (1)
33-43: Consider adding aria-busy for better accessibilityThe button shows a loading state visually, but adding aria-busy would improve accessibility for screen readers.
<button onClick={generateToken} disabled={tokenLoading} + aria-busy={tokenLoading} className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors disabled:opacity-50 font-medium text-sm" > {tokenLoading ? "Generating..." : "Generate Token"} </button>docs-v2/components/ConnectCodeSnippets.js (3)
16-16: Consider making the environment configurable instead of hardcoded.The
environmentis currently hardcoded as "development". Consider making this a parameter or reading from an environment variable to support different environments.- environment: "development", + environment: process.env.PIPEDREAM_PROJECT_ENVIRONMENT || "development",
42-42: Fix indentation in the code snippet template.There's an indentation issue in the template string that will result in misaligned code in the rendered snippet. The
pdinitialization and the following line should have consistent indentation.- const pd = createFrontendClient() - pd.connectAccount({ + const pd = createFrontendClient() + pd.connectAccount({
44-47: Simplify the token value template expression for better readability.The conditional expression for the token value spans multiple lines, making the template string harder to read. Consider simplifying this with a ternary operator or a variable.
- token: "${tokenData?.token - ? tokenData.token - : "{connect_token}"}", + token: "${tokenData?.token || "{connect_token}"}",docs-v2/components/AccountConnectionDemo.jsx (1)
44-51: Consider adding a loading state for the connect button.While the component handles the loading state for account details, there's no visual indication when the connect operation is in progress. Consider adding a loading state to the connect button.
import { useGlobalConnect } from "./GlobalConnectProvider"; import CodeBlock from "./CodeBlock"; +import SDKButton from "./SDKButton"; // ... - <button + <SDKButton onClick={connectAccount} disabled={!tokenData} - className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors disabled:opacity-50 font-medium text-sm" + isLoading={connectLoading} > Connect Account - </button> + </SDKButton>Note: This assumes there's a
connectLoadingstate in the global connect context and thatSDKButtonis similar to the button used inTokenGenerationDemo.docs-v2/pages/connect/managed-auth/quickstart.mdx (1)
4-4: Remove unused import.The
VideoPlayercomponent is imported but not used in this file.-import VideoPlayer from "@/components/VideoPlayer"docs-v2/pages/api/demo-connect/token.js (1)
22-33: Consider extracting API URLs and request configurations to constants.The API URLs and request configurations are hardcoded in the function. Consider extracting these to constants or configuration objects at the module level for better maintainability.
+const PIPEDREAM_API_BASE_URL = "https://api.pipedream.com/v1"; +const PIPEDREAM_OAUTH_ENDPOINT = `${PIPEDREAM_API_BASE_URL}/oauth/token`; +const PIPEDREAM_CONNECT_TOKENS_ENDPOINT = (projectId) => + `${PIPEDREAM_API_BASE_URL}/connect/${projectId}/tokens`; async function tokenHandler(req, res) { // ... // First, obtain an OAuth access token - const tokenResponse = await fetch("https://api.pipedream.com/v1/oauth/token", { + const tokenResponse = await fetch(PIPEDREAM_OAUTH_ENDPOINT, { // ... }); // Later in the code... - const connectTokenResponse = await fetch(`https://api.pipedream.com/v1/connect/${process.env.PIPEDREAM_PROJECT_ID}/tokens`, { + const connectTokenResponse = await fetch(PIPEDREAM_CONNECT_TOKENS_ENDPOINT(process.env.PIPEDREAM_PROJECT_ID), { // ... });docs-v2/components/GlobalConnectProvider.jsx (3)
11-17: Consider using an established UUID library instead of custom implementation.While your implementation follows the UUID v4 format, consider using established libraries like
uuidto ensure cryptographically secure random values and standard compliance.-function generateUUID() { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - const r = Math.random() * 16 | 0; - const v = c === "x" ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); -} +import { v4 as uuidv4 } from 'uuid'; + +function generateUUID() { + return uuidv4(); +}🧰 Tools
🪛 GitHub Check: Lint Code Base
[failure] 14-14:
Expected newline between test and consequent of ternary expression
56-63: Add timeout handling to API requests.The token generation API call lacks timeout handling, which could leave users waiting indefinitely on network issues.
try { - const data = await generateConnectToken(externalUserId); + const data = await Promise.race([ + generateConnectToken(externalUserId), + new Promise((_, reject) => + setTimeout(() => reject(new Error("Request timed out")), 10000) + ) + ]); setTokenData(data); } catch (err) { setError(err.message || "An error occurred"); }
78-108: Consider adding connection loading state to improve UX.The account connection process doesn't indicate a loading state to the user, which might cause confusion during the connection flow.
+ const [connectionLoading, setConnectionLoading] = useState(false); function connectAccount() { if (!tokenData?.token) { setError("Please generate a token first"); return; } setError(null); + setConnectionLoading(true); try { const pd = createFrontendClient(); pd.connectAccount({ app: appSlug, token: tokenData.token, onSuccess: async (account) => { + setConnectionLoading(false); // Initialize with just the ID and loading state setConnectedAccount({ id: account.id, loading: true, }); // Rest of the code... }, onError: (err) => { + setConnectionLoading(false); setError(err.message || "Failed to connect account"); }, onClose: () => { + setConnectionLoading(false); // Dialog closed by user - no action needed }, }); } catch (err) { + setConnectionLoading(false); setError(err.message || "An error occurred"); } } // Add to contextValue const contextValue = { // Existing properties... tokenLoading, + connectionLoading, // Rest of the properties... };docs-v2/components/api.js (2)
47-64: Add timeout handling for fetch requests.API requests should have timeouts to prevent indefinite waiting on connection issues.
export async function fetchAccountDetails(accountId) { const requestToken = generateRequestToken(); - const response = await fetch(`/docs/api-demo-connect/accounts/${accountId}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - "X-Request-Token": requestToken, - }, - }); + + // Create an AbortController to handle timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout + + try { + const response = await fetch(`/docs/api-demo-connect/accounts/${accountId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "X-Request-Token": requestToken, + }, + signal: controller.signal + }); + + clearTimeout(timeoutId); if (!response.ok) { return { id: accountId, }; // Fall back to just the ID } return await response.json(); + } catch (error) { + clearTimeout(timeoutId); + if (error.name === 'AbortError') { + console.error('Request timed out'); + } + return { + id: accountId, + error: error.message || 'Failed to fetch account details' + }; + } }
57-61: Enhance error logging for debugging purposes.When an API request fails, it silently returns a minimal object without logging the error, which complicates debugging.
if (!response.ok) { + console.error(`Failed to fetch account details: ${response.status} ${response.statusText}`); + try { + const errorData = await response.json(); + console.error('Error details:', errorData); + } catch (e) { + // Response wasn't JSON, continue with fallback + } return { id: accountId, + error: `Request failed with status: ${response.status}` }; // Fall back to just the ID }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
docs-v2/package-lock.jsonis excluded by!**/package-lock.jsondocs-v2/yarn.lockis excluded by!**/yarn.lock,!**/*.lockpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (16)
docs-v2/components/AccountConnectionDemo.jsx(1 hunks)docs-v2/components/CodeBlock.js(1 hunks)docs-v2/components/ConnectCodeSnippets.js(1 hunks)docs-v2/components/ConnectLinkDemo.jsx(1 hunks)docs-v2/components/GlobalConnectProvider.jsx(1 hunks)docs-v2/components/SDKButton.js(1 hunks)docs-v2/components/TokenGenerationDemo.jsx(1 hunks)docs-v2/components/api.js(1 hunks)docs-v2/next.config.mjs(1 hunks)docs-v2/package.json(1 hunks)docs-v2/pages/api/demo-connect/accounts/[id].js(1 hunks)docs-v2/pages/api/demo-connect/token.js(1 hunks)docs-v2/pages/api/demo-connect/utils.js(1 hunks)docs-v2/pages/connect/managed-auth/quickstart.mdx(4 hunks)docs-v2/styles/globals.css(1 hunks)docs-v2/styles/prism-custom.css(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
docs-v2/components/AccountConnectionDemo.jsx (5)
docs-v2/components/GlobalConnectProvider.jsx (5)
useGlobalConnect(143-149)appSlug(27-27)tokenData(32-32)error(36-36)connectedAccount(29-29)docs-v2/components/TokenGenerationDemo.jsx (1)
useGlobalConnect(7-13)docs-v2/components/ConnectLinkDemo.jsx (1)
useGlobalConnect(10-12)docs-v2/components/CodeBlock.js (1)
CodeBlock(11-108)docs-v2/components/ConnectCodeSnippets.js (1)
getClientCodeSnippet(36-65)
docs-v2/components/ConnectCodeSnippets.js (1)
docs-v2/components/GlobalConnectProvider.jsx (3)
externalUserId(28-28)appSlug(27-27)tokenData(32-32)
docs-v2/components/api.js (2)
docs-v2/pages/api/demo-connect/utils.js (3)
generateRequestToken(16-19)baseString(17-17)requestToken(42-42)docs-v2/components/GlobalConnectProvider.jsx (1)
externalUserId(28-28)
docs-v2/components/TokenGenerationDemo.jsx (4)
docs-v2/components/GlobalConnectProvider.jsx (4)
useGlobalConnect(143-149)externalUserId(28-28)tokenLoading(35-35)tokenData(32-32)docs-v2/components/AccountConnectionDemo.jsx (1)
useGlobalConnect(7-15)docs-v2/components/CodeBlock.js (1)
CodeBlock(11-108)docs-v2/components/ConnectCodeSnippets.js (1)
getServerCodeSnippet(11-28)
docs-v2/pages/api/demo-connect/token.js (1)
docs-v2/pages/api/demo-connect/utils.js (3)
ALLOWED_ORIGINS(6-10)ALLOWED_ORIGINS(6-10)createApiHandler(84-103)
docs-v2/components/ConnectLinkDemo.jsx (2)
docs-v2/components/GlobalConnectProvider.jsx (3)
useGlobalConnect(143-149)tokenData(32-32)appSlug(27-27)docs-v2/components/AccountConnectionDemo.jsx (1)
useGlobalConnect(7-15)
docs-v2/pages/api/demo-connect/utils.js (3)
docs-v2/components/api.js (2)
generateRequestToken(9-14)baseString(12-12)docs-v2/pages/api/demo-connect/accounts/[id].js (1)
req(12-12)docs-v2/pages/api/demo-connect/token.js (1)
req(14-14)
🪛 Biome (1.9.4)
docs-v2/styles/globals.css
[error] 6-6: This @import is in the wrong position.
Any @import rules must precede all other valid at-rules and style rules in a stylesheet (ignoring @charset and @layer), or else the @import rule is invalid.
Consider moving import position.
(lint/correctness/noInvalidPositionAtImportRule)
docs-v2/components/CodeBlock.js
[error] 97-97: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🪛 GitHub Check: Lint Code Base
docs-v2/components/GlobalConnectProvider.jsx
[failure] 3-3:
Expected a line break after this opening brace
[failure] 3-3:
Expected a line break before this closing brace
[failure] 5-5:
Expected a line break after this opening brace
[failure] 5-5:
Expected a line break before this closing brace
[failure] 6-6:
Expected a line break after this opening brace
[failure] 6-6:
Expected a line break before this closing brace
[failure] 14-14:
Expected newline between test and consequent of ternary expression
docs-v2/components/ConnectLinkDemo.jsx
[failure] 7-7:
'CodeBlock' is defined but never used
[failure] 103-103:
This line has a length of 111. Maximum allowed is 100
[failure] 107-107:
This line has a length of 133. Maximum allowed is 100
🪛 GitHub Actions: Pull Request Checks
docs-v2/components/ConnectLinkDemo.jsx
[error] 7-7: ESLint: 'CodeBlock' is defined but never used. (no-unused-vars)
🔇 Additional comments (17)
docs-v2/package.json (2)
22-22: New dependency for API integration looks good.Adding the Pipedream SDK dependency enables integration with the Connect SDK for token generation, account connection, and account details fetching - essential for the interactive demos.
30-30: Good addition for syntax highlighting support.Including PrismJS as a dependency is appropriate for implementing the syntax highlighting in code snippets used throughout the interactive demos.
docs-v2/components/SDKButton.js (1)
1-21: Well-structured reusable button component.This component follows React best practices with:
- Default prop values
 - Clear conditional rendering logic
 - Consistent Tailwind styling
 - Visual feedback for loading and disabled states
 The component will provide a consistent UI element across the Connect interactive demos.
docs-v2/styles/prism-custom.css (1)
1-93: Comprehensive syntax highlighting styles.The PrismJS custom styles provide consistent and visually distinct highlighting for code snippets throughout the interactive demos. The color scheme provides good contrast and readability for all token types.
docs-v2/components/CodeBlock.js (2)
31-61: Good approach for dynamic loading of PrismJS.The implementation correctly:
- Loads PrismJS only on the client side
 - Dynamically imports language definitions as needed
 - Handles manual highlighting mode
 - Provides error handling for highlighting failures
 This approach optimizes performance and ensures compatibility with server-side rendering.
69-106: Excellent copy-to-clipboard implementation with accessible feedback.The copy button implementation:
- Provides visual feedback with icon changes
 - Shows proper loading states
 - Includes appropriate aria labels
 - Has good focus and hover states
 - Is positioned well relative to the code block
 This enhances the user experience when interacting with code examples.
🧰 Tools
🪛 Biome (1.9.4)
[error] 97-97: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs-v2/pages/api/demo-connect/utils.js (4)
6-10: CORS origins configuration looks goodThe ALLOWED_ORIGINS array properly includes production domains and localhost for development, which is a standard security practice.
16-19: Token generation looks secureThe server-side token generation function mirrors the client-side implementation from
docs-v2/components/api.js, correctly using request headers to create a browser-specific token. This helps validate that requests originate from the expected frontend.
24-33: CORS headers are properly implementedThe function correctly sets CORS headers, only allowing specified origins and methods. The conditional check on line 27 ensures origins are validated before being allowed.
84-103: Well-structured API handler wrapperThe
createApiHandlerfunction provides a clean abstraction for applying CORS headers, handling preflight requests, and validating requests. This promotes code reuse and consistent security implementation across API routes.docs-v2/next.config.mjs (1)
553-560: Rewrite rules are correctly implementedThe new rewrite rules for the Connect demo API endpoints follow the same pattern as existing rules and are properly configured to map the hyphenated paths to their corresponding API handlers.
docs-v2/pages/api/demo-connect/accounts/[id].js (1)
43-44: Good use of API handler wrapperUsing the
createApiHandlerutility correctly applies CORS protection and request validation to this endpoint.docs-v2/components/TokenGenerationDemo.jsx (2)
1-14: React component structure is well-implementedThe component correctly uses the
useGlobalConnecthook to access shared state and functions. The client directive ensures proper client-side execution.
15-59: UI implementation follows best practicesThe component has a clean structure with:
- Clear user interface with appropriate headings
 - Proper button state management during loading
 - Conditional rendering of the response data
 - Well-organized styling using Tailwind CSS
 The code is readable and follows React best practices.
docs-v2/components/AccountConnectionDemo.jsx (1)
1-86: Well-structured component with good error and success state handling.The component is well-organized and follows React best practices. It effectively leverages the global connect context and handles different states appropriately.
docs-v2/pages/connect/managed-auth/quickstart.mdx (2)
5-8: Good use of interactive components for enhanced documentation.The addition of these interactive components significantly improves the user experience of the documentation by providing hands-on examples.
154-161: Helpful explanatory footer enhances user understanding.This footer provides valuable context about the implementation flow and user ID generation, which helps users understand the demo better.
| dangerouslySetInnerHTML={{ | ||
| __html: highlightedCode, | ||
| }} | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Consider XSS protection when using dangerouslySetInnerHTML.
While necessary for syntax highlighting, using dangerouslySetInnerHTML can expose users to XSS attacks if the input isn't properly sanitized.
Ensure the code prop is properly sanitized before highlighting or implement a safer alternative for rendering highlighted code. Consider using DOMPurify:
🌐 Web query:
How to safely use dangerouslySetInnerHTML with PrismJS in React?
💡 Result:
Using dangerouslySetInnerHTML with PrismJS in React Safely
When combining PrismJS for syntax highlighting with React components, you'll often need to use dangerouslySetInnerHTML to render the highlighted code. However, this approach requires careful implementation to avoid security vulnerabilities. Here's a comprehensive guide on how to safely implement this combination.
Understanding the Risks
The dangerouslySetInnerHTML prop in React is named that way for a reason - it bypasses React's built-in XSS protections by directly inserting HTML into the DOM[9]. This creates potential security vulnerabilities, particularly cross-site scripting (XSS) attacks, if you're rendering untrusted content[7].
Best Practices for Safe Implementation
1. Sanitize Input Content
Always sanitize any HTML content before passing it to dangerouslySetInnerHTML. This is especially important when working with user-generated content or content from external sources[9][10].
Use established sanitization libraries:
- DOMPurify: A robust library for sanitizing HTML
 - sanitize-html: Provides configurable HTML sanitization
 - js-xss: Specifically designed to prevent XSS attacks[10]
 
Example implementation with DOMPurify:
import DOMPurify from 'dompurify';
import Prism from 'prismjs';
import 'prismjs/themes/prism.css';
function CodeBlock({ code, language }) {
  const highlightedCode = Prism.highlight(
    code,
    Prism.languages[language] || Prism.languages.javascript,
    language
  );
  
  const sanitizedCode = DOMPurify.sanitize(highlightedCode);
  
  return (
    <pre>
      <code 
        className={`language-${language}`}
        dangerouslySetInnerHTML={{ __html: sanitizedCode }}
      />
    </pre>
  );
}2. Proper PrismJS Integration
There are two main approaches to integrating PrismJS with React:
Approach 1: Using marked with PrismJS for Markdown
import React, { useEffect } from 'react';
import Prism from 'prismjs';
import 'prismjs/themes/prism.css';
import { marked } from 'marked';
function MarkdownRenderer({ markdownText }) {
  // Configure marked to use Prism for highlighting
  marked.setOptions({
    breaks: true,
    highlight: function (code, language) {
      if (Prism.languages[language]) {
        return Prism.highlight(code, Prism.languages[language], language);
      }
      return code;
    }
  });
  
  // Create renderer for additional customization if needed
  const renderer = new marked.Renderer();
  
  return (
    <div
      className="markdown-content"
      dangerouslySetInnerHTML={{
        __html: DOMPurify.sanitize(marked(markdownText, { renderer }))
      }}
    />
  );
}This approach is particularly useful when you need to render markdown content with code blocks that require syntax highlighting[5].
Approach 2: Direct PrismJS Highlighting
For cases where you need to highlight specific code snippets:
import React, { useEffect, useRef } from 'react';
import Prism from 'prismjs';
import 'prismjs/themes/prism.css';
function CodeHighlighter({ code, language }) {
  const codeRef = useRef(null);
  
  useEffect(() => {
    if (codeRef.current) {
      Prism.highlightElement(codeRef.current);
    }
  }, [code, language]);
  
  return (
    <pre>
      <code ref={codeRef} className={`language-${language}`}>
        {code}
      </code>
    </pre>
  );
}This approach avoids dangerouslySetInnerHTML altogether by using PrismJS's DOM manipulation directly through the highlightElement method.
3. Timing Considerations
One common issue with PrismJS in React is timing problems, where highlighting occurs before React finishes rendering. To solve this:
function CodeBlock({ code, language }) {
  const codeRef = useRef(null);
  
  useEffect(() => {
    // Ensure highlighting happens after React rendering is complete
    if (codeRef.current) {
      Prism.highlightElement(codeRef.current);
    }
  }, [code, language]);
  
  return (
    <pre>
      <code 
        ref={codeRef} 
        className={`language-${language}`}
      >
        {code}
      </code>
    </pre>
  );
}This approach addresses the potential mismatch between React's rendering cycle and when Prism tries to highlight the code[1].
Alternative Approaches
Using Unified/Remark/Rehype
For more complex markdown processing with syntax highlighting, consider using the unified ecosystem:
import React from 'react';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypePrism from 'rehype-prism';
import rehypeReact from 'rehype-react';
function MarkdownWithSyntaxHighlighting({ markdown }) {
  const content = unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypePrism)
    .use(rehypeReact, { createElement: React.createElement })
    .processSync(markdown).result;
    
  return <div className="markdown-content">{content}</div>;
}This approach avoids dangerouslySetInnerHTML entirely by transforming markdown directly into React elements, which is more secure for user-generated content[8].
When to Avoid dangerouslySetInnerHTML
Consider alternatives to dangerouslySetInnerHTML when:
- Working with user-generated content that could contain malicious code
 - You can achieve the same result using React's component composition
 - You're building a high-security application where XSS protection is critical
 
In these cases, approaches like the unified/remark/rehype pipeline or direct DOM manipulation through refs might be safer options.
Conclusion
While dangerouslySetInnerHTML is sometimes necessary when working with PrismJS in React, you can mitigate risks by:
- Always sanitizing input content with libraries like DOMPurify
 - Considering alternative approaches that avoid direct HTML insertion
 - Being especially careful with user-generated content
 - Properly timing your PrismJS highlight calls in relation to React's rendering lifecycle
 
By following these best practices, you can safely implement code syntax highlighting in your React applications while maintaining robust security protections.
Citations:
- 1: problem with dangerouslySetInnerHTML PrismJS/prism#3041
 - 2: https://github.com/react-syntax-highlighter/react-syntax-highlighter
 - 3: https://kahimyang.com/developer/2957/using-prismjs-to-highlight-code-in-reactnext-components
 - 4: https://andrejdragojevic.com/posts/react-rails-prismjs-syntax-highlighting
 - 5: https://forum.freecodecamp.org/t/markdown-previewer-with-prism-js-highlighting-confusion/482059
 - 6: https://codeparrot.ai/blogs/understanding-dangerouslysetinnerhtml-in-react-a-complete-guide
 - 7: https://dev.to/alakkadshaw/how-to-safely-use-dangerouslysetinnerhtml-in-react-57l1
 - 8: https://www.codedaily.io/tutorials/Markdown-Syntax-Highlighting-with-PrismJS-using-Unified-Remark-and-Rehype
 - 9: https://dev.to/hijazi313/using-dangerouslysetinnerhtml-safely-in-react-and-nextjs-production-systems-115n
 - 10: https://ayeshaiq.hashnode.dev/safely-use-dangerouslysetinnerhtml-in-your-react-applications
 
Sanitize highlighted code before using dangerouslySetInnerHTML
Using dangerouslySetInnerHTML without sanitization can expose users to XSS vulnerabilities. Please update the rendering in docs-v2/components/CodeBlock.js (lines 97–99):
• Import and apply DOMPurify to your highlighted HTML:
 import React from "react";
+import DOMPurify from "dompurify";
 // …
 <code
   className={`language-${language}`}
-  dangerouslySetInnerHTML={{ __html: highlightedCode }}
+  dangerouslySetInnerHTML={{
+    __html: DOMPurify.sanitize(highlightedCode),
+  }}
 />• Alternatively, avoid dangerouslySetInnerHTML entirely by using Prism’s highlightElement with a ref:
import React, { useEffect, useRef } from "react";
import Prism from "prismjs";
function CodeBlock({ code, language }) {
  const codeRef = useRef(null);
  useEffect(() => {
    if (codeRef.current) Prism.highlightElement(codeRef.current);
  }, [code, language]);
  return (
    <pre>
      <code ref={codeRef} className={`language-${language}`}>
        {code}
      </code>
    </pre>
  );
}Select one of these approaches to ensure your code snippets remain XSS-safe.
🧰 Tools
🪛 Biome (1.9.4)
[error] 97-97: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
| // First, obtain an OAuth access token | ||
| const tokenResponse = await fetch("https://api.pipedream.com/v1/oauth/token", { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify({ | ||
| grant_type: "client_credentials", | ||
| client_id: process.env.PIPEDREAM_CLIENT_ID, | ||
| client_secret: process.env.PIPEDREAM_CLIENT_SECRET, | ||
| }), | ||
| }); | ||
| 
               | 
          ||
| if (!tokenResponse.ok) { | ||
| return res.status(500).json({ | ||
| error: "Failed to authenticate with Pipedream API", | ||
| }); | ||
| } | ||
| 
               | 
          ||
| const { access_token } = await tokenResponse.json(); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Validate environment variables before use.
The code assumes that environment variables like PIPEDREAM_CLIENT_ID and PIPEDREAM_CLIENT_SECRET exist. Consider validating these before use to provide more helpful error messages.
async function tokenHandler(req, res) {
  try {
    const { external_user_id } = req.body;
    if (!external_user_id) {
      return res.status(400).json({
        error: "external_user_id is required",
      });
    }
+   // Validate required environment variables
+   const requiredEnvVars = [
+     'PIPEDREAM_CLIENT_ID',
+     'PIPEDREAM_CLIENT_SECRET',
+     'PIPEDREAM_PROJECT_ID'
+   ];
+   
+   const missingEnvVars = requiredEnvVars.filter(varName => !process.env[varName]);
+   
+   if (missingEnvVars.length > 0) {
+     console.error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
+     return res.status(500).json({
+       error: "Server configuration error",
+     });
+   }
    // First, obtain an OAuth access token
    // ...Committable suggestion skipped: line range outside the PR's diff.
| if (!connectTokenResponse.ok) { | ||
| return res.status(500).json({ | ||
| error: "Failed to create Connect token", | ||
| }); | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error reporting with response details.
When the token creation fails, the error response doesn't include details from the Pipedream API. Consider extracting and returning the error details for better debugging.
    if (!connectTokenResponse.ok) {
+     const errorData = await connectTokenResponse.json().catch(() => ({}));
      return res.status(500).json({
        error: "Failed to create Connect token",
+       details: errorData.error || connectTokenResponse.statusText,
      });
    }📝 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.
| if (!connectTokenResponse.ok) { | |
| return res.status(500).json({ | |
| error: "Failed to create Connect token", | |
| }); | |
| } | |
| if (!connectTokenResponse.ok) { | |
| const errorData = await connectTokenResponse.json().catch(() => ({})); | |
| return res.status(500).json({ | |
| error: "Failed to create Connect token", | |
| details: errorData.error || connectTokenResponse.statusText, | |
| }); | |
| } | 
| <button | ||
| onClick={() => { | ||
| navigator.clipboard.writeText(connectLinkUrl); | ||
| }} | ||
| className="px-4 py-2 bg-gray-100 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-200 transition-colors font-medium text-sm inline-flex items-center" | ||
| > | ||
| Copy URL | ||
| <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" /> | ||
| </svg> | ||
| </button> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add user feedback for clipboard operations.
The copy to clipboard button doesn't provide any feedback to the user upon successful copying. Consider adding a temporary visual indicator.
+  const [copyFeedback, setCopyFeedback] = useState(false);
+
+  const handleCopy = () => {
+    navigator.clipboard.writeText(connectLinkUrl)
+      .then(() => {
+        setCopyFeedback(true);
+        setTimeout(() => setCopyFeedback(false), 2000);
+      })
+      .catch(err => console.error('Failed to copy URL:', err));
+  };
  <button
-    onClick={() => {
-      navigator.clipboard.writeText(connectLinkUrl);
-    }}
+    onClick={handleCopy}
    className="px-4 py-2 bg-gray-100 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-200 transition-colors font-medium text-sm inline-flex items-center"
  >
-    Copy URL
+    {copyFeedback ? "Copied!" : "Copy URL"}
    <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
    </svg>
  </button>📝 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.
| <button | |
| onClick={() => { | |
| navigator.clipboard.writeText(connectLinkUrl); | |
| }} | |
| className="px-4 py-2 bg-gray-100 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-200 transition-colors font-medium text-sm inline-flex items-center" | |
| > | |
| Copy URL | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" /> | |
| </svg> | |
| </button> | |
| // Inside your ConnectLinkDemo component (ensure useState is imported from React) | |
| const [copyFeedback, setCopyFeedback] = useState(false); | |
| const handleCopy = () => { | |
| navigator.clipboard | |
| .writeText(connectLinkUrl) | |
| .then(() => { | |
| setCopyFeedback(true); | |
| setTimeout(() => setCopyFeedback(false), 2000); | |
| }) | |
| .catch(err => console.error('Failed to copy URL:', err)); | |
| }; | |
| return ( | |
| <button | |
| onClick={handleCopy} | |
| className="px-4 py-2 bg-gray-100 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-200 transition-colors font-medium text-sm inline-flex items-center" | |
| > | |
| {copyFeedback ? "Copied!" : "Copy URL"} | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| className="h-4 w-4 ml-1" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke="currentColor" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" | |
| /> | |
| </svg> | |
| </button> | |
| ); | 
| export function generateRequestToken() { | ||
| if (typeof window === "undefined") return ""; | ||
| 
               | 
          ||
| const baseString = `${navigator.userAgent}:${window.location.host}:connect-demo`; | ||
| return btoa(baseString); | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Strengthen the request token generation.
The current implementation creates a predictable token based on user agent and host. Consider adding entropy to make tokens less predictable.
export function generateRequestToken() {
  if (typeof window === "undefined") return "";
-  const baseString = `${navigator.userAgent}:${window.location.host}:connect-demo`;
+  const randomValue = Math.random().toString(36).substring(2);
+  const timestamp = Date.now().toString();
+  const baseString = `${navigator.userAgent}:${window.location.host}:connect-demo:${randomValue}:${timestamp}`;
  return btoa(baseString);
}📝 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.
| export function generateRequestToken() { | |
| if (typeof window === "undefined") return ""; | |
| const baseString = `${navigator.userAgent}:${window.location.host}:connect-demo`; | |
| return btoa(baseString); | |
| } | |
| export function generateRequestToken() { | |
| if (typeof window === "undefined") return ""; | |
| const randomValue = Math.random().toString(36).substring(2); | |
| const timestamp = Date.now().toString(); | |
| const baseString = `${navigator.userAgent}:${window.location.host}:connect-demo:${randomValue}:${timestamp}`; | |
| return btoa(baseString); | |
| } | 
| if (!response.ok) { | ||
| const errorData = await response.json(); | ||
| throw new Error(errorData.error || "Failed to get token"); | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error handling for non-JSON responses.
The error handling assumes the error response will always be valid JSON, which may not be true for network errors or server issues.
if (!response.ok) {
-  const errorData = await response.json();
-  throw new Error(errorData.error || "Failed to get token");
+  try {
+    const errorData = await response.json();
+    throw new Error(errorData.error || "Failed to get token");
+  } catch (e) {
+    throw new Error(`Failed to get token: ${response.status} ${response.statusText}`);
+  }
}📝 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.
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.error || "Failed to get token"); | |
| } | |
| if (!response.ok) { | |
| try { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.error || "Failed to get token"); | |
| } catch (e) { | |
| throw new Error(`Failed to get token: ${response.status} ${response.statusText}`); | |
| } | |
| } | 
There was a problem hiding this 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 (3)
docs-v2/pages/api/demo-connect/utils.js (2)
32-49:isAllowedlosesthisif the function is detached
isAllowedrelies onthis.originsList/regexPatterns.
If the method is extracted (const { isAllowed } = ALLOWED_ORIGINS) the context is lost and the check always fails.A safer pattern is to close over the lists instead of relying on
this.- isAllowed(origin) { - if (!origin) return false; - if (this.originsList.includes(origin)) return true; - return this.regexPatterns.some((pattern) => pattern.test(origin)); - }, + isAllowed: (origin) => { + if (!origin) return false; + if (originsList.includes(origin)) return true; + return regexPatterns.some((pattern) => pattern.test(origin)); + },
59-62: Reverse-proxy awareness for token generation
generateRequestTokenusesreq.headers["host"].
Behind a proxy/CDN, the canonical host usually arrives viax-forwarded-host, which means a perfectly valid request could fail the token check after being rewritten by your edge.At minimum, fall back to
x-forwarded-hostbeforehost, e.g.:- const baseString = `${req.headers["user-agent"]}:${req.headers["host"]}:connect-demo`; + const host = + req.headers["x-forwarded-host"] ?? + req.headers["host"] ?? + "unknown-host"; + const baseString = `${req.headers["user-agent"]}:${host}:connect-demo`;docs-v2/pages/connect/managed-auth/quickstart.mdx (1)
39-40: Minor wording fix
see an execute→see and execute.-We'll walk through these steps below with an interactive demo that lets you see an execute the code directly in the docs. +We'll walk through these steps below with an interactive demo that lets you see **and** execute the code directly in the docs.🧰 Tools
🪛 LanguageTool
[misspelling] ~39-~39: Did you mean “and”?
Context: ...h an interactive demo that lets you see an execute the code directly in the docs. ...(AN_AND)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (8)
docs-v2/.env.example(1 hunks)docs-v2/components/AccountConnectionDemo.jsx(1 hunks)docs-v2/components/ConnectLinkDemo.jsx(1 hunks)docs-v2/components/GlobalConnectProvider.jsx(1 hunks)docs-v2/pages/api/demo-connect/token.js(1 hunks)docs-v2/pages/api/demo-connect/utils.js(1 hunks)docs-v2/pages/connect/managed-auth/quickstart.mdx(5 hunks)docs-v2/styles/globals.css(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- docs-v2/.env.example
 
🚧 Files skipped from review as they are similar to previous changes (4)
- docs-v2/styles/globals.css
 - docs-v2/components/GlobalConnectProvider.jsx
 - docs-v2/components/ConnectLinkDemo.jsx
 - docs-v2/pages/api/demo-connect/token.js
 
🧰 Additional context used
🧬 Code Graph Analysis (2)
docs-v2/components/AccountConnectionDemo.jsx (5)
docs-v2/components/ConnectLinkDemo.jsx (1)
useGlobalConnect(10-14)docs-v2/components/GlobalConnectProvider.jsx (5)
useGlobalConnect(174-180)appSlug(40-43)tokenData(54-57)error(64-67)connectedAccount(48-51)docs-v2/components/TokenGenerationDemo.jsx (1)
useGlobalConnect(7-13)docs-v2/components/CodeBlock.js (1)
CodeBlock(11-108)docs-v2/components/ConnectCodeSnippets.js (1)
getClientCodeSnippet(36-65)
docs-v2/pages/api/demo-connect/utils.js (3)
docs-v2/components/api.js (2)
generateRequestToken(9-14)baseString(12-12)docs-v2/pages/api/demo-connect/token.js (1)
req(14-14)docs-v2/pages/api/demo-connect/accounts/[id].js (1)
req(12-12)
🪛 LanguageTool
docs-v2/pages/connect/managed-auth/quickstart.mdx
[misspelling] ~39-~39: Did you mean “and”?
Context: ...h an interactive demo that lets you see an execute the code directly in the docs. ...
(AN_AND)
[uncategorized] ~72-~72: Possible missing comma found.
Context: ...d scope them to end users.  In the code below you can see how we generate a Connect t...
(AI_HYDRA_LEO_MISSING_COMMA)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: pnpm publish
 - GitHub Check: Lint Code Base
 - GitHub Check: validate-links
 
🔇 Additional comments (1)
docs-v2/components/AccountConnectionDemo.jsx (1)
41-42: Verify arguments passed togetClientCodeSnippet
getClientCodeSnippetdefined inConnectCodeSnippets.jsexpects(appSlug, tokenData), but here it’s invoked without parameters.
If the context version isn’t curried, the snippet will renderundefinedplaceholders.Please confirm the provider wraps the function appropriately or pass the arguments explicitly:
- <CodeBlock code={getClientCodeSnippet()} language="javascript" /> + <CodeBlock code={getClientCodeSnippet(appSlug, tokenData)} language="javascript" />
| // Use the new isAllowed method to check if the origin is allowed | ||
| res.setHeader( | ||
| "Access-Control-Allow-Origin", | ||
| ALLOWED_ORIGINS.isAllowed(req.headers.origin) | ||
| ? req.headers.origin | ||
| : "", | ||
| ); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid emitting an empty Access-Control-Allow-Origin header
When an origin is not allowed, the code still sets the CORS header to an empty string.
Browsers treat an empty value as an invalid header and will block the response, but it also adds unnecessary bytes to every reply.
Consider omitting the header entirely (or returning a 403) instead of sending an empty value.
-  res.setHeader(
-    "Access-Control-Allow-Origin",
-    ALLOWED_ORIGINS.isAllowed(req.headers.origin)
-      ? req.headers.origin
-      : "",
-  );
+  if (ALLOWED_ORIGINS.isAllowed(req.headers.origin)) {
+    res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
+  }📝 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.
| // Use the new isAllowed method to check if the origin is allowed | |
| res.setHeader( | |
| "Access-Control-Allow-Origin", | |
| ALLOWED_ORIGINS.isAllowed(req.headers.origin) | |
| ? req.headers.origin | |
| : "", | |
| ); | |
| // Use the new isAllowed method to check if the origin is allowed | |
| if (ALLOWED_ORIGINS.isAllowed(req.headers.origin)) { | |
| res.setHeader("Access-Control-Allow-Origin", req.headers.origin); | |
| } | 
| <option value="google_sheets">Google Sheets</option> | ||
| <option value="github">GitHub</option> | ||
| <option value="notion">Notion</option> | ||
| <option value="gmail">Gmail</option> | ||
| <option value="openai">OpenAI</option> | ||
| </select> | ||
| </label> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default appSlug ("slack") is missing from the <select> list
GlobalConnectProvider initialises appSlug with "slack", but the option is not rendered here.
On first render the <select> will show a blank value, forcing the user to re-select an app.
               <select
                 value={appSlug}
                 onChange={(e) => setAppSlug(e.target.value)}
                 className="ml-2 p-1 border rounded text-sm"
               >
+                <option value="slack">Slack</option>
                 <option value="google_sheets">Google Sheets</option>📝 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.
| <option value="google_sheets">Google Sheets</option> | |
| <option value="github">GitHub</option> | |
| <option value="notion">Notion</option> | |
| <option value="gmail">Gmail</option> | |
| <option value="openai">OpenAI</option> | |
| </select> | |
| </label> | |
| <select | |
| value={appSlug} | |
| onChange={(e) => setAppSlug(e.target.value)} | |
| className="ml-2 p-1 border rounded text-sm" | |
| > | |
| + <option value="slack">Slack</option> | |
| <option value="google_sheets">Google Sheets</option> | |
| <option value="github">GitHub</option> | |
| <option value="notion">Notion</option> | |
| <option value="gmail">Gmail</option> | |
| <option value="openai">OpenAI</option> | |
| </select> | 
There was a problem hiding this 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 (2)
docs-v2/components/ConnectLinkDemo.jsx (1)
91-101: Implement user feedback for clipboard operations.The copy button doesn't provide any feedback to the user when the URL is copied. Consider adding a visual indicator to improve user experience.
+ const [copyFeedback, setCopyFeedback] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(connectLinkUrl) + .then(() => { + setCopyFeedback(true); + setTimeout(() => setCopyFeedback(false), 2000); + }) + .catch(err => console.error('Failed to copy URL:', err)); + }; <button - onClick={() => { - navigator.clipboard.writeText(connectLinkUrl); - }} + onClick={handleCopy} className={styles.secondaryButton} > - Copy URL + {copyFeedback ? "Copied!" : "Copy URL"} <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" /> </svg> </button>docs-v2/components/CodeBlock.js (1)
97-100:⚠️ Potential issueSecure your component against XSS attacks.
The highlighted code is directly inserted using
dangerouslySetInnerHTMLwithout any sanitization, which can expose users to XSS vulnerabilities.Import and use DOMPurify to sanitize the highlighted code before rendering:
"use client"; import { useState, useEffect, } from "react"; + import DOMPurify from "dompurify"; // We don't need the default Prism CSS as we're using our custom CSS // import "prismjs/themes/prism.css"; // ... <code className={`language-${language} text-gray-800 dark:text-gray-200 [text-shadow:none]`} dangerouslySetInnerHTML={{ - __html: highlightedCode, + __html: DOMPurify.sanitize(highlightedCode), }} />This sanitization will help prevent potential XSS attacks while still allowing your syntax highlighting to work correctly.
🧰 Tools
🪛 Biome (1.9.4)
[error] 98-98: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🧹 Nitpick comments (2)
docs-v2/components/CodeBlock.js (2)
43-45: Implement consistent pattern for language imports.You're checking for JSON language support, but not for other potential languages that could be passed as props.
Consider implementing a more generalized approach to language imports:
- if (!Prism.languages.json && language === "json") { - await import("prismjs/components/prism-json"); - } + // Import the requested language if it's not already loaded + if (!Prism.languages[language] && language !== "javascript") { + try { + await import(`prismjs/components/prism-${language}`); + } catch (error) { + console.warn(`Language '${language}' not found in PrismJS, falling back to plain text`); + } + }This approach will dynamically load any requested language, making your component more flexible.
29-62: Consider cleanup for effect dependencies.The current implementation doesn't clean up Prism when the component unmounts or when the language changes.
useEffect(() => { setIsClient(true); + let mounted = true; const loadPrism = async () => { Prism = (await import("prismjs")).default; // Use manual mode so we can control highlighting Prism.manual = true; // Import language definitions dynamically if (!Prism.languages.javascript) { await import("prismjs/components/prism-javascript"); } if (!Prism.languages.json && language === "json") { await import("prismjs/components/prism-json"); } // Apply syntax highlighting try { if (Prism.languages[language]) { const highlighted = Prism.highlight(code, Prism.languages[language], language); - setHighlightedCode(highlighted); + if (mounted) { + setHighlightedCode(highlighted); + } } } catch (error) { console.error("Prism highlighting error:", error); } }; loadPrism(); + return () => { + mounted = false; + }; }, [ code, language, ]);This prevents potential state updates after the component has unmounted, avoiding React warnings.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
docs-v2/components/AccountConnectionDemo.jsx(1 hunks)docs-v2/components/CodeBlock.js(1 hunks)docs-v2/components/ConnectLinkDemo.jsx(1 hunks)docs-v2/components/GlobalConnectProvider.jsx(1 hunks)docs-v2/components/TokenGenerationDemo.jsx(1 hunks)docs-v2/styles/prism-custom.css(1 hunks)docs-v2/utils/componentStyles.js(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- docs-v2/utils/componentStyles.js
 
🚧 Files skipped from review as they are similar to previous changes (4)
- docs-v2/components/TokenGenerationDemo.jsx
 - docs-v2/styles/prism-custom.css
 - docs-v2/components/GlobalConnectProvider.jsx
 - docs-v2/components/AccountConnectionDemo.jsx
 
🧰 Additional context used
🧠 Learnings (1)
docs-v2/components/CodeBlock.js (2)
Learnt from: dylburger
PR: PipedreamHQ/pipedream#12685
File: packages/sdk/examples/next-app/app/CodePanel.tsx:42-42
Timestamp: 2024-10-08T15:33:38.240Z
Learning: In React components, use `DOMPurify.sanitize` to sanitize HTML content instead of using `dangerouslySetInnerHTML` to prevent XSS attacks.
Learnt from: dylburger
PR: PipedreamHQ/pipedream#12685
File: packages/sdk/examples/next-app/app/CodePanel.tsx:42-42
Timestamp: 2024-08-14T17:26:51.614Z
Learning: In React components, use `DOMPurify.sanitize` to sanitize HTML content instead of using `dangerouslySetInnerHTML` to prevent XSS attacks.
🪛 Biome (1.9.4)
docs-v2/components/CodeBlock.js
[error] 98-98: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🪛 GitHub Check: Lint Code Base
docs-v2/components/ConnectLinkDemo.jsx
[failure] 111-111:
This line has a length of 132. Maximum allowed is 100
🪛 GitHub Actions: Pull Request Checks
docs-v2/components/ConnectLinkDemo.jsx
[error] 111-111: ESLint: This line has a length of 132. Maximum allowed is 100. (max-len)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: pnpm publish
 - GitHub Check: validate-links
 
| <strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use. After a successful connection, | ||
| you'll need to generate a new token. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix line length issue.
This line exceeds the maximum allowed length of 100 characters, causing linting errors.
-            <strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use. After a successful connection,
-            you'll need to generate a new token.
+            <strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use. 
+            After a successful connection, you'll need to generate a new token.📝 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.
| <strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use. After a successful connection, | |
| you'll need to generate a new token. | |
| <strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use. | |
| After a successful connection, you'll need to generate a new token. | 
🧰 Tools
🪛 GitHub Check: Lint Code Base
[failure] 111-111:
This line has a length of 132. Maximum allowed is 100
🪛 GitHub Actions: Pull Request Checks
[error] 111-111: ESLint: This line has a length of 132. Maximum allowed is 100. (max-len)
| const copyToClipboard = () => { | ||
| navigator.clipboard.writeText(code); | ||
| setCopied(true); | ||
| setTimeout(() => setCopied(false), 2000); | ||
| }; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for clipboard operations.
The clipboard API might fail in certain browsers or contexts, but there's no error handling in the current implementation.
const copyToClipboard = () => {
-  navigator.clipboard.writeText(code);
-  setCopied(true);
-  setTimeout(() => setCopied(false), 2000);
+  navigator.clipboard.writeText(code)
+    .then(() => {
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
+    })
+    .catch(err => {
+      console.error("Failed to copy code:", err);
+      // Optionally show a user-friendly error message
+    });
};This handles potential errors and makes the component more robust.
📝 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.
| const copyToClipboard = () => { | |
| navigator.clipboard.writeText(code); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| }; | |
| const copyToClipboard = () => { | |
| - navigator.clipboard.writeText(code); | |
| - setCopied(true); | |
| - setTimeout(() => setCopied(false), 2000); | |
| + navigator.clipboard.writeText(code) | |
| + .then(() => { | |
| + setCopied(true); | |
| + setTimeout(() => setCopied(false), 2000); | |
| + }) | |
| + .catch(err => { | |
| + console.error("Failed to copy code:", err); | |
| + // Optionally show a user-friendly error message | |
| + }); | |
| }; | 
Replace custom UUI generation w/ crypto.randomUUID()
WHY
Summary by CodeRabbit
New Features
Documentation
Style
Chores