From b51fc2dc32b34dddfab1607c39a4cd0327d6a87c Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Tue, 10 Jun 2025 23:40:17 -0700 Subject: [PATCH 1/4] Adding externalUserId to connect-react userId should be deprecated --- packages/connect-react/CHANGELOG.md | 24 ++++++++++++++ packages/connect-react/package.json | 2 +- .../src/components/ComponentForm.tsx | 14 ++++++-- .../src/components/ControlApp.tsx | 3 ++ .../src/components/RemoteOptionsContainer.tsx | 4 +-- .../connect-react/src/hooks/form-context.tsx | 33 +++++++++++++++---- .../src/utils/resolve-user-id.ts | 21 ++++++++++++ pnpm-lock.yaml | 13 ++++++-- 8 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 packages/connect-react/src/utils/resolve-user-id.ts diff --git a/packages/connect-react/CHANGELOG.md b/packages/connect-react/CHANGELOG.md index 91eb9e0021eed..f20ecc8cb064a 100644 --- a/packages/connect-react/CHANGELOG.md +++ b/packages/connect-react/CHANGELOG.md @@ -2,6 +2,30 @@ # Changelog +# [1.3.0] - 2025-06-10 + +## Added +- Support for `externalUserId` parameter across all components as the preferred way to identify users +- Backward compatibility with existing `userId` parameter +- Proper user identification in SDK debugger for `configureComponent`, `reloadComponentProps`, and `getAccounts` calls + +## Changed +- All internal SDK calls now use `externalUserId` parameter for consistency with backend SDK +- Account loading now properly includes user identification from form context + +## Deprecated +- `userId` parameter in favor of `externalUserId` (existing code continues to work with console warnings) + +## Migration +Replace `userId` with `externalUserId` in component props: +```typescript +// Before + + +// After (recommended) + +``` + # [1.2.1] - 2025-06-07 - Fixing the SelectApp component to properly handle controlled values when not found in search results diff --git a/packages/connect-react/package.json b/packages/connect-react/package.json index 84c28e1962c22..412efcbabae97 100644 --- a/packages/connect-react/package.json +++ b/packages/connect-react/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/connect-react", - "version": "1.2.1", + "version": "1.3.0", "description": "Pipedream Connect library for React", "files": [ "dist" diff --git a/packages/connect-react/src/components/ComponentForm.tsx b/packages/connect-react/src/components/ComponentForm.tsx index 47c19f9cac8f2..2ebe0b2dbaabf 100644 --- a/packages/connect-react/src/components/ComponentForm.tsx +++ b/packages/connect-react/src/components/ComponentForm.tsx @@ -10,7 +10,14 @@ import type { import { InternalComponentForm } from "./InternalComponentForm"; export type ComponentFormProps> = { - userId: string; + /** + * Your end user ID, for whom you're configuring the component. + */ + externalUserId?: string; + /** + * @deprecated Use `externalUserId` instead. + */ + userId?: string; component: V1Component; configuredProps?: U; // XXX value? disableQueryDisabling?: boolean; @@ -22,7 +29,10 @@ export type ComponentFormProps(props: ComponentFormProps) { return ( diff --git a/packages/connect-react/src/components/ControlApp.tsx b/packages/connect-react/src/components/ControlApp.tsx index cfdf0a063207e..0535ce881c00e 100644 --- a/packages/connect-react/src/components/ControlApp.tsx +++ b/packages/connect-react/src/components/ControlApp.tsx @@ -2,6 +2,7 @@ import Select, { components as ReactSelectComponents } from "react-select"; import { useFrontendClient } from "../hooks/frontend-client-context"; import { useAccounts } from "../hooks/use-accounts"; import { useFormFieldContext } from "../hooks/form-field-context"; +import { useFormContext } from "../hooks/form-context"; import { useCustomize } from "../hooks/customization-context"; import type { BaseReactSelectProps } from "../hooks/customization-context"; import { useMemo } from "react"; @@ -28,6 +29,7 @@ type ControlAppProps = { export function ControlApp({ app }: ControlAppProps) { const client = useFrontendClient(); + const { externalUserId } = useFormContext(); const formFieldCtx = useFormFieldContext(); const { id, prop, value, onChange, @@ -73,6 +75,7 @@ export function ControlApp({ app }: ControlAppProps) { refetch: refetchAccounts, } = useAccounts( { + externalUserId, app: app.name_slug, oauth_app_id: oauthAppId, }, diff --git a/packages/connect-react/src/components/RemoteOptionsContainer.tsx b/packages/connect-react/src/components/RemoteOptionsContainer.tsx index d606bee52f6c0..5dd526f79265b 100644 --- a/packages/connect-react/src/components/RemoteOptionsContainer.tsx +++ b/packages/connect-react/src/components/RemoteOptionsContainer.tsx @@ -13,7 +13,7 @@ export type RemoteOptionsContainerProps = { export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerProps) { const client = useFrontendClient(); const { - userId, + externalUserId, component, configurableProps, configuredProps, @@ -60,7 +60,7 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP configuredPropsUpTo[prop.name] = configuredProps[prop.name]; } const componentConfigureInput: ComponentConfigureOpts = { - userId, + externalUserId, page, prevContext: context, componentId: component.key, diff --git a/packages/connect-react/src/hooks/form-context.tsx b/packages/connect-react/src/hooks/form-context.tsx index e78ba540919a3..f6e0fa6adbeb1 100644 --- a/packages/connect-react/src/hooks/form-context.tsx +++ b/packages/connect-react/src/hooks/form-context.tsx @@ -16,6 +16,7 @@ import { import { Observation, SdkError, } from "../types"; +import { resolveUserId } from "../utils/resolve-user-id"; export type DynamicProps = { id: string; configurableProps: T; }; // TODO @@ -39,6 +40,8 @@ export type FormContext = { setConfiguredProp: (idx: number, value: unknown) => void; // XXX type safety for value (T will rarely be static right?) setSubmitting: (submitting: boolean) => void; submitting: boolean; + externalUserId: string; + /** @deprecated Use externalUserId instead */ userId: string; enableDebugging?: boolean; }; @@ -77,8 +80,23 @@ export const FormContextProvider = ({ const id = useId(); const { - component, configuredProps: __configuredProps, propNames, userId, sdkResponse, enableDebugging, + component, configuredProps: __configuredProps, propNames, externalUserId, userId, sdkResponse, enableDebugging, } = formProps; + + // Resolve user ID with deprecation warning + const { resolvedId: resolvedExternalUserId, warningType } = useMemo(() => + resolveUserId(externalUserId, userId), + [externalUserId, userId] + ); + + // Show deprecation warnings in useEffect to avoid render side effects + useEffect(() => { + if (warningType === 'both') { + console.warn('[connect-react] Both externalUserId and userId provided. Using externalUserId. Please remove userId to avoid this warning.'); + } else if (warningType === 'deprecated') { + console.warn('[connect-react] userId is deprecated. Please use externalUserId instead.'); + } + }, [warningType]); const componentId = component.key; const [ @@ -134,7 +152,7 @@ export const FormContextProvider = ({ setReloadPropIdx, ] = useState(); const componentReloadPropsInput: ReloadComponentPropsOpts = { - userId, + externalUserId: resolvedExternalUserId, componentId, configuredProps, dynamicPropsId: dynamicProps?.id, @@ -349,14 +367,14 @@ export const FormContextProvider = ({ const [ prevUserId, setPrevUserId, - ] = useState(userId) + ] = useState(resolvedExternalUserId) useEffect(() => { - if (prevUserId !== userId) { + if (prevUserId !== resolvedExternalUserId) { updateConfiguredProps({}); - setPrevUserId(userId) + setPrevUserId(resolvedExternalUserId) } }, [ - userId, + resolvedExternalUserId, ]); // maybe should take prop as first arg but for text inputs didn't want to compute index each time @@ -550,7 +568,8 @@ export const FormContextProvider = ({ id, isValid: !Object.keys(errors).length, // XXX want to expose more from errors props: formProps, - userId, + externalUserId: resolvedExternalUserId, + userId: resolvedExternalUserId, // Keep for backward compatibility component, configurableProps, configuredProps, diff --git a/packages/connect-react/src/utils/resolve-user-id.ts b/packages/connect-react/src/utils/resolve-user-id.ts new file mode 100644 index 0000000000000..e03fe274bbd85 --- /dev/null +++ b/packages/connect-react/src/utils/resolve-user-id.ts @@ -0,0 +1,21 @@ +/** + * Resolves user ID from either externalUserId or userId parameters + * Prefers externalUserId and returns both the resolved value and warning info + */ +export const resolveUserId = ( + externalUserId?: string, + userId?: string +): { resolvedId: string; warningType?: 'both' | 'deprecated' } => { + if (externalUserId) { + if (userId) { + return { resolvedId: externalUserId, warningType: 'both' }; + } + return { resolvedId: externalUserId }; + } + + if (userId) { + return { resolvedId: userId, warningType: 'deprecated' }; + } + + throw new Error('Either externalUserId or userId must be provided'); +}; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a0e6f69d632c..3e858c4e9b933 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -751,8 +751,7 @@ importers: components/amazon_polly: {} - components/amazon_redshift: - specifiers: {} + components/amazon_redshift: {} components/amazon_selling_partner: {} @@ -15670,6 +15669,14 @@ importers: specifier: ^6.0.0 version: 6.2.0 + modelcontextprotocol/node_modules2/@modelcontextprotocol/sdk/dist/cjs: {} + + modelcontextprotocol/node_modules2/@modelcontextprotocol/sdk/dist/esm: {} + + modelcontextprotocol/node_modules2/zod-to-json-schema/dist/cjs: {} + + modelcontextprotocol/node_modules2/zod-to-json-schema/dist/esm: {} + packages/ai: dependencies: '@pipedream/sdk': @@ -36089,6 +36096,8 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: From 966f81edbd013f91179ec3dc1f38f9eb315ddaa5 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Tue, 10 Jun 2025 23:48:49 -0700 Subject: [PATCH 2/4] linting fixes --- .../connect-react/src/hooks/form-context.tsx | 26 ++++++++++------- .../src/utils/resolve-user-id.ts | 28 ++++++++++++------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/connect-react/src/hooks/form-context.tsx b/packages/connect-react/src/hooks/form-context.tsx index f6e0fa6adbeb1..d4d8bce555a60 100644 --- a/packages/connect-react/src/hooks/form-context.tsx +++ b/packages/connect-react/src/hooks/form-context.tsx @@ -82,21 +82,27 @@ export const FormContextProvider = ({ const { component, configuredProps: __configuredProps, propNames, externalUserId, userId, sdkResponse, enableDebugging, } = formProps; - + // Resolve user ID with deprecation warning - const { resolvedId: resolvedExternalUserId, warningType } = useMemo(() => - resolveUserId(externalUserId, userId), - [externalUserId, userId] - ); + const { + resolvedId: resolvedExternalUserId, warningType, + } = useMemo(() => + resolveUserId(externalUserId, userId), + [ + externalUserId, + userId, + ]); // Show deprecation warnings in useEffect to avoid render side effects useEffect(() => { - if (warningType === 'both') { - console.warn('[connect-react] Both externalUserId and userId provided. Using externalUserId. Please remove userId to avoid this warning.'); - } else if (warningType === 'deprecated') { - console.warn('[connect-react] userId is deprecated. Please use externalUserId instead.'); + if (warningType === "both") { + console.warn("[connect-react] Both externalUserId and userId provided. Using externalUserId. Please remove userId to avoid this warning."); + } else if (warningType === "deprecated") { + console.warn("[connect-react] userId is deprecated. Please use externalUserId instead."); } - }, [warningType]); + }, [ + warningType, + ]); const componentId = component.key; const [ diff --git a/packages/connect-react/src/utils/resolve-user-id.ts b/packages/connect-react/src/utils/resolve-user-id.ts index e03fe274bbd85..0b1aea4ed8ce5 100644 --- a/packages/connect-react/src/utils/resolve-user-id.ts +++ b/packages/connect-react/src/utils/resolve-user-id.ts @@ -3,19 +3,27 @@ * Prefers externalUserId and returns both the resolved value and warning info */ export const resolveUserId = ( - externalUserId?: string, - userId?: string -): { resolvedId: string; warningType?: 'both' | 'deprecated' } => { + externalUserId?: string, + userId?: string, +): { resolvedId: string; warningType?: "both" | "deprecated" } => { if (externalUserId) { if (userId) { - return { resolvedId: externalUserId, warningType: 'both' }; + return { + resolvedId: externalUserId, + warningType: "both", + }; } - return { resolvedId: externalUserId }; + return { + resolvedId: externalUserId, + }; } - + if (userId) { - return { resolvedId: userId, warningType: 'deprecated' }; + return { + resolvedId: userId, + warningType: "deprecated", + }; } - - throw new Error('Either externalUserId or userId must be provided'); -}; \ No newline at end of file + + throw new Error("Either externalUserId or userId must be provided"); +}; From e69ed69a1a686c306c4213aa4f54653816bf5a1f Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Tue, 10 Jun 2025 23:58:58 -0700 Subject: [PATCH 3/4] PR feedback --- packages/connect-react/src/hooks/form-context.tsx | 14 +++++++------- .../connect-react/src/utils/resolve-user-id.ts | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/connect-react/src/hooks/form-context.tsx b/packages/connect-react/src/hooks/form-context.tsx index d4d8bce555a60..d516615bda0aa 100644 --- a/packages/connect-react/src/hooks/form-context.tsx +++ b/packages/connect-react/src/hooks/form-context.tsx @@ -86,19 +86,19 @@ export const FormContextProvider = ({ // Resolve user ID with deprecation warning const { resolvedId: resolvedExternalUserId, warningType, - } = useMemo(() => - resolveUserId(externalUserId, userId), - [ + } = useMemo(() => resolveUserId(externalUserId, userId), [ externalUserId, userId, ]); // Show deprecation warnings in useEffect to avoid render side effects useEffect(() => { - if (warningType === "both") { - console.warn("[connect-react] Both externalUserId and userId provided. Using externalUserId. Please remove userId to avoid this warning."); - } else if (warningType === "deprecated") { - console.warn("[connect-react] userId is deprecated. Please use externalUserId instead."); + if (process.env.NODE_ENV !== "production") { + if (warningType === "both") { + console.warn("[connect-react] Both externalUserId and userId provided. Using externalUserId. Please remove userId to avoid this warning."); + } else if (warningType === "deprecated") { + console.warn("[connect-react] userId is deprecated. Please use externalUserId instead."); + } } }, [ warningType, diff --git a/packages/connect-react/src/utils/resolve-user-id.ts b/packages/connect-react/src/utils/resolve-user-id.ts index 0b1aea4ed8ce5..ee269ba96a168 100644 --- a/packages/connect-react/src/utils/resolve-user-id.ts +++ b/packages/connect-react/src/utils/resolve-user-id.ts @@ -6,8 +6,8 @@ export const resolveUserId = ( externalUserId?: string, userId?: string, ): { resolvedId: string; warningType?: "both" | "deprecated" } => { - if (externalUserId) { - if (userId) { + if (externalUserId !== undefined && externalUserId !== "") { + if (userId !== undefined && userId !== "") { return { resolvedId: externalUserId, warningType: "both", @@ -18,7 +18,7 @@ export const resolveUserId = ( }; } - if (userId) { + if (userId !== undefined && userId !== "") { return { resolvedId: userId, warningType: "deprecated", From 3228bb3a36619679ebf2319398eaf3b6b8b02029 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Wed, 11 Jun 2025 14:31:53 -0700 Subject: [PATCH 4/4] Update CHANGELOG.md --- packages/connect-react/CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/connect-react/CHANGELOG.md b/packages/connect-react/CHANGELOG.md index f20ecc8cb064a..354ab2afcd45b 100644 --- a/packages/connect-react/CHANGELOG.md +++ b/packages/connect-react/CHANGELOG.md @@ -5,19 +5,22 @@ # [1.3.0] - 2025-06-10 ## Added -- Support for `externalUserId` parameter across all components as the preferred way to identify users + +- Support for `externalUserId` param across all components as the preferred way to identify users - Backward compatibility with existing `userId` parameter -- Proper user identification in SDK debugger for `configureComponent`, `reloadComponentProps`, and `getAccounts` calls ## Changed + - All internal SDK calls now use `externalUserId` parameter for consistency with backend SDK -- Account loading now properly includes user identification from form context ## Deprecated + - `userId` parameter in favor of `externalUserId` (existing code continues to work with console warnings) ## Migration + Replace `userId` with `externalUserId` in component props: + ```typescript // Before