Skip to content

Conversation

@dannyroosevelt
Copy link
Collaborator

@dannyroosevelt dannyroosevelt commented Apr 23, 2025

WHY

Summary by CodeRabbit

  • New Features

    • Introduced interactive demo components for generating Connect tokens, connecting accounts, and using Connect Links directly within the documentation.
    • Added live code snippet displays with syntax highlighting and copy-to-clipboard functionality for server-side and client-side integration examples.
    • Enabled selection of different apps (Slack, GitHub, Google Sheets, Notion, Gmail, OpenAI) for demo interactions.
    • Provided real-time account connection status, error handling, and detailed connected account information display.
    • Added a reusable styled button component for consistent UI interactions.
  • Documentation

    • Updated the Managed Auth quickstart guide to embed interactive demos, replacing static code samples and images.
    • Added clarifications on Connect Link usage, token expiration, and recommended flow steps.
  • Style

    • Integrated custom Prism syntax highlighting styles for enhanced code block appearance.
  • Chores

    • Added new dependencies for SDK integration and syntax highlighting.
    • Implemented API routes and utility functions with enhanced security and dynamic CORS origin validation to support the interactive demos.
    • Configured URL rewrites for API endpoints.

@vercel
Copy link

vercel bot commented Apr 23, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

3 Skipped Deployments
Name Status Preview Comments Updated (UTC)
docs-v2 ⬜️ Ignored (Inspect) Visit Preview Apr 24, 2025 5:31pm
pipedream-docs ⬜️ Ignored (Inspect) Apr 24, 2025 5:31pm
pipedream-docs-redirect-do-not-edit ⬜️ Ignored (Inspect) Apr 24, 2025 5:31pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 23, 2025

Walkthrough

This 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

File(s) Change Summary
docs-v2/components/AccountConnectionDemo.jsx,
TokenGenerationDemo.jsx,
ConnectLinkDemo.jsx
Added new React components for interactive demos: account connection, token generation, and Connect Link usage. Each component manages its own UI and uses shared context for state and actions.
docs-v2/components/GlobalConnectProvider.jsx Introduced a React context provider and custom hook to manage shared state and actions for the Connect demo, including token generation, account connection, and account details retrieval.
docs-v2/components/CodeBlock.js Added a React component for syntax-highlighted code blocks with copy-to-clipboard functionality, using PrismJS and dynamic imports for client-side compatibility.
docs-v2/components/ConnectCodeSnippets.js Added utility functions to generate parameterized code snippets for server-side and client-side Connect SDK usage, used in the demo components.
docs-v2/components/SDKButton.js Added a styled, reusable button component with loading and disabled states for use in the demo UIs.
docs-v2/components/api.js Added API client functions for demo components: generating a request token, requesting a Connect token, and fetching account details, all with appropriate error handling.
docs-v2/pages/api/demo-connect/token.js,
accounts/[id].js
Added new API endpoints for generating Connect tokens and retrieving account details, using the Pipedream SDK and environment variables for credentials.
docs-v2/pages/api/demo-connect/utils.js Introduced shared utility functions for API routes, including CORS handling, request validation (origin, referer, custom token), and a handler wrapper for secure API endpoints.
docs-v2/pages/connect/managed-auth/quickstart.mdx Refactored the quickstart documentation to replace static code examples with the new interactive demo components, and wrapped the content in the global context provider. Clarified Connect Link instructions and added a summary info block.
docs-v2/next.config.mjs Added rewrite rules to map demo API endpoints with hyphenated paths to their corresponding internal API routes.
docs-v2/package.json Added new dependencies: @pipedream/sdk and prismjs for SDK integration and syntax highlighting.
docs-v2/styles/globals.css Imported a custom Prism syntax highlighting stylesheet into the global CSS.
docs-v2/styles/prism-custom.css Added a new CSS file defining custom PrismJS syntax highlighting styles for code blocks and inline code.
docs-v2/.env.example Added environment variable example file for the Connect demo API integration, including client credentials, project info, and optional redirect URIs and allowed origins.
docs-v2/utils/componentStyles.js Added a utility module exporting a centralized collection of Tailwind CSS class strings for consistent styling of UI components in the Connect demo environment.

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
Loading

Suggested labels

ai-assisted

Suggested reviewers

  • andrewjschuang

Poem

A hop, a skip, a code-filled leap,
Now demos sparkle, no time for sleep!
Tokens are minted, accounts connect fast,
With Prism-lit code blocks, style unsurpassed.
Quickstart’s alive—click, copy, and see,
This bunny’s made docs as fun as can be!
🐇✨

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

docs-v2/components/GlobalConnectProvider.jsx

Oops! Something went wrong! :(

ESLint: 8.57.1

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs
at packageResolve (node:internal/modules/esm/resolve:839:9)
at moduleResolve (node:internal/modules/esm/resolve:908:18)
at defaultResolve (node:internal/modules/esm/resolve:1038:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:557:12)
at ModuleLoader.resolve (node:internal/modules/esm/loader:525:25)
at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:246:38)
at ModuleJob._link (node:internal/modules/esm/module_job:126:49)


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9434f9a and 8ca823a.

📒 Files selected for processing (1)
  • docs-v2/components/GlobalConnectProvider.jsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs-v2/components/GlobalConnectProvider.jsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: pnpm publish
  • GitHub Check: Lint Code Base
  • GitHub Check: validate-links
✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

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

🧹 Nitpick comments (15)
docs-v2/pages/api/demo-connect/utils.js (1)

51-60: Consider tightening referer validation

The 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 logging

While 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 validation

The 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 accessibility

The 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 environment is 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 pd initialization 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 connectLoading state in the global connect context and that SDKButton is similar to the button used in TokenGenerationDemo.

docs-v2/pages/connect/managed-auth/quickstart.mdx (1)

4-4: Remove unused import.

The VideoPlayer component 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 uuid to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 56b5ab7 and 6186932.

⛔ Files ignored due to path filters (3)
  • docs-v2/package-lock.json is excluded by !**/package-lock.json
  • docs-v2/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • pnpm-lock.yaml is 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 good

The ALLOWED_ORIGINS array properly includes production domains and localhost for development, which is a standard security practice.


16-19: Token generation looks secure

The 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 implemented

The 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 wrapper

The createApiHandler function 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 implemented

The 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 wrapper

Using the createApiHandler utility correctly applies CORS protection and request validation to this endpoint.

docs-v2/components/TokenGenerationDemo.jsx (2)

1-14: React component structure is well-implemented

The component correctly uses the useGlobalConnect hook to access shared state and functions. The client directive ensures proper client-side execution.


15-59: UI implementation follows best practices

The 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.

Comment on lines +97 to +99
dangerouslySetInnerHTML={{
__html: highlightedCode,
}}
Copy link
Contributor

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:

  1. Working with user-generated content that could contain malicious code
  2. You can achieve the same result using React's component composition
  3. 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:

  1. Always sanitizing input content with libraries like DOMPurify
  2. Considering alternative approaches that avoid direct HTML insertion
  3. Being especially careful with user-generated content
  4. 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:


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)

Comment on lines +22 to +41
// 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();
Copy link
Contributor

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.

Comment on lines +60 to +64
if (!connectTokenResponse.ok) {
return res.status(500).json({
error: "Failed to create Connect token",
});
}
Copy link
Contributor

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.

Suggested change
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,
});
}

Comment on lines 88 to 98
<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>
Copy link
Contributor

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.

Suggested change
<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>
);

Comment on lines +9 to +14
export function generateRequestToken() {
if (typeof window === "undefined") return "";

const baseString = `${navigator.userAgent}:${window.location.host}:connect-demo`;
return btoa(baseString);
}
Copy link
Contributor

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.

Suggested change
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);
}

Comment on lines +34 to +37
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || "Failed to get token");
}
Copy link
Contributor

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.

Suggested change
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}`);
}
}

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 (3)
docs-v2/pages/api/demo-connect/utils.js (2)

32-49: isAllowed loses this if the function is detached

isAllowed relies on this.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

generateRequestToken uses req.headers["host"].
Behind a proxy/CDN, the canonical host usually arrives via x-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-host before host, 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 executesee 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6186932 and acaac74.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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 to getClientCodeSnippet

getClientCodeSnippet defined in ConnectCodeSnippets.js expects (appSlug, tokenData), but here it’s invoked without parameters.
If the context version isn’t curried, the snippet will render undefined placeholders.

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" />

Comment on lines +68 to +74
// 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
: "",
);
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

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.

Suggested change
// 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);
}

Comment on lines +31 to +37
<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>
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

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.

Suggested change
<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>

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

♻️ 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 issue

Secure your component against XSS attacks.

The highlighted code is directly inserted using dangerouslySetInnerHTML without 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

📥 Commits

Reviewing files that changed from the base of the PR and between acaac74 and 8be5e39.

📒 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

Comment on lines 111 to 112
<strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use. After a successful connection,
you&apos;ll need to generate a new token.
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

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&apos;ll need to generate a new token.
+            <strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use. 
+            After a successful connection, you&apos;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.

Suggested change
<strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use. After a successful connection,
you&apos;ll need to generate a new token.
<strong className={styles.text.strongMuted}>Note:</strong> Connect tokens are single-use.
After a successful connection, you&apos;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)

Comment on lines +64 to +68
const copyToClipboard = () => {
navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
Copy link
Contributor

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.

Suggested change
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()
@dannyroosevelt dannyroosevelt merged commit 257d716 into master Apr 24, 2025
9 checks passed
@dannyroosevelt dannyroosevelt deleted the danny/add-connect-sdk-to-docs branch April 24, 2025 17:33
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.

3 participants