From 5671929d9ad30f7ee90026075eeda70597d801da Mon Sep 17 00:00:00 2001 From: William Wong Date: Wed, 16 Jul 2025 19:05:30 +0000 Subject: [PATCH 01/16] Initial commit of polyMiddleware --- package-lock.json | 76 +++++++++ package.json | 2 + ...teMiddleware.ts => templateMiddleware.tsx} | 22 ++- packages/component/src/Composer.tsx | 49 ++++-- .../private/LegacyActivityBridge.tsx} | 50 +++--- .../ui/RenderActivityGrouping.tsx | 37 +++-- .../RenderingActivitiesComposer.tsx | 44 ++--- .../private/RenderingActivitiesContext.ts | 4 +- .../useInternalActivitiesWithRenderer.ts | 78 --------- .../useActivityRendererMap.ts | 9 ++ .../useGetRenderActivityCallback.ts | 11 -- packages/middleware/.eslintrc.yml | 10 ++ packages/middleware/.gitignore | 4 + packages/middleware/README.md | 0 packages/middleware/package.json | 72 +++++++++ .../middleware/src/PolyMiddlewareComposer.tsx | 43 +++++ .../middleware/src/activityPolyMiddleware.tsx | 66 ++++++++ packages/middleware/src/index.ts | 27 ++++ ...createActivityPolyMiddlewareFromLegacy.tsx | 83 ++++++++++ .../private/templateMiddleware.check.test.tsx | 47 ++++++ .../src/private/templateMiddleware.test.tsx | 98 +++++++++++ .../src/private/templateMiddleware.tsx | 153 ++++++++++++++++++ packages/middleware/src/tsconfig.json | 11 ++ .../middleware/src/types/GenericMiddleware.ts | 12 ++ .../src/types/LegacyComponentMiddleware.ts | 13 ++ .../middleware/src/types/PolyMiddleware.ts | 10 ++ packages/middleware/tsup.config.ts | 21 +++ 27 files changed, 886 insertions(+), 166 deletions(-) rename packages/api/src/middleware/private/{templateMiddleware.ts => templateMiddleware.tsx} (83%) rename packages/component/src/{Transcript/TranscriptActivity.tsx => Middleware/Activity/private/LegacyActivityBridge.tsx} (68%) delete mode 100644 packages/component/src/providers/RenderingActivities/private/useInternalActivitiesWithRenderer.ts create mode 100644 packages/component/src/providers/RenderingActivities/useActivityRendererMap.ts delete mode 100644 packages/component/src/providers/RenderingActivities/useGetRenderActivityCallback.ts create mode 100644 packages/middleware/.eslintrc.yml create mode 100644 packages/middleware/.gitignore create mode 100644 packages/middleware/README.md create mode 100644 packages/middleware/package.json create mode 100644 packages/middleware/src/PolyMiddlewareComposer.tsx create mode 100644 packages/middleware/src/activityPolyMiddleware.tsx create mode 100644 packages/middleware/src/index.ts create mode 100644 packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx create mode 100644 packages/middleware/src/private/templateMiddleware.check.test.tsx create mode 100644 packages/middleware/src/private/templateMiddleware.test.tsx create mode 100644 packages/middleware/src/private/templateMiddleware.tsx create mode 100644 packages/middleware/src/tsconfig.json create mode 100644 packages/middleware/src/types/GenericMiddleware.ts create mode 100644 packages/middleware/src/types/LegacyComponentMiddleware.ts create mode 100644 packages/middleware/src/types/PolyMiddleware.ts create mode 100644 packages/middleware/tsup.config.ts diff --git a/package-lock.json b/package-lock.json index 340db42b43..42774f1641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "./packages/test/harness", "./packages/test/web-server", "./packages/core", + "./packages/middleware", "./packages/react-valibot", "./packages/redux-store", "./packages/styles", @@ -16174,6 +16175,19 @@ "react": ">=16.8.0" } }, + "node_modules/react-chain-of-responsibility/node_modules/valibot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", + "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/react-dictate-button": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/react-dictate-button/-/react-dictate-button-4.0.0.tgz", @@ -21839,6 +21853,68 @@ "webpack-cli": "^6.0.1" } }, + "packages/middleware": { + "name": "botframework-webchat-middleware", + "version": "0.0.0-0", + "license": "MIT", + "dependencies": { + "react-chain-of-responsibility": "0.4.0-main.235b355", + "valibot": "1.1.0" + }, + "devDependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@tsconfig/strictest": "^2.0.5", + "@types/node": "^22.13.4", + "botframework-webchat-base": "0.0.0-0", + "botframework-webchat-react-valibot": "^0.0.0-0", + "cross-env": "^7.0.3", + "type-fest": "^4.34.1", + "typescript": "^5.7.3" + }, + "peerDependencies": { + "react": ">= 16.8.6" + } + }, + "packages/middleware/node_modules/@types/node": { + "version": "22.16.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.4.tgz", + "integrity": "sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "packages/middleware/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/middleware/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "packages/middleware/node_modules/valibot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", + "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "packages/react-types": { "name": "@msinternal/botframework-webchat-react-types", "version": "0.0.0-0", diff --git a/package.json b/package.json index 63507046d7..962b36629a 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "./packages/test/web-server", "./packages/core", "./packages/react-valibot", + "./packages/middleware", "./packages/redux-store", "./packages/styles", "./packages/support/cldr-data-downloader", @@ -86,6 +87,7 @@ "start:core": "cd packages && cd core && npm start", "start:directlinespeech": "cd packages && cd directlinespeech && npm start", "start:fluent-theme": "cd packages && cd fluent-theme && npm start", + "start:middleware": "cd packages && cd middleware && npm start", "start:react-valibot": "cd packages && cd react-valibot && npm start", "start:redux-store": "cd packages && cd redux-store && npm start", "start:server": "serve -p 5000", diff --git a/packages/api/src/middleware/private/templateMiddleware.ts b/packages/api/src/middleware/private/templateMiddleware.tsx similarity index 83% rename from packages/api/src/middleware/private/templateMiddleware.ts rename to packages/api/src/middleware/private/templateMiddleware.tsx index 1bc97235ea..426e4be5ed 100644 --- a/packages/api/src/middleware/private/templateMiddleware.ts +++ b/packages/api/src/middleware/private/templateMiddleware.tsx @@ -1,4 +1,5 @@ import { warnOnce } from 'botframework-webchat-core'; +import React, { memo, type ReactNode } from 'react'; import { createChainOfResponsibility, type ComponentMiddleware } from 'react-chain-of-responsibility'; import { array, function_, safeParse, type InferOutput } from 'valibot'; @@ -58,15 +59,30 @@ function templateMiddleware(name: string) { return EMPTY_ARRAY; }; - const { Provider, Proxy } = createChainOfResponsibility(); + const { Provider, Proxy } = createChainOfResponsibility(); + + type TemplatedProviderProps = { + readonly children?: ReactNode | undefined; + readonly middleware: readonly Middleware[]; + }; + + // eslint-disable-next-line prefer-arrow-callback + const TemplatedProvider = memo(function TemplatedProvider({ children, middleware }: TemplatedProviderProps) { + return ( + + {children} + + ); + }); + + TemplatedProvider.displayName = `${name}Provider`; - Provider.displayName = `${name}Provider`; Proxy.displayName = `${name}Proxy`; return { createMiddleware, extractMiddleware, - Provider, + Provider: TemplatedProvider, Proxy, '~types': undefined as { middleware: Middleware; diff --git a/packages/component/src/Composer.tsx b/packages/component/src/Composer.tsx index 67ef2973e6..3326eb233c 100644 --- a/packages/component/src/Composer.tsx +++ b/packages/component/src/Composer.tsx @@ -12,6 +12,11 @@ import { } from 'botframework-webchat-api'; import { DecoratorComposer, type DecoratorMiddleware } from 'botframework-webchat-api/decorator'; import { singleToArray } from 'botframework-webchat-core'; +import { + createActivityPolyMiddlewareFromLegacy, + PolyMiddlewareComposer, + type PolyMiddleware +} from 'botframework-webchat-middleware'; import classNames from 'classnames'; import MarkdownIt from 'markdown-it'; import PropTypes from 'prop-types'; @@ -33,6 +38,7 @@ import WebChatUIContext from './hooks/internal/WebChatUIContext'; import { FocusSendBoxScope } from './hooks/sendBoxFocus'; import { ScrollRelativeTranscriptScope } from './hooks/transcriptScrollRelative'; import createDefaultActivityMiddleware from './Middleware/Activity/createCoreMiddleware'; +import LegacyActivityBridge from './Middleware/Activity/private/LegacyActivityBridge'; import createDefaultActivityStatusMiddleware from './Middleware/ActivityStatus/createCoreMiddleware'; import createDefaultAttachmentForScreenReaderMiddleware from './Middleware/AttachmentForScreenReader/createCoreMiddleware'; import createDefaultAvatarMiddleware from './Middleware/Avatar/createCoreMiddleware'; @@ -441,6 +447,15 @@ const InternalComposer = ({ [sendBoxToolbarMiddlewareFromProps, theme.sendBoxToolbarMiddleware] ); + const polyMiddlewareArray = useMemo( + () => + Object.freeze([ + // TODO: Add . + createActivityPolyMiddlewareFromLegacy(LegacyActivityBridge, () => undefined, ...patchedActivityMiddleware) + ]), + [patchedActivityMiddleware] + ); + return ( - - - - {children} - {onTelemetry && } - - - + + + + + {children} + {onTelemetry && } + + + + diff --git a/packages/component/src/Transcript/TranscriptActivity.tsx b/packages/component/src/Middleware/Activity/private/LegacyActivityBridge.tsx similarity index 68% rename from packages/component/src/Transcript/TranscriptActivity.tsx rename to packages/component/src/Middleware/Activity/private/LegacyActivityBridge.tsx index fec94f1286..d58dd08ed1 100644 --- a/packages/component/src/Transcript/TranscriptActivity.tsx +++ b/packages/component/src/Middleware/Activity/private/LegacyActivityBridge.tsx @@ -1,23 +1,27 @@ -import { hooks, type ActivityComponentFactory } from 'botframework-webchat-api'; -import { type WebChatActivity } from 'botframework-webchat-core'; +import { hooks } from 'botframework-webchat-api'; +import { bridgeComponentPropsSchema, type BridgeComponentProps } from 'botframework-webchat-middleware'; +import { validateProps } from 'botframework-webchat-react-valibot'; import React, { memo, useCallback, useMemo } from 'react'; -import useFirstActivityInSenderGroup from '../Middleware/ActivityGrouping/ui/SenderGrouping/useFirstActivity'; -import useLastActivityInSenderGroup from '../Middleware/ActivityGrouping/ui/SenderGrouping/useLastActivity'; -import useFirstActivityInStatusGroup from '../Middleware/ActivityGrouping/ui/StatusGrouping/useFirstActivity'; -import useLastActivityInStatusGroup from '../Middleware/ActivityGrouping/ui/StatusGrouping/useLastActivity'; -import useActivityElementMapRef from '../providers/ChatHistoryDOM/useActivityElementRef'; -import isZeroOrPositive from '../Utils/isZeroOrPositive'; -import ActivityRow from './ActivityRow'; +import useActivityElementMapRef from '../../../providers/ChatHistoryDOM/useActivityElementRef'; +import ActivityRow from '../../../Transcript/ActivityRow'; +import isZeroOrPositive from '../../../Utils/isZeroOrPositive'; +import useFirstActivityInSenderGroup from '../../ActivityGrouping/ui/SenderGrouping/useFirstActivity'; +import useLastActivityInSenderGroup from '../../ActivityGrouping/ui/SenderGrouping/useLastActivity'; +import useFirstActivityInStatusGroup from '../../ActivityGrouping/ui/StatusGrouping/useFirstActivity'; +import useLastActivityInStatusGroup from '../../ActivityGrouping/ui/StatusGrouping/useLastActivity'; -const { useCreateActivityStatusRenderer, useCreateAvatarRenderer, useGetKeyByActivity, useStyleOptions } = hooks; +const { + useCreateActivityStatusRenderer, + useCreateAvatarRenderer, + useGetKeyByActivity, + useRenderAttachment, + useStyleOptions +} = hooks; -type TranscriptActivityProps = Readonly<{ - activity: WebChatActivity; - renderActivity: Exclude, false>; -}>; +function LegacyActivityBridge(props: BridgeComponentProps) { + const { activity, render } = validateProps(bridgeComponentPropsSchema, props); -const TranscriptActivity = ({ activity, renderActivity }: TranscriptActivityProps) => { const [{ bubbleFromUserNubOffset, bubbleNubOffset, groupTimestamp, showAvatarInGroup }] = useStyleOptions(); const [firstActivityInSenderGroup] = useFirstActivityInSenderGroup(); const [firstActivityInStatusGroup] = useFirstActivityInStatusGroup(); @@ -26,9 +30,9 @@ const TranscriptActivity = ({ activity, renderActivity }: TranscriptActivityProp const activityElementMapRef = useActivityElementMapRef(); const createActivityStatusRenderer = useCreateActivityStatusRenderer(); const getKeyByActivity = useGetKeyByActivity(); + const renderAttachment = useRenderAttachment(); const renderAvatar = useCreateAvatarRenderer(); - const activityKey: string = useMemo(() => getKeyByActivity(activity), [activity, getKeyByActivity]); const hideAllTimestamps = groupTimestamp === false; const isFirstInSenderGroup = firstActivityInSenderGroup === activity || typeof firstActivityInSenderGroup === 'undefined'; @@ -53,6 +57,7 @@ const TranscriptActivity = ({ activity, renderActivity }: TranscriptActivityProp [activity, createActivityStatusRenderer] ); + const activityKey: string = useMemo(() => getKeyByActivity(activity), [activity, getKeyByActivity]); const activityCallbackRef = useCallback( (activityElement: HTMLElement) => { activityElement @@ -83,23 +88,22 @@ const TranscriptActivity = ({ activity, renderActivity }: TranscriptActivityProp showCallout = true; } - const children = useMemo( + const node = useMemo( () => - renderActivity({ + render(renderAttachment, { hideTimestamp, renderActivityStatus, renderAvatar: renderAvatarForSenderGroup, showCallout }), - [hideTimestamp, renderActivity, renderActivityStatus, renderAvatarForSenderGroup, showCallout] + [render, hideTimestamp, renderActivityStatus, renderAttachment, renderAvatarForSenderGroup, showCallout] ); return ( - {children} + {node} ); -}; +} -export default memo(TranscriptActivity); -export { type TranscriptActivityProps }; +export default memo(LegacyActivityBridge); diff --git a/packages/component/src/Middleware/ActivityGrouping/ui/RenderActivityGrouping.tsx b/packages/component/src/Middleware/ActivityGrouping/ui/RenderActivityGrouping.tsx index 26337165c6..abdfd804fb 100644 --- a/packages/component/src/Middleware/ActivityGrouping/ui/RenderActivityGrouping.tsx +++ b/packages/component/src/Middleware/ActivityGrouping/ui/RenderActivityGrouping.tsx @@ -1,28 +1,35 @@ import { hooks } from 'botframework-webchat-api'; import { type WebChatActivity } from 'botframework-webchat-core'; +import { validateProps } from 'botframework-webchat-react-valibot'; import React, { Fragment, memo } from 'react'; -import useGetRenderActivityCallback from '../../../providers/RenderingActivities/useGetRenderActivityCallback'; -import TranscriptActivity from '../../../Transcript/TranscriptActivity'; +import { array, custom, object, pipe, readonly, safeParse, type InferInput } from 'valibot'; + +import useActivityRendererMap from '../../../providers/RenderingActivities/useActivityRendererMap'; const { useGetKeyByActivity } = hooks; -type RenderActivityGroupingProps = Readonly<{ - activities: readonly WebChatActivity[]; -}>; +const renderActivityGroupingPropsSchema = pipe( + object({ + activities: pipe(array(custom(value => safeParse(object({}), value).success)), readonly()) + }), + readonly() +); + +type RenderActivityGroupingProps = Readonly>; -const RenderActivityGrouping = ({ activities }: RenderActivityGroupingProps) => { +const RenderActivityGrouping = (props: RenderActivityGroupingProps) => { + const { activities } = validateProps(renderActivityGroupingPropsSchema, props); + + const [activityRendererMap] = useActivityRendererMap(); const getKeyByActivity = useGetKeyByActivity(); - const getRenderActivityCallback = useGetRenderActivityCallback(); return ( - {activities.map(activity => ( - - ))} + {activities.map(activity => { + const children = activityRendererMap.get(activity)?.({}); + + return children && {children}; + })} ); }; @@ -30,4 +37,4 @@ const RenderActivityGrouping = ({ activities }: RenderActivityGroupingProps) => RenderActivityGrouping.displayName = 'RenderActivityGrouping'; export default memo(RenderActivityGrouping); -export { type RenderActivityGroupingProps }; +export { renderActivityGroupingPropsSchema, type RenderActivityGroupingProps }; diff --git a/packages/component/src/providers/RenderingActivities/RenderingActivitiesComposer.tsx b/packages/component/src/providers/RenderingActivities/RenderingActivitiesComposer.tsx index 4f0f077fd6..3e8174c791 100644 --- a/packages/component/src/providers/RenderingActivities/RenderingActivitiesComposer.tsx +++ b/packages/component/src/providers/RenderingActivities/RenderingActivitiesComposer.tsx @@ -1,20 +1,19 @@ -import { hooks, type ActivityComponentFactory } from 'botframework-webchat-api'; +import { hooks } from 'botframework-webchat-api'; import { type WebChatActivity } from 'botframework-webchat-core'; +import { useBuildRenderActivityCallback, type ActivityPolyMiddlewareRenderer } from 'botframework-webchat-middleware'; import React, { memo, useMemo, type ReactNode } from 'react'; import RenderingActivitiesContext, { type RenderingActivitiesContextType } from './private/RenderingActivitiesContext'; -import useInternalActivitiesWithRenderer from './private/useInternalActivitiesWithRenderer'; type RenderingActivitiesComposerProps = Readonly<{ children?: ReactNode | undefined; }>; -const { useActivities, useActivityKeys, useCreateActivityRenderer, useGetActivitiesByKey, useGetKeyByActivity } = hooks; +const { useActivities, useActivityKeys, useGetActivitiesByKey, useGetKeyByActivity } = hooks; const RenderingActivitiesComposer = ({ children }: RenderingActivitiesComposerProps) => { const [activities] = useActivities(); const activityKeys = useActivityKeys(); - const createActivityRenderer = useCreateActivityRenderer(); const getActivitiesByKey = useGetActivitiesByKey(); const getKeyByActivity = useGetKeyByActivity(); @@ -41,38 +40,47 @@ const RenderingActivitiesComposer = ({ children }: RenderingActivitiesComposerPr return Object.freeze(activitiesOfLatestRevision); }, [activityKeys, getActivitiesByKey, getKeyByActivity, activities]); - const activitiesWithRenderer = useInternalActivitiesWithRenderer(activitiesOfLatestRevision, createActivityRenderer); + const renderActivity = useBuildRenderActivityCallback(); - const renderingActivitiesState = useMemo( - () => Object.freeze([activitiesWithRenderer.map(({ activity }) => activity)] as const), - [activitiesWithRenderer] + const activityRendererMap = useMemo>( + () => + Object.freeze( + new Map( + activitiesOfLatestRevision + .map(activity => [activity, renderActivity({ activity })] as const) + .filter((tuple): tuple is [WebChatActivity, ActivityPolyMiddlewareRenderer] => !!tuple[1]) + ) + ), + [activitiesOfLatestRevision, renderActivity] + ); + + const renderingActivitiesState = useMemo( + () => Object.freeze([Object.freeze(Array.from(activityRendererMap.keys()))]), + [activityRendererMap] ); const renderingActivityKeysState = useMemo(() => { const keys = Object.freeze(renderingActivitiesState[0].map(activity => getKeyByActivity(activity))); if (keys.some(key => !key)) { - throw new Error('botframework-webchat internal: activitiesWithRenderer[].activity must have activity key'); + throw new Error('botframework-webchat internal: activityRendererMap[].activity must have activity key'); } return Object.freeze([keys] as const); - }, [renderingActivitiesState, getKeyByActivity]); + }, [getKeyByActivity, renderingActivitiesState]); - const renderActivityCallbackMap = useMemo< - ReadonlyMap, false>> - >( - () => - Object.freeze(new Map(activitiesWithRenderer.map(({ activity, renderActivity }) => [activity, renderActivity]))), - [activitiesWithRenderer] + const activityRendererMapState = useMemo]>( + () => Object.freeze([activityRendererMap]), + [activityRendererMap] ); const contextValue: RenderingActivitiesContextType = useMemo( () => ({ - renderActivityCallbackMap, + activityRendererMapState, renderingActivitiesState, renderingActivityKeysState }), - [renderActivityCallbackMap, renderingActivitiesState, renderingActivityKeysState] + [activityRendererMapState, renderingActivitiesState, renderingActivityKeysState] ); return {children}; diff --git a/packages/component/src/providers/RenderingActivities/private/RenderingActivitiesContext.ts b/packages/component/src/providers/RenderingActivities/private/RenderingActivitiesContext.ts index 86371ab958..c851853d90 100644 --- a/packages/component/src/providers/RenderingActivities/private/RenderingActivitiesContext.ts +++ b/packages/component/src/providers/RenderingActivities/private/RenderingActivitiesContext.ts @@ -1,9 +1,9 @@ -import { type ActivityComponentFactory } from 'botframework-webchat-api'; import { type WebChatActivity } from 'botframework-webchat-core'; +import { type ActivityPolyMiddlewareRenderer } from 'botframework-webchat-middleware'; import createContextAndHook from '../../createContextAndHook'; type RenderingActivitiesContextType = Readonly<{ - renderActivityCallbackMap: ReadonlyMap, false>>; + activityRendererMapState: readonly [ReadonlyMap]; renderingActivitiesState: readonly [readonly WebChatActivity[]]; renderingActivityKeysState: readonly [readonly string[]]; }>; diff --git a/packages/component/src/providers/RenderingActivities/private/useInternalActivitiesWithRenderer.ts b/packages/component/src/providers/RenderingActivities/private/useInternalActivitiesWithRenderer.ts deleted file mode 100644 index 069d398eb4..0000000000 --- a/packages/component/src/providers/RenderingActivities/private/useInternalActivitiesWithRenderer.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { type ActivityComponentFactory } from 'botframework-webchat-api'; -import { type WebChatActivity } from 'botframework-webchat-core'; -import { useMemo } from 'react'; - -import useMemoWithPrevious from '../../../hooks/internal/useMemoWithPrevious'; - -type ActivityWithRenderer = Readonly<{ - activity: WebChatActivity; - renderActivity: Exclude, false>; -}>; - -type Call = Readonly<{ - activity: WebChatActivity; - nextVisibleActivity: WebChatActivity; - renderActivity: Exclude, false>; -}>; - -type Run = Readonly<{ - createActivityRenderer: ActivityComponentFactory; - calls: readonly Call[]; -}>; - -export default function useInternalActivitiesWithRenderer( - activities: readonly WebChatActivity[], - createActivityRenderer: ActivityComponentFactory -): readonly ActivityWithRenderer[] { - const run = useMemoWithPrevious( - prevRun => { - if (prevRun && !Object.is(prevRun.createActivityRenderer, createActivityRenderer)) { - // If `createActivityRenderer` changed, invalidate the cache. - prevRun = undefined; - } - - const calls: Call[] = []; - let nextVisibleActivity: undefined | WebChatActivity; - - for (let index = activities.length - 1; index >= 0; index--) { - const activity = activities[+index]; - - const prevEntry = prevRun?.calls.find( - entry => Object.is(activity, entry.activity) && Object.is(nextVisibleActivity, entry.nextVisibleActivity) - ); - - if (prevEntry) { - calls.unshift(prevEntry); - - nextVisibleActivity = activity; - } else { - const renderActivity = createActivityRenderer({ activity, nextVisibleActivity }); - - if (renderActivity) { - calls.unshift( - Object.freeze({ - activity, - nextVisibleActivity, - renderActivity - }) - ); - - nextVisibleActivity = activity; - } - } - } - - return Object.freeze({ createActivityRenderer, calls: Object.freeze(calls) }); - }, - [activities, createActivityRenderer] - ); - - return useMemo( - () => - run.calls.map(call => ({ - activity: call.activity, - renderActivity: call.renderActivity - })), - [run] - ); -} diff --git a/packages/component/src/providers/RenderingActivities/useActivityRendererMap.ts b/packages/component/src/providers/RenderingActivities/useActivityRendererMap.ts new file mode 100644 index 0000000000..2f8f83a547 --- /dev/null +++ b/packages/component/src/providers/RenderingActivities/useActivityRendererMap.ts @@ -0,0 +1,9 @@ +import { type WebChatActivity } from 'botframework-webchat-core'; +import { type ActivityPolyMiddlewareRenderer } from 'botframework-webchat-middleware'; +import { useRenderingActivitiesContext } from './private/RenderingActivitiesContext'; + +export default function useActivityRendererMap(): readonly [ + ReadonlyMap +] { + return useRenderingActivitiesContext().activityRendererMapState; +} diff --git a/packages/component/src/providers/RenderingActivities/useGetRenderActivityCallback.ts b/packages/component/src/providers/RenderingActivities/useGetRenderActivityCallback.ts deleted file mode 100644 index 9b19da7118..0000000000 --- a/packages/component/src/providers/RenderingActivities/useGetRenderActivityCallback.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type ActivityComponentFactory } from 'botframework-webchat-api'; -import { type WebChatActivity } from 'botframework-webchat-core'; - -import { useRenderingActivitiesContext } from './private/RenderingActivitiesContext'; - -export default function useGetRenderActivityCallback() { - const { renderActivityCallbackMap } = useRenderingActivitiesContext(); - - return (activity: WebChatActivity): Exclude, false> | undefined => - renderActivityCallbackMap.get(activity); -} diff --git a/packages/middleware/.eslintrc.yml b/packages/middleware/.eslintrc.yml new file mode 100644 index 0000000000..84aac24130 --- /dev/null +++ b/packages/middleware/.eslintrc.yml @@ -0,0 +1,10 @@ +extends: + - ../../.eslintrc.production.yml + +# This package is compatible with web browser. +env: + browser: true + +rules: + # React functional component is better in function style than arrow style + prefer-arrow-callback: off diff --git a/packages/middleware/.gitignore b/packages/middleware/.gitignore new file mode 100644 index 0000000000..62b899b6ae --- /dev/null +++ b/packages/middleware/.gitignore @@ -0,0 +1,4 @@ +/*.tgz +/dist/ +/lib/ +/node_modules/ diff --git a/packages/middleware/README.md b/packages/middleware/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/middleware/package.json b/packages/middleware/package.json new file mode 100644 index 0000000000..827dcc3bb1 --- /dev/null +++ b/packages/middleware/package.json @@ -0,0 +1,72 @@ +{ + "name": "botframework-webchat-middleware", + "version": "0.0.0-0", + "description": "The botframework-webchat middleware package", + "main": "./dist/botframework-webchat-middleware.js", + "types": "./dist/botframework-webchat-middleware.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/botframework-webchat-middleware.d.mts", + "default": "./dist/botframework-webchat-middleware.mjs" + }, + "require": { + "types": "./dist/botframework-webchat-middleware.d.ts", + "default": "./dist/botframework-webchat-middleware.js" + } + }, + "./tsconfig.json": "./src/ts-config/config.json", + "./env": { + "types": "./src/env.d.ts" + } + }, + "author": "Microsoft Corporation", + "license": "MIT", + "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/microsoft/BotFramework-WebChat.git" + }, + "bugs": { + "url": "https://github.com/microsoft/BotFramework-WebChat/issues" + }, + "files": [ + "./dist/**/*", + "./src/**/*", + "*.js" + ], + "localDependencies": { + "botframework-webchat-base": "development" + }, + "homepage": "https://github.com/microsoft/BotFramework-WebChat/tree/main/packages/middleware#readme", + "scripts": { + "build": "tsup --config ./tsup.config.ts", + "bump": "npm run bump:prod && npm run bump:dev && (npm audit fix || exit 0)", + "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localDependencies // {} | keys) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | contains([$K]) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", + "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localDependencies // {} | keys) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | contains([$K]) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install --save-exact $PACKAGES_TO_BUMP || true", + "eslint": "npm run precommit", + "postversion": "cat package.json | jq '.version as $V | (.localDependencies // {} | with_entries(select(.value == \"production\") | { key: .key, value: $V })) as $L1 | (.localDependencies // {} | with_entries(select(.value == \"development\") | { key: .key, value: $V })) as $L2 | ((.dependencies // {}) + $L1 | to_entries | sort_by(.key) | from_entries) as $D1 | ((.devDependencies // {}) + $L2 | to_entries | sort_by(.key) | from_entries) as $D2 | . + { dependencies: $D1, devDependencies: $D2 }' > package-temp.json && mv package-temp.json package.json", + "precommit": "npm run precommit:eslint -- src && npm run precommit:typecheck", + "precommit:eslint": "../../node_modules/.bin/eslint --report-unused-disable-directives --max-warnings 0", + "precommit:typecheck": "tsc --project ./src --emitDeclarationOnly false --esModuleInterop true --noEmit --pretty false", + "preversion": "cat package.json | jq '(.localDependencies // {} | to_entries | map([if .value == \"production\" then \"dependencies\" else \"devDependencies\" end, .key])) as $P | delpaths($P)' > package-temp.json && mv package-temp.json package.json", + "start": "npm run build -- --watch" + }, + "devDependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@tsconfig/strictest": "^2.0.5", + "@types/node": "^22.13.4", + "botframework-webchat-base": "0.0.0-0", + "botframework-webchat-react-valibot": "^0.0.0-0", + "cross-env": "^7.0.3", + "type-fest": "^4.34.1", + "typescript": "^5.7.3" + }, + "peerDependencies": { + "react": ">= 16.8.6" + }, + "dependencies": { + "react-chain-of-responsibility": "0.4.0-main.235b355", + "valibot": "1.1.0" + } +} diff --git a/packages/middleware/src/PolyMiddlewareComposer.tsx b/packages/middleware/src/PolyMiddlewareComposer.tsx new file mode 100644 index 0000000000..7779452734 --- /dev/null +++ b/packages/middleware/src/PolyMiddlewareComposer.tsx @@ -0,0 +1,43 @@ +import { reactNode, validateProps } from 'botframework-webchat-react-valibot'; +import React, { memo, useMemo } from 'react'; +import { + array, + custom, + function_, + object, + optional, + pipe, + readonly, + safeParse, + transform, + type InferInput +} from 'valibot'; + +import { ActivityPolyMiddlewareProvider, extractActivityPolyMiddleware } from './activityPolyMiddleware'; +import { PolyMiddleware } from './types/PolyMiddleware'; + +const polyMiddlewareComposerPropsSchema = pipe( + object({ + children: optional(reactNode()), + middleware: pipe( + custom(value => safeParse(array(function_()), value).success), + transform(value => Object.freeze(Array.from(value))) + ) + }), + readonly() +); + +type PolyMiddlewareComposerProps = Readonly>; + +function PolyMiddlewareComposer(props: PolyMiddlewareComposerProps) { + const { children, middleware } = validateProps(polyMiddlewareComposerPropsSchema, props); + + const activityPolyMiddleware = useMemo(() => extractActivityPolyMiddleware(middleware), [middleware]); + + return ( + {children} + ); +} + +export default memo(PolyMiddlewareComposer); +export { polyMiddlewareComposerPropsSchema, type PolyMiddlewareComposerProps }; diff --git a/packages/middleware/src/activityPolyMiddleware.tsx b/packages/middleware/src/activityPolyMiddleware.tsx new file mode 100644 index 0000000000..679088e260 --- /dev/null +++ b/packages/middleware/src/activityPolyMiddleware.tsx @@ -0,0 +1,66 @@ +import { type WebChatActivity } from 'botframework-webchat-core'; +import { validateProps } from 'botframework-webchat-react-valibot'; +import React, { memo, useMemo } from 'react'; +import { custom, object, pipe, readonly, safeParse, type InferInput } from 'valibot'; + +import templateMiddleware, { + type InferHandler, + type InferHandlerResult, + type InferMiddleware, + type InferProps, + type InferProviderProps, + type InferRenderer, + type InferRequest +} from './private/templateMiddleware'; + +const { + createMiddleware: createActivityPolyMiddleware, + extractMiddleware: extractActivityPolyMiddleware, + Provider: ActivityPolyMiddlewareProvider, + Proxy, + reactComponent: activityComponent, + useBuildRenderCallback: useBuildRenderActivityCallback +} = templateMiddleware<{ readonly activity: WebChatActivity }, { readonly children?: never }>('activity'); + +type ActivityPolyMiddleware = InferMiddleware; +type ActivityPolyMiddlewareHandler = InferHandler; +type ActivityPolyMiddlewareHandlerResult = InferHandlerResult; +type ActivityPolyMiddlewareProps = InferProps; +type ActivityPolyMiddlewareRenderer = InferRenderer; +type ActivityPolyMiddlewareRequest = InferRequest; +type ActivityPolyMiddlewareProviderProps = InferProviderProps; + +const activityPolyMiddlewareProxyPropsSchema = pipe( + object({ + activity: custom>(value => safeParse(object({}), value).success) + }), + readonly() +); + +type ActivityPolyMiddlewareProxyProps = Readonly>; + +// A friendlier version than the organic . +const ActivityPolyMiddlewareProxy = memo(function ActivityPolyMiddlewareProxy(props: ActivityPolyMiddlewareProxyProps) { + const { activity } = validateProps(activityPolyMiddlewareProxyPropsSchema, props); + + const request = useMemo(() => ({ activity }), [activity]); + + return ; +}); + +export { + activityComponent, + ActivityPolyMiddlewareProvider, + ActivityPolyMiddlewareProxy, + createActivityPolyMiddleware, + extractActivityPolyMiddleware, + useBuildRenderActivityCallback, + type ActivityPolyMiddleware, + type ActivityPolyMiddlewareHandler, + type ActivityPolyMiddlewareHandlerResult, + type ActivityPolyMiddlewareProps, + type ActivityPolyMiddlewareProviderProps, + type ActivityPolyMiddlewareProxyProps, + type ActivityPolyMiddlewareRenderer, + type ActivityPolyMiddlewareRequest +}; diff --git a/packages/middleware/src/index.ts b/packages/middleware/src/index.ts new file mode 100644 index 0000000000..48dea7a4d7 --- /dev/null +++ b/packages/middleware/src/index.ts @@ -0,0 +1,27 @@ +export { + activityComponent, + ActivityPolyMiddlewareProvider, + ActivityPolyMiddlewareProxy, + createActivityPolyMiddleware, + extractActivityPolyMiddleware, + useBuildRenderActivityCallback, + type ActivityPolyMiddleware, + type ActivityPolyMiddlewareHandler, + type ActivityPolyMiddlewareHandlerResult, + type ActivityPolyMiddlewareProps, + type ActivityPolyMiddlewareProviderProps, + type ActivityPolyMiddlewareProxyProps, + type ActivityPolyMiddlewareRenderer, + type ActivityPolyMiddlewareRequest +} from './activityPolyMiddleware'; + +export { + bridgeComponentPropsSchema, + default as createActivityPolyMiddlewareFromLegacy, + fallbackComponentPropsSchema, + type BridgeComponentProps, + type FallbackComponentProps +} from './internal/createActivityPolyMiddlewareFromLegacy'; + +export { default as PolyMiddlewareComposer } from './PolyMiddlewareComposer'; +export { type Init, type PolyMiddleware } from './types/PolyMiddleware'; diff --git a/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx b/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx new file mode 100644 index 0000000000..b1bea87cf1 --- /dev/null +++ b/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx @@ -0,0 +1,83 @@ +import { type ActivityMiddleware, type RenderAttachment } from 'botframework-webchat-api'; +import { type WebChatActivity } from 'botframework-webchat-core'; +import React, { type ComponentType, type ReactNode } from 'react'; + +import { custom, function_, never, object, optional, pipe, readonly, safeParse, type InferInput } from 'valibot'; +import { + activityComponent, + createActivityPolyMiddleware, + type ActivityPolyMiddleware +} from '../activityPolyMiddleware'; +import composeEnhancer from '../types/GenericMiddleware'; + +const webChatActivitySchema = custom(value => safeParse(object({}), value).success); + +type LegacyRenderFunction = ( + renderAttachment: RenderAttachment, + options: { + readonly hideTimestamp: boolean; + readonly renderActivityStatus: (options: { hideTimestamp: boolean }) => ReactNode; + readonly renderAvatar: false | (() => Exclude); + readonly showCallout: boolean; + } +) => Exclude; + +const bridgeComponentPropsSchema = pipe( + object({ + activity: webChatActivitySchema, + children: optional(never()), + render: custom(value => safeParse(function_(), value).success) + }), + readonly() +); + +type BridgeComponentProps = Readonly & { children?: never }>; + +const fallbackComponentPropsSchema = pipe( + object({ + activity: webChatActivitySchema, + children: optional(never()) + }), + readonly() +); + +type FallbackComponentProps = Readonly & { children?: never }>; + +function createActivityPolyMiddlewareFromLegacy( + bridgeComponent: ComponentType, + // Use lowercase for argument name, but we need uppercase for JSX. + fallbackComponent: ComponentType, + ...middleware: readonly ActivityMiddleware[] +): ActivityPolyMiddleware; + +function createActivityPolyMiddlewareFromLegacy( + bridgeComponent: ComponentType, + FallbackComponent: ComponentType, + ...middleware: readonly ActivityMiddleware[] +): ActivityPolyMiddleware { + const legacyEnhancer = composeEnhancer(...middleware.map(middleware => middleware())); + + return createActivityPolyMiddleware(() => { + const legacyHandler = legacyEnhancer(request => () => ); + + return ({ activity }) => { + // TODO: `nextVisibleActivity` is deprecated and should be removed. + const legacyResult = legacyHandler({ activity, nextVisibleActivity: undefined as any }); + + if (!legacyResult) { + return undefined; + } + + return activityComponent(bridgeComponent, { activity, render: legacyResult }); + }; + }); +} + +export default createActivityPolyMiddlewareFromLegacy; + +export { + bridgeComponentPropsSchema, + fallbackComponentPropsSchema, + type BridgeComponentProps, + type FallbackComponentProps +}; diff --git a/packages/middleware/src/private/templateMiddleware.check.test.tsx b/packages/middleware/src/private/templateMiddleware.check.test.tsx new file mode 100644 index 0000000000..a830acd2e1 --- /dev/null +++ b/packages/middleware/src/private/templateMiddleware.check.test.tsx @@ -0,0 +1,47 @@ +import templateMiddleware from './templateMiddleware'; + +test('should warn if middleware is not an array of function', () => { + const warn = jest.fn(); + const template = templateMiddleware('Check' as any); + + jest.spyOn(console, 'warn').mockImplementation(warn); + + template.extractMiddleware(1 as any); + + expect(warn).toHaveBeenCalledTimes(1); + expect(warn).toHaveBeenNthCalledWith(1, expect.stringContaining('must be an array of function')); +}); + +test('should warn if middleware did not return function', () => { + const warn = jest.fn(); + const template = templateMiddleware('Check' as any); + + jest.spyOn(console, 'warn').mockImplementation(warn); + + template.extractMiddleware([() => 1 as any]); + + expect(warn).toHaveBeenCalledTimes(1); + expect(warn).toHaveBeenNthCalledWith(1, expect.stringContaining('must return enhancer function')); +}); + +test('should not warn if middleware return false', () => { + const warn = jest.fn(); + const template = templateMiddleware('Check' as any); + + jest.spyOn(console, 'warn').mockImplementation(warn); + + template.extractMiddleware([() => false]); + + expect(warn).toHaveBeenCalledTimes(0); +}); + +test('should not warn if middleware return function', () => { + const warn = jest.fn(); + const template = templateMiddleware('Check' as any); + + jest.spyOn(console, 'warn').mockImplementation(warn); + + template.extractMiddleware([() => () => 1 as any]); + + expect(warn).toHaveBeenCalledTimes(0); +}); diff --git a/packages/middleware/src/private/templateMiddleware.test.tsx b/packages/middleware/src/private/templateMiddleware.test.tsx new file mode 100644 index 0000000000..4e1c20f88a --- /dev/null +++ b/packages/middleware/src/private/templateMiddleware.test.tsx @@ -0,0 +1,98 @@ +/** @jest-environment @happy-dom/jest-environment */ + +import { render } from '@testing-library/react'; +import React, { type ReactNode } from 'react'; + +import templateMiddleware, { type InferMiddleware } from './templateMiddleware'; + +type ButtonProps = Readonly<{ children?: ReactNode | undefined }>; +type LinkProps = Readonly<{ children?: ReactNode | undefined; href: string }>; + +const ButtonImpl = ({ children }: ButtonProps) => ; + +const ExternalLinkImpl = ({ children, href }: LinkProps) => ( + + {children} + +); + +const InternalLinkImpl = ({ children, href }: LinkProps) => {children}; + +// User story for using templateMiddleware as a building block for uber middleware. +test('an uber middleware', () => { + const buttonTemplate = templateMiddleware('Button' as any); + const { + createMiddleware: createButtonMiddleware, + extractMiddleware: extractButtonMiddleware, + Provider: ButtonProvider, + Proxy: Button, + reactComponent: buttonComponent + } = buttonTemplate; + + type ButtonMiddleware = InferMiddleware; + + const linkTemplate = templateMiddleware<{ external: boolean }, LinkProps>('Link' as any); + const { + createMiddleware: createLinkMiddleware, + extractMiddleware: extractLinkMiddleware, + Provider: LinkProvider, + Proxy: Link, + reactComponent: linkComponent + } = linkTemplate; + + type LinkMiddleware = InferMiddleware; + + const buttonMiddleware: ButtonMiddleware[] = [createButtonMiddleware(() => () => buttonComponent(ButtonImpl))]; + const linkMiddleware: LinkMiddleware[] = [ + createLinkMiddleware(next => request => (request.external ? linkComponent(ExternalLinkImpl) : next(request))), + createLinkMiddleware(() => () => linkComponent(InternalLinkImpl)) + ]; + + const App = ({ + children, + middleware + }: Readonly<{ + children?: ReactNode | undefined; + middleware: ReadonlyArray; + }>) => ( + /* TODO: Should not cast middleware to any */ + + {/* TODO: Should not cast middleware to any */} + {children} + + ); + + const result = render( + + {/* TODO: If "request" is of type "void", we should not need it specified. */} + + + {'Internal link'} + + + {'External link'} + + + ); + + expect(result.container).toMatchInlineSnapshot(` + +`); +}); diff --git a/packages/middleware/src/private/templateMiddleware.tsx b/packages/middleware/src/private/templateMiddleware.tsx new file mode 100644 index 0000000000..9962166987 --- /dev/null +++ b/packages/middleware/src/private/templateMiddleware.tsx @@ -0,0 +1,153 @@ +import { warnOnce } from 'botframework-webchat-core'; +import React, { memo, type ReactNode } from 'react'; +import { + createChainOfResponsibility, + type ComponentEnhancer, + type ComponentHandler, + type ComponentHandlerResult, + type ComponentRenderer, + type InferMiddleware as InferOrganicMiddleware, + type ProviderProps, + type ProxyProps +} from 'react-chain-of-responsibility/preview'; +import { array, function_, safeParse, type InferOutput } from 'valibot'; + +import { type GenericEnhancer } from '../types/GenericMiddleware'; + +const arrayOfFunctionSchema = array(function_()); +// TODO: Move marker inside templateMiddleware. Think if every type of middleware should have their own marker and not crossed. +const middlewareFactoryMarker = Symbol(); + +const isArrayOfFunction = (middleware: unknown): middleware is InferOutput => + safeParse(arrayOfFunctionSchema, middleware).success; + +const BYPASS_ENHANCER: GenericEnhancer = next => request => next(request); +const EMPTY_ARRAY = Object.freeze([]); + +// Following @types/react to use {} for props. +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +function templateMiddleware(name: string) { + const { Provider, Proxy, reactComponent, useBuildRenderCallback } = createChainOfResponsibility< + Request, + Props, + string + >(); + + type TemplatedEnhancer = ReturnType>; + type TemplatedMiddleware = (init: string) => TemplatedEnhancer; + + const createMiddleware = (enhancer: TemplatedEnhancer): TemplatedMiddleware => { + const factory: TemplatedMiddleware = init => (init === name ? enhancer : BYPASS_ENHANCER); + + // This is for checking if the middleware is created via factory function or not. + // We enforce middleware to be created using factory function. + + // TODO: Consider using valibot for validation, plus using "Symbol in object" check. + (factory as any)[middlewareFactoryMarker satisfies symbol] = middlewareFactoryMarker; + + return factory; + }; + + const warnInvalidExtraction = warnOnce(`Middleware passed for extraction of "${name}" must be an array of function`); + + const extractMiddleware = ( + middleware: readonly TemplatedMiddleware[] | undefined + ): readonly TemplatedMiddleware[] => { + if (middleware) { + if (isArrayOfFunction(middleware)) { + return Object.freeze( + middleware + .map(middleware => { + // TODO: Validate every middleware is created through `createMiddleware()`. + const result = middleware(name); + + if (typeof result !== 'function' && result) { + console.warn(`botframework-webchat: ${name}.middleware must return enhancer function or false`); + + return false; + } + + return result; + }) + .filter((enhancer): enhancer is ReturnType => !!enhancer) + .map(enhancer => () => enhancer) + ); + } + + warnInvalidExtraction(); + } + + return EMPTY_ARRAY; + }; + + // Bind "init" props. + const TemplatedProvider = memo(function TemplatedProvider({ + children, + middleware + }: { + children?: ReactNode | undefined; + middleware: readonly TemplatedMiddleware[]; + }) { + return ( + + {children} + + ); + }); + + TemplatedProvider.displayName = `${name}Provider`; + + Proxy.displayName = `${name}Proxy`; + + return { + createMiddleware, + extractMiddleware, + Provider: TemplatedProvider as typeof TemplatedProvider & InferenceHelper, + Proxy, + reactComponent, + useBuildRenderCallback + }; +} + +type InferenceHelper = { + '~types': { + handler: ComponentHandler; + handlerResult: ComponentHandlerResult; + middleware: (init: string) => ComponentEnhancer; + props: Props; + providerProps: ProviderProps; + proxyProps: ProxyProps; + renderer: ComponentRenderer; + request: Request; + }; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type InferHandler> = T['~types']['handler']; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type InferHandlerResult> = T['~types']['handlerResult']; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type InferMiddleware> = T['~types']['middleware']; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type InferProps> = T['~types']['props']; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type InferProviderProps> = T['~types']['providerProps']; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type InferProxyProps> = T['~types']['proxyProps']; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type InferRenderer> = T['~types']['renderer']; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type InferRequest> = T['~types']['request']; + +export default templateMiddleware; +export { + middlewareFactoryMarker, + type InferHandler, + type InferHandlerResult, + type InferMiddleware, + type InferProps, + type InferProviderProps, + type InferProxyProps, + type InferRenderer, + type InferRequest +}; diff --git a/packages/middleware/src/tsconfig.json b/packages/middleware/src/tsconfig.json new file mode 100644 index 0000000000..5677d4e342 --- /dev/null +++ b/packages/middleware/src/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "jsx": "react", + "moduleResolution": "Bundler", + "noEmit": true, + "skipLibCheck": true, + "target": "ESNext" + }, + "extends": "@tsconfig/strictest" +} diff --git a/packages/middleware/src/types/GenericMiddleware.ts b/packages/middleware/src/types/GenericMiddleware.ts new file mode 100644 index 0000000000..121c5603b2 --- /dev/null +++ b/packages/middleware/src/types/GenericMiddleware.ts @@ -0,0 +1,12 @@ +type GenericHandler = (request: Request) => Result; +type GenericEnhancer = (next: GenericHandler) => GenericHandler; +type GenericMiddleware = (init: Init) => GenericEnhancer; + +function composeEnhancer( + ...enhancers: readonly GenericEnhancer[] +): GenericEnhancer { + return next => enhancers.reduce((chain, enhancer) => enhancer(chain), next); +} + +export default composeEnhancer; +export { type GenericEnhancer, type GenericHandler, type GenericMiddleware }; diff --git a/packages/middleware/src/types/LegacyComponentMiddleware.ts b/packages/middleware/src/types/LegacyComponentMiddleware.ts new file mode 100644 index 0000000000..1d1c86519e --- /dev/null +++ b/packages/middleware/src/types/LegacyComponentMiddleware.ts @@ -0,0 +1,13 @@ +import { type ComponentType } from 'react'; + +import { type GenericEnhancer, type GenericHandler, type GenericMiddleware } from './GenericMiddleware'; + +type LegacyComponentHandler = GenericHandler, Request>; +type LegacyComponentEnhancer = GenericEnhancer, Request>; +type LegacyComponentMiddleware = GenericMiddleware< + ComponentType, + Request, + Init +>; + +export { type LegacyComponentEnhancer, type LegacyComponentHandler, type LegacyComponentMiddleware }; diff --git a/packages/middleware/src/types/PolyMiddleware.ts b/packages/middleware/src/types/PolyMiddleware.ts new file mode 100644 index 0000000000..17244a515e --- /dev/null +++ b/packages/middleware/src/types/PolyMiddleware.ts @@ -0,0 +1,10 @@ +import { type ActivityPolyMiddleware } from '../activityPolyMiddleware'; + +export type PolyMiddleware = ActivityPolyMiddleware; + +export type Init = + | 'activity' + | 'activity border' + | 'activity grouping' + | 'sendBoxMiddleware' + | 'sendBoxToolbarMiddleware'; diff --git a/packages/middleware/tsup.config.ts b/packages/middleware/tsup.config.ts new file mode 100644 index 0000000000..cb8add69d5 --- /dev/null +++ b/packages/middleware/tsup.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'tsup'; +import baseConfig from '../../tsup.base.config'; + +const config: typeof baseConfig = { + ...baseConfig, + entry: { + 'botframework-webchat-middleware': './src/index.ts' + } +}; + +export default defineConfig([ + { + ...config, + format: 'esm' + }, + { + ...config, + format: 'cjs', + target: [...config.target, 'es2019'] + } +]); From 6e33a6e811c458f6bf8bd138a266a5720c2b2bdf Mon Sep 17 00:00:00 2001 From: William Wong Date: Mon, 28 Jul 2025 09:22:15 +0000 Subject: [PATCH 02/16] Bump react-chain-of-responsibility and handler-chain --- .../html2/middleware/activity/legacy.html | 89 +++++++++++++++++++ package-lock.json | 13 ++- packages/api/src/types/ActivityMiddleware.ts | 8 +- packages/component/src/Composer.tsx | 2 +- packages/middleware/package.json | 5 +- ...createActivityPolyMiddlewareFromLegacy.tsx | 2 +- .../src/private/templateMiddleware.tsx | 5 +- .../middleware/src/types/GenericMiddleware.ts | 12 --- .../src/types/LegacyComponentMiddleware.ts | 13 +-- 9 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 __tests__/html2/middleware/activity/legacy.html delete mode 100644 packages/middleware/src/types/GenericMiddleware.ts diff --git a/__tests__/html2/middleware/activity/legacy.html b/__tests__/html2/middleware/activity/legacy.html new file mode 100644 index 0000000000..ee53158935 --- /dev/null +++ b/__tests__/html2/middleware/activity/legacy.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + +
+ + + diff --git a/package-lock.json b/package-lock.json index 42774f1641..6d0aebfa26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,8 @@ "./packages/test/harness", "./packages/test/web-server", "./packages/core", - "./packages/middleware", "./packages/react-valibot", + "./packages/middleware", "./packages/redux-store", "./packages/styles", "./packages/support/cldr-data-downloader", @@ -10341,6 +10341,14 @@ "dev": true, "license": "MIT" }, + "node_modules/handler-chain": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/handler-chain/-/handler-chain-0.1.0.tgz", + "integrity": "sha512-Bp0Imbm0pmt3FUNDK1y4Fj31K/tOGfVHKNZGprcuedHrcoRNwC7EYtJwJfdl1x9q8Lcs21sK43hbB8V01f+grA==", + "dependencies": { + "handler-chain": "^0.1.0" + } + }, "node_modules/happy-dom": { "version": "18.0.1", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.1.tgz", @@ -21858,7 +21866,8 @@ "version": "0.0.0-0", "license": "MIT", "dependencies": { - "react-chain-of-responsibility": "0.4.0-main.235b355", + "handler-chain": "^0.1.0", + "react-chain-of-responsibility": "0.4.0-main.a361688", "valibot": "1.1.0" }, "devDependencies": { diff --git a/packages/api/src/types/ActivityMiddleware.ts b/packages/api/src/types/ActivityMiddleware.ts index 341ca3285a..4f130bec39 100644 --- a/packages/api/src/types/ActivityMiddleware.ts +++ b/packages/api/src/types/ActivityMiddleware.ts @@ -1,6 +1,8 @@ -import type { ReactNode } from 'react'; -import type { RenderAttachment } from './AttachmentMiddleware'; -import type { WebChatActivity } from 'botframework-webchat-core'; +// TODO: Move this to botframework-webchat-middleware. +import { type WebChatActivity } from 'botframework-webchat-core'; +import { type ReactNode } from 'react'; + +import { type RenderAttachment } from './AttachmentMiddleware'; type ActivityProps = { hideTimestamp: boolean; diff --git a/packages/component/src/Composer.tsx b/packages/component/src/Composer.tsx index 3326eb233c..fd39184c85 100644 --- a/packages/component/src/Composer.tsx +++ b/packages/component/src/Composer.tsx @@ -451,7 +451,7 @@ const InternalComposer = ({ () => Object.freeze([ // TODO: Add . - createActivityPolyMiddlewareFromLegacy(LegacyActivityBridge, () => undefined, ...patchedActivityMiddleware) + createActivityPolyMiddlewareFromLegacy(LegacyActivityBridge, () => null, ...patchedActivityMiddleware) ]), [patchedActivityMiddleware] ); diff --git a/packages/middleware/package.json b/packages/middleware/package.json index 827dcc3bb1..afc59057e0 100644 --- a/packages/middleware/package.json +++ b/packages/middleware/package.json @@ -50,7 +50,7 @@ "precommit:eslint": "../../node_modules/.bin/eslint --report-unused-disable-directives --max-warnings 0", "precommit:typecheck": "tsc --project ./src --emitDeclarationOnly false --esModuleInterop true --noEmit --pretty false", "preversion": "cat package.json | jq '(.localDependencies // {} | to_entries | map([if .value == \"production\" then \"dependencies\" else \"devDependencies\" end, .key])) as $P | delpaths($P)' > package-temp.json && mv package-temp.json package.json", - "start": "npm run build -- --watch" + "start": "npm run build -- --onSuccess=\"touch ../api/src/index.ts ../component/src/index.ts\" --watch" }, "devDependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -66,7 +66,8 @@ "react": ">= 16.8.6" }, "dependencies": { - "react-chain-of-responsibility": "0.4.0-main.235b355", + "handler-chain": "^0.1.0", + "react-chain-of-responsibility": "0.4.0-main.a361688", "valibot": "1.1.0" } } diff --git a/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx b/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx index b1bea87cf1..05db10fa1a 100644 --- a/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx +++ b/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx @@ -1,5 +1,6 @@ import { type ActivityMiddleware, type RenderAttachment } from 'botframework-webchat-api'; import { type WebChatActivity } from 'botframework-webchat-core'; +import { composeEnhancer } from 'handler-chain'; import React, { type ComponentType, type ReactNode } from 'react'; import { custom, function_, never, object, optional, pipe, readonly, safeParse, type InferInput } from 'valibot'; @@ -8,7 +9,6 @@ import { createActivityPolyMiddleware, type ActivityPolyMiddleware } from '../activityPolyMiddleware'; -import composeEnhancer from '../types/GenericMiddleware'; const webChatActivitySchema = custom(value => safeParse(object({}), value).success); diff --git a/packages/middleware/src/private/templateMiddleware.tsx b/packages/middleware/src/private/templateMiddleware.tsx index 9962166987..c5d393b6cd 100644 --- a/packages/middleware/src/private/templateMiddleware.tsx +++ b/packages/middleware/src/private/templateMiddleware.tsx @@ -1,4 +1,5 @@ import { warnOnce } from 'botframework-webchat-core'; +import { type Enhancer } from 'handler-chain'; import React, { memo, type ReactNode } from 'react'; import { createChainOfResponsibility, @@ -12,8 +13,6 @@ import { } from 'react-chain-of-responsibility/preview'; import { array, function_, safeParse, type InferOutput } from 'valibot'; -import { type GenericEnhancer } from '../types/GenericMiddleware'; - const arrayOfFunctionSchema = array(function_()); // TODO: Move marker inside templateMiddleware. Think if every type of middleware should have their own marker and not crossed. const middlewareFactoryMarker = Symbol(); @@ -21,7 +20,7 @@ const middlewareFactoryMarker = Symbol(); const isArrayOfFunction = (middleware: unknown): middleware is InferOutput => safeParse(arrayOfFunctionSchema, middleware).success; -const BYPASS_ENHANCER: GenericEnhancer = next => request => next(request); +const BYPASS_ENHANCER: Enhancer = next => request => next(request); const EMPTY_ARRAY = Object.freeze([]); // Following @types/react to use {} for props. diff --git a/packages/middleware/src/types/GenericMiddleware.ts b/packages/middleware/src/types/GenericMiddleware.ts deleted file mode 100644 index 121c5603b2..0000000000 --- a/packages/middleware/src/types/GenericMiddleware.ts +++ /dev/null @@ -1,12 +0,0 @@ -type GenericHandler = (request: Request) => Result; -type GenericEnhancer = (next: GenericHandler) => GenericHandler; -type GenericMiddleware = (init: Init) => GenericEnhancer; - -function composeEnhancer( - ...enhancers: readonly GenericEnhancer[] -): GenericEnhancer { - return next => enhancers.reduce((chain, enhancer) => enhancer(chain), next); -} - -export default composeEnhancer; -export { type GenericEnhancer, type GenericHandler, type GenericMiddleware }; diff --git a/packages/middleware/src/types/LegacyComponentMiddleware.ts b/packages/middleware/src/types/LegacyComponentMiddleware.ts index 1d1c86519e..8f34358c6f 100644 --- a/packages/middleware/src/types/LegacyComponentMiddleware.ts +++ b/packages/middleware/src/types/LegacyComponentMiddleware.ts @@ -1,13 +1,8 @@ +import { type Enhancer, type Handler, type Middleware } from 'handler-chain'; import { type ComponentType } from 'react'; -import { type GenericEnhancer, type GenericHandler, type GenericMiddleware } from './GenericMiddleware'; - -type LegacyComponentHandler = GenericHandler, Request>; -type LegacyComponentEnhancer = GenericEnhancer, Request>; -type LegacyComponentMiddleware = GenericMiddleware< - ComponentType, - Request, - Init ->; +type LegacyComponentHandler = Handler, Request>; +type LegacyComponentEnhancer = Enhancer, Request>; +type LegacyComponentMiddleware = Middleware, Request, Init>; export { type LegacyComponentEnhancer, type LegacyComponentHandler, type LegacyComponentMiddleware }; From 10d1d42a06075677dd9f890909390b9f834f21ed Mon Sep 17 00:00:00 2001 From: William Wong Date: Mon, 28 Jul 2025 19:54:12 +0000 Subject: [PATCH 03/16] Move middleware typing to middleware package --- packages/api/src/hooks/Composer.tsx | 4 ++-- packages/api/src/hooks/internal/WebChatAPIContext.ts | 3 +-- .../internal/useCreateActivityRendererInternal.ts | 3 +-- packages/api/src/hooks/useCreateActivityRenderer.ts | 2 +- packages/api/src/hooks/useRenderAttachment.ts | 2 +- packages/api/src/index.ts | 8 ++++++-- packages/middleware/package.json | 10 ++++++++++ .../createActivityPolyMiddlewareFromLegacy.tsx | 3 ++- packages/middleware/src/legacy.ts | 6 ++++++ .../src/legacy/activityMiddleware.ts} | 4 ++-- .../src/legacy/attachmentMiddleware.ts} | 3 ++- packages/middleware/tsup.config.ts | 3 ++- 12 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 packages/middleware/src/legacy.ts rename packages/{api/src/types/ActivityMiddleware.ts => middleware/src/legacy/activityMiddleware.ts} (91%) rename packages/{api/src/types/AttachmentMiddleware.ts => middleware/src/legacy/attachmentMiddleware.ts} (78%) diff --git a/packages/api/src/hooks/Composer.tsx b/packages/api/src/hooks/Composer.tsx index 39d235f6d1..c7f919daec 100644 --- a/packages/api/src/hooks/Composer.tsx +++ b/packages/api/src/hooks/Composer.tsx @@ -32,6 +32,8 @@ import { type OneOrMany, type WebChatActivity } from 'botframework-webchat-core'; +import { type ActivityMiddleware, type AttachmentMiddleware } from 'botframework-webchat-middleware/legacy'; +import { ReduxStoreComposer } from 'botframework-webchat-redux-store'; import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useMemo, useRef, useState, type ComponentType, type ReactNode } from 'react'; import { Provider } from 'react-redux'; @@ -54,10 +56,8 @@ import ActivitySendStatusComposer from '../providers/ActivitySendStatus/Activity import ActivitySendStatusTelemetryComposer from '../providers/ActivitySendStatusTelemetry/ActivitySendStatusTelemetryComposer'; import ActivityTypingComposer from '../providers/ActivityTyping/ActivityTypingComposer'; import PonyfillComposer from '../providers/Ponyfill/PonyfillComposer'; -import ActivityMiddleware from '../types/ActivityMiddleware'; import { type ActivityStatusMiddleware, type RenderActivityStatus } from '../types/ActivityStatusMiddleware'; import AttachmentForScreenReaderMiddleware from '../types/AttachmentForScreenReaderMiddleware'; -import AttachmentMiddleware from '../types/AttachmentMiddleware'; import AvatarMiddleware from '../types/AvatarMiddleware'; import CardActionMiddleware from '../types/CardActionMiddleware'; import { type ContextOf } from '../types/ContextOf'; diff --git a/packages/api/src/hooks/internal/WebChatAPIContext.ts b/packages/api/src/hooks/internal/WebChatAPIContext.ts index 0396a90010..42e71a512c 100644 --- a/packages/api/src/hooks/internal/WebChatAPIContext.ts +++ b/packages/api/src/hooks/internal/WebChatAPIContext.ts @@ -6,13 +6,12 @@ import { type sendMessage, type setSendBoxAttachments } from 'botframework-webchat-core'; +import { type LegacyActivityRenderer, type RenderAttachment } from 'botframework-webchat-middleware/legacy'; import { createContext, type ComponentType } from 'react'; import { StrictStyleOptions } from '../../StyleOptions'; -import { LegacyActivityRenderer } from '../../types/ActivityMiddleware'; import { RenderActivityStatus } from '../../types/ActivityStatusMiddleware'; import { AttachmentForScreenReaderComponentFactory } from '../../types/AttachmentForScreenReaderMiddleware'; -import { RenderAttachment } from '../../types/AttachmentMiddleware'; import { AvatarComponentFactory } from '../../types/AvatarMiddleware'; import { PerformCardAction } from '../../types/CardActionMiddleware'; import { GroupActivities } from '../../types/GroupActivitiesMiddleware'; diff --git a/packages/api/src/hooks/internal/useCreateActivityRendererInternal.ts b/packages/api/src/hooks/internal/useCreateActivityRendererInternal.ts index ec03d9c6d5..f39e42c087 100644 --- a/packages/api/src/hooks/internal/useCreateActivityRendererInternal.ts +++ b/packages/api/src/hooks/internal/useCreateActivityRendererInternal.ts @@ -1,7 +1,6 @@ import { isValidElement, useMemo } from 'react'; +import { type ActivityComponentFactory, type RenderAttachment } from 'botframework-webchat-middleware/legacy'; -import { ActivityComponentFactory } from '../../types/ActivityMiddleware'; -import { RenderAttachment } from '../../types/AttachmentMiddleware'; import useRenderAttachment from '../useRenderAttachment'; import useWebChatAPIContext from './useWebChatAPIContext'; diff --git a/packages/api/src/hooks/useCreateActivityRenderer.ts b/packages/api/src/hooks/useCreateActivityRenderer.ts index 55fb552223..c03eeaab07 100644 --- a/packages/api/src/hooks/useCreateActivityRenderer.ts +++ b/packages/api/src/hooks/useCreateActivityRenderer.ts @@ -1,4 +1,4 @@ -import { ActivityComponentFactory } from '../types/ActivityMiddleware'; +import { type ActivityComponentFactory } from 'botframework-webchat-middleware/legacy'; import useCreateActivityRendererInternal from './internal/useCreateActivityRendererInternal'; // The newer useCreateActivityRenderer() hook does not support override renderAttachment(). diff --git a/packages/api/src/hooks/useRenderAttachment.ts b/packages/api/src/hooks/useRenderAttachment.ts index 5add4176fd..7cfc025347 100644 --- a/packages/api/src/hooks/useRenderAttachment.ts +++ b/packages/api/src/hooks/useRenderAttachment.ts @@ -1,4 +1,4 @@ -import { type RenderAttachment } from '../types/AttachmentMiddleware'; +import { type RenderAttachment } from 'botframework-webchat-middleware/legacy'; import useWebChatAPIContext from './internal/useWebChatAPIContext'; export default function useRenderAttachment(): RenderAttachment | undefined { diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 0f34bae547..6ad6b6622d 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,4 +1,10 @@ // TODO: Move the pattern to re-export. +import { + type ActivityComponentFactory, + type ActivityMiddleware, + type AttachmentMiddleware, + type RenderAttachment +} from 'botframework-webchat-middleware/legacy'; import StyleOptions, { StrictStyleOptions } from './StyleOptions'; import defaultStyleOptions from './defaultStyleOptions'; import Composer, { ComposerProps } from './hooks/Composer'; @@ -9,12 +15,10 @@ import { type DebouncedNotification, type DebouncedNotifications } from './hooks import { type PostActivityFile } from './hooks/useSendFiles'; import { localize } from './localization/Localize'; import normalizeStyleOptions from './normalizeStyleOptions'; -import ActivityMiddleware, { type ActivityComponentFactory } from './types/ActivityMiddleware'; import { type ActivityStatusMiddleware, type RenderActivityStatus } from './types/ActivityStatusMiddleware'; import AttachmentForScreenReaderMiddleware, { AttachmentForScreenReaderComponentFactory } from './types/AttachmentForScreenReaderMiddleware'; -import AttachmentMiddleware, { type RenderAttachment } from './types/AttachmentMiddleware'; import AvatarMiddleware, { type AvatarComponentFactory } from './types/AvatarMiddleware'; import CardActionMiddleware, { type PerformCardAction } from './types/CardActionMiddleware'; import { type ContextOf } from './types/ContextOf'; diff --git a/packages/middleware/package.json b/packages/middleware/package.json index afc59057e0..23b9ebd18f 100644 --- a/packages/middleware/package.json +++ b/packages/middleware/package.json @@ -5,6 +5,16 @@ "main": "./dist/botframework-webchat-middleware.js", "types": "./dist/botframework-webchat-middleware.d.ts", "exports": { + "./legacy": { + "import": { + "types": "./dist/botframework-webchat-middleware.legacy.d.mts", + "default": "./dist/botframework-webchat-middleware.legacy.mjs" + }, + "require": { + "types": "./dist/botframework-webchat-middleware.legacy.d.ts", + "default": "./dist/botframework-webchat-middleware.legacy.js" + } + }, ".": { "import": { "types": "./dist/botframework-webchat-middleware.d.mts", diff --git a/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx b/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx index 05db10fa1a..4c3e0647e2 100644 --- a/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx +++ b/packages/middleware/src/internal/createActivityPolyMiddlewareFromLegacy.tsx @@ -1,7 +1,8 @@ -import { type ActivityMiddleware, type RenderAttachment } from 'botframework-webchat-api'; import { type WebChatActivity } from 'botframework-webchat-core'; import { composeEnhancer } from 'handler-chain'; import React, { type ComponentType, type ReactNode } from 'react'; +import type ActivityMiddleware from '../legacy/activityMiddleware'; +import { type RenderAttachment } from '../legacy/attachmentMiddleware'; import { custom, function_, never, object, optional, pipe, readonly, safeParse, type InferInput } from 'valibot'; import { diff --git a/packages/middleware/src/legacy.ts b/packages/middleware/src/legacy.ts new file mode 100644 index 0000000000..3ea5528472 --- /dev/null +++ b/packages/middleware/src/legacy.ts @@ -0,0 +1,6 @@ +export { + type ActivityComponentFactory, + type default as ActivityMiddleware, + type LegacyActivityRenderer +} from './legacy/activityMiddleware'; +export { type default as AttachmentMiddleware, type RenderAttachment } from './legacy/attachmentMiddleware'; diff --git a/packages/api/src/types/ActivityMiddleware.ts b/packages/middleware/src/legacy/activityMiddleware.ts similarity index 91% rename from packages/api/src/types/ActivityMiddleware.ts rename to packages/middleware/src/legacy/activityMiddleware.ts index 4f130bec39..ef4025bbb1 100644 --- a/packages/api/src/types/ActivityMiddleware.ts +++ b/packages/middleware/src/legacy/activityMiddleware.ts @@ -1,8 +1,8 @@ -// TODO: Move this to botframework-webchat-middleware. +// TODO: This is moved from /api, need to revisit/rewrite everything in this file. import { type WebChatActivity } from 'botframework-webchat-core'; import { type ReactNode } from 'react'; -import { type RenderAttachment } from './AttachmentMiddleware'; +import { type RenderAttachment } from './attachmentMiddleware'; type ActivityProps = { hideTimestamp: boolean; diff --git a/packages/api/src/types/AttachmentMiddleware.ts b/packages/middleware/src/legacy/attachmentMiddleware.ts similarity index 78% rename from packages/api/src/types/AttachmentMiddleware.ts rename to packages/middleware/src/legacy/attachmentMiddleware.ts index a3fc769cd2..a0fc48e8aa 100644 --- a/packages/api/src/types/AttachmentMiddleware.ts +++ b/packages/middleware/src/legacy/attachmentMiddleware.ts @@ -1,4 +1,5 @@ -import { ReactNode } from 'react'; +// TODO: This is moved from /api, need to revisit/rewrite everything in this file. +import { type ReactNode } from 'react'; import type { DirectLineAttachment, WebChatActivity } from 'botframework-webchat-core'; type AttachmentProps = { diff --git a/packages/middleware/tsup.config.ts b/packages/middleware/tsup.config.ts index cb8add69d5..b2526537a8 100644 --- a/packages/middleware/tsup.config.ts +++ b/packages/middleware/tsup.config.ts @@ -4,7 +4,8 @@ import baseConfig from '../../tsup.base.config'; const config: typeof baseConfig = { ...baseConfig, entry: { - 'botframework-webchat-middleware': './src/index.ts' + 'botframework-webchat-middleware': './src/index.ts', + 'botframework-webchat-middleware.legacy': './src/legacy.ts' } }; From ff67e17aad8ec3d5710aa0f84fc11dec461f00dd Mon Sep 17 00:00:00 2001 From: William Wong Date: Tue, 29 Jul 2025 01:54:36 +0000 Subject: [PATCH 04/16] Add useReduceMemo --- .../html/renderActivity.performance.html | 137 +++-- __tests__/html/renderActivity.profiling.html | 518 ++++++++++-------- package-lock.json | 1 + packages/component/package.json | 1 + .../Activity/private/LegacyActivityBridge.tsx | 6 +- .../RenderingActivitiesComposer.tsx | 29 +- 6 files changed, 391 insertions(+), 301 deletions(-) diff --git a/__tests__/html/renderActivity.performance.html b/__tests__/html/renderActivity.performance.html index dc0136eacf..e6b3ece1fa 100644 --- a/__tests__/html/renderActivity.performance.html +++ b/__tests__/html/renderActivity.performance.html @@ -19,20 +19,32 @@ diff --git a/__tests__/html/renderActivity.profiling.html b/__tests__/html/renderActivity.profiling.html index edb4b12a2e..7163153dc6 100644 --- a/__tests__/html/renderActivity.profiling.html +++ b/__tests__/html/renderActivity.profiling.html @@ -1,11 +1,14 @@ - + - + @@ -46,21 +49,19 @@ ReactDOM: { render }, React: { Profiler }, WebChat: { - Components: { - BasicTranscript, - Composer - }, + Components: { BasicTranscript, Composer }, createDirectLine } } = window; const BATCH_SIZE = 20; - const timesActivityRendered = new Map(); + const timesEnhancerCalled = new Map(); + const timesHandlerCalled = new Map(); let activitiesCount = 0; - const commits = new Map() + const commits = new Map(); function handleRender(id, renderPhase, actualDuration, baseDuration, startTime, commitTime) { const commit = commits.get(commitTime) ?? {}; commit.activitiesCount = document.querySelectorAll('.webchat__bubble__content').length; @@ -69,29 +70,45 @@ commit[`${id} ${renderPhase} actual`] += actualDuration; commit[`${id} ${renderPhase} count`] ??= 0; commit[`${id} ${renderPhase} count`] += 1; - commit[`${id} ${renderPhase} avg`] = commit[`${id} ${renderPhase} actual`] / commit[`${id} ${renderPhase} count`] ; + commit[`${id} ${renderPhase} avg`] = + commit[`${id} ${renderPhase} actual`] / commit[`${id} ${renderPhase} count`]; commits.set(commitTime, commit); } function activityRendered() { - return next => (...args) => { - const [{ activity }] = args; - const renderActivity = next(...args) - timesActivityRendered.set(activity.id, (timesActivityRendered.get(activity.id) ?? 0) + 1); - return (...args) => ( - <> - - {renderActivity.call ? renderActivity(...args) : renderActivity} - - Rendered {timesActivityRendered.get(activity.id)} times - - ) - } + return next => + (...args) => { + const [{ activity }] = args; + const renderActivity = next(...args); + + timesEnhancerCalled.set(activity.id, (timesEnhancerCalled.get(activity.id) ?? 0) + 1); + + return (...args) => { + timesHandlerCalled.set(activity.id, (timesHandlerCalled.get(activity.id) ?? 0) + 1); + + return ( + <> + + {renderActivity.call ? renderActivity(...args) : renderActivity} + + + {' '} + Enhancer called {timesEnhancerCalled.get(activity.id)} times and handler called{' '} + {timesHandlerCalled.get(activity.id)} times + + + ); + }; + }; } const createActivity = (timestamp = new Date().toISOString(), nextIndex = activitiesCount++) => ({ - id: `activity-${nextIndex}`, text: `Message ${nextIndex}.`, textFormat: 'plain', type: 'message', timestamp - }) + id: `activity-${nextIndex}`, + text: `Message ${nextIndex}.`, + textFormat: 'plain', + type: 'message', + timestamp + }); async function postMessagesBatch(directLine) { const promises = []; @@ -99,10 +116,7 @@ for (let index = 0; index < BATCH_SIZE; index++) { promises.push( // Plain text message isolate dependencies on Markdown. - directLine.emulateIncomingActivity( - createActivity(timestamp), - { skipWait: true } - ) + directLine.emulateIncomingActivity(createActivity(timestamp), { skipWait: true }) ); } @@ -112,72 +126,92 @@ const testComplete = Promise.withResolvers(); - run(async function () { - const { directLine, store } = testHelpers.createDirectLineEmulator(); - - render( - -

-            
-              
-            
-          
, - document.getElementById('webchat') - ); + run( + async function () { + const { directLine, store } = testHelpers.createDirectLineEmulator(); + + render( + +

+              
+                
+              
+            
, + document.getElementById('webchat') + ); - await pageConditions.uiConnected(); - - // WHEN: Adding 100 activities. - await postMessagesBatch(directLine); - await postMessagesBatch(directLine); - await postMessagesBatch(directLine); - await postMessagesBatch(directLine); - await postMessagesBatch(directLine); - - const data = []; - for (const entry of commits.values()) { - const { - commit, - 'Transcript nested-update actual': transcriptNestedUpdate = 0, - 'Transcript update actual': transcriptUpdate = 0, - 'Activity update actual': activityUpdate = 0, - 'Activity mount actual': acivityMount = 0, - activitiesCount - } = entry; - if (acivityMount || transcriptNestedUpdate || transcriptUpdate || activityUpdate) { - data.push({ index: data.length, commit, activityUpdate, acivityMount, transcriptNestedUpdate, transcriptUpdate, activitiesCount }) + await pageConditions.uiConnected(); + + // WHEN: Adding 100 activities. + await postMessagesBatch(directLine); + await postMessagesBatch(directLine); + await postMessagesBatch(directLine); + await postMessagesBatch(directLine); + await postMessagesBatch(directLine); + + const data = []; + for (const entry of commits.values()) { + const { + commit, + 'Transcript nested-update actual': transcriptNestedUpdate = 0, + 'Transcript update actual': transcriptUpdate = 0, + 'Activity update actual': activityUpdate = 0, + 'Activity mount actual': acivityMount = 0, + activitiesCount + } = entry; + if (acivityMount || transcriptNestedUpdate || transcriptUpdate || activityUpdate) { + data.push({ + index: data.length, + commit, + activityUpdate, + acivityMount, + transcriptNestedUpdate, + transcriptUpdate, + activitiesCount + }); + } } - } - const activityTimes = data - .map(({ acivityMount, activityUpdate, activitiesCount }) => ({time: acivityMount + activityUpdate, count: activitiesCount })) - .filter(({ time }) => time); - const activityTimesSorted = activityTimes.sort(({ time: a }, { time: b }) => a > b ? 1 : a < b ? -1 : 0); - const excludedTimes = [].concat(activityTimesSorted.slice(0, 5), activityTimesSorted.slice(activityTimesSorted.length - 5)); - const activityTimesNorm = activityTimes.filter(v => !excludedTimes.includes(v)); + const activityTimes = data + .map(({ acivityMount, activityUpdate, activitiesCount }) => ({ + time: acivityMount + activityUpdate, + count: activitiesCount + })) + .filter(({ time }) => time); + const activityTimesSorted = activityTimes.sort(({ time: a }, { time: b }) => (a > b ? 1 : a < b ? -1 : 0)); + const excludedTimes = [].concat( + activityTimesSorted.slice(0, 5), + activityTimesSorted.slice(activityTimesSorted.length - 5) + ); + const activityTimesNorm = activityTimes.filter(v => !excludedTimes.includes(v)); - displayResults(activityTimesNorm); + displayResults(activityTimesNorm); - setTimeout(() => testComplete.resolve({ data, activityTimesNorm })); + setTimeout(() => testComplete.resolve({ data, activityTimesNorm })); - await host.snapshot(); - }, { ignoreErrors: true }); + await host.snapshot(); + }, + { ignoreErrors: true } + ); function displayResults(activityTimesNorm, pretty = true) { const prettyBool = condition => { - if (pretty) return condition()[0] ? '✅' : '❌' + if (pretty) return condition()[0] ? '✅' : '❌'; let m = condition.toString().match(/\[(.+),\s*(.*)\]/u); let c = condition(); - return `${c[0]}\n ${m[1]}\n ${m[2]} = ${c[1]}` - } - const covariance = ss.sampleCovariance(activityTimesNorm.map(({ time }) => time), activityTimesNorm.map(({ count }) => count)); + return `${c[0]}\n ${m[1]}\n ${m[2]} = ${c[1]}`; + }; + const covariance = ss.sampleCovariance( + activityTimesNorm.map(({ time }) => time), + activityTimesNorm.map(({ count }) => count) + ); const linearRegression = ss.linearRegression(activityTimesNorm.map(({ time }, i) => [i, time])); const results = document.querySelector('pre'); results.innerText = `Render results: Activities rendered:\t\t\t ${activitiesCount} Render data samples:\t\t\t ${activityTimesNorm.length} -Render time grows slow:\t\t\t ${prettyBool(() => [linearRegression.m < 0.5, linearRegression.m ])} +Render time grows slow:\t\t\t ${prettyBool(() => [linearRegression.m < 0.5, linearRegression.m])} Render time slightly moves with count:\t ${prettyBool(() => [covariance < 50, covariance])} `; expect(linearRegression.m).toBeLessThan(0.5); @@ -198,206 +232,234 @@ const marginBottom = 30; const marginLeft = 30; - const x = d3.scaleLinear() + const x = d3 + .scaleLinear() .domain(d3.extent(data, d => d.index)) .range([marginLeft, width - marginRight]); - const y = d3.scaleLinear() - .domain([0, d3.max(data, d => Math.max(d.activityUpdate, d.transcriptNestedUpdate, d.transcriptUpdate, activitiesCount))]).nice() + const y = d3 + .scaleLinear() + .domain([ + 0, + d3.max(data, d => Math.max(d.activityUpdate, d.transcriptNestedUpdate, d.transcriptUpdate, activitiesCount)) + ]) + .nice() .range([height - marginBottom, marginTop]); // Create the horizontal axis generator, called at startup and when zooming. - const xAxis = (g, x) => g - .call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0)) + const xAxis = (g, x) => + g.call( + d3 + .axisBottom(x) + .ticks(width / 80) + .tickSizeOuter(0) + ); // The area generators, called at startup and when zooming. - const areaTNU = (data, x) => d3.area() + const areaTNU = (data, x) => + d3 + .area() + .curve(d3.curveStepAfter) + .x(d => x(d.index)) + .y0(y(0)) + .y1(d => y(d.transcriptNestedUpdate))(data); + + const areaTU = (data, x) => + d3 + .area() .curve(d3.curveStepAfter) .x(d => x(d.index)) .y0(y(0)) - .y1(d => y(d.transcriptNestedUpdate)) - (data); + .y1(d => y(d.transcriptUpdate))(data); - const areaTU = (data, x) => d3.area() + const areaAU = (data, x) => + d3 + .area() .curve(d3.curveStepAfter) .x(d => x(d.index)) .y0(y(0)) - .y1(d => y(d.transcriptUpdate)) - (data); - - const areaAU = (data, x) => d3.area() - .curve(d3.curveStepAfter) - .x(d => x(d.index)) - .y0(y(0)) - .y1(d => y(d.activityUpdate)) - (data); - - const areaAM = (data, x) => d3.area() - .curve(d3.curveStepAfter) - .x(d => x(d.index)) - .y0(y(0)) - .y1(d => y(d.acivityMount + d.activityUpdate)) - (data); - - const areaAC = (data, x) => d3.area() + .y1(d => y(d.activityUpdate))(data); + + const areaAM = (data, x) => + d3 + .area() .curve(d3.curveStepAfter) .x(d => x(d.index)) .y0(y(0)) - .y1(d => y(d.activitiesCount)) - (data); + .y1(d => y(d.acivityMount + d.activityUpdate))(data); + + const areaAC = (data, x) => + d3 + .area() + .curve(d3.curveStepAfter) + .x(d => x(d.index)) + .y0(y(0)) + .y1(d => y(d.activitiesCount))(data); // Create the zoom behavior. - const zoom = d3.zoom() - .scaleExtent([1, 32]) - .extent([[marginLeft, 0], [width - marginRight, height]]) - .translateExtent([[marginLeft, -Infinity], [width - marginRight, Infinity]]) - .on("zoom", zoomed); + const zoom = d3 + .zoom() + .scaleExtent([1, 32]) + .extent([ + [marginLeft, 0], + [width - marginRight, height] + ]) + .translateExtent([ + [marginLeft, -Infinity], + [width - marginRight, Infinity] + ]) + .on('zoom', zoomed); // Create the SVG container. - const svg = d3.create("svg") - .attr("viewBox", [0, 0, width, height]) - .attr("width", width) - .attr("height", height) - .attr("style", "max-width: 100%; height: auto;"); + const svg = d3 + .create('svg') + .attr('viewBox', [0, 0, width, height]) + .attr('width', width) + .attr('height', height) + .attr('style', 'max-width: 100%; height: auto;'); const clip = { id: 'clip-path-1', - toString () { - return `url(#${this.id})` + toString() { + return `url(#${this.id})`; } }; - svg.append("clipPath") - .attr("id", clip.id) - .append("rect") - .attr("x", marginLeft) - .attr("y", marginTop) - .attr("width", width - marginLeft - marginRight) - .attr("height", height - marginTop - marginBottom); + svg + .append('clipPath') + .attr('id', clip.id) + .append('rect') + .attr('x', marginLeft) + .attr('y', marginTop) + .attr('width', width - marginLeft - marginRight) + .attr('height', height - marginTop - marginBottom); // Create area paths. - const pathTNU = svg.append("path") - .attr("clip-path", clip) - .attr("fill", "SteelBlue") - .attr("d", areaTNU(data, x)); - - const pathTU = svg.append("path") - .attr("clip-path", clip) - .attr("fill", "LightSteelBlue") - .attr("d", areaTU(data, x)); - - const pathAM = svg.append("path") - .attr("clip-path", clip) - .attr("fill", "LavenderBlush") - .attr("stroke", "LightPink") - .attr("stroke-width", "1") - .attr("d", areaAM(data, x)); - - const pathAU = svg.append("path") - .attr("clip-path", clip) - .attr("fill", "LightPink") - .attr("d", areaAU(data, x)); - - const pathAC = svg.append("path") - .attr("clip-path", clip) - .attr("stroke", "Coral") - .attr("stroke-width", "2") - .attr("fill", "none") - .attr("d", areaAC(data, x)); + const pathTNU = svg + .append('path') + .attr('clip-path', clip) + .attr('fill', 'SteelBlue') + .attr('d', areaTNU(data, x)); + + const pathTU = svg + .append('path') + .attr('clip-path', clip) + .attr('fill', 'LightSteelBlue') + .attr('d', areaTU(data, x)); + + const pathAM = svg + .append('path') + .attr('clip-path', clip) + .attr('fill', 'LavenderBlush') + .attr('stroke', 'LightPink') + .attr('stroke-width', '1') + .attr('d', areaAM(data, x)); + + const pathAU = svg.append('path').attr('clip-path', clip).attr('fill', 'LightPink').attr('d', areaAU(data, x)); + + const pathAC = svg + .append('path') + .attr('clip-path', clip) + .attr('stroke', 'Coral') + .attr('stroke-width', '2') + .attr('fill', 'none') + .attr('d', areaAC(data, x)); // Append the horizontal axis. - const gx = svg.append("g") - .attr("transform", `translate(0,${height - marginBottom})`) - .call(xAxis, x); + const gx = svg + .append('g') + .attr('transform', `translate(0,${height - marginBottom})`) + .call(xAxis, x); // Append the vertical axis. - svg.append("g") - .attr("transform", `translate(${marginLeft},0)`) - .call(d3.axisLeft(y).ticks(null, "s")) - .call(g => g.select(".domain").remove()) - .call(g => g.select(".tick:last-of-type text").clone() - .attr("x", 3) - .attr("text-anchor", "start") - .attr("font-weight", "bold") - .text("ms / activities")); + svg + .append('g') + .attr('transform', `translate(${marginLeft},0)`) + .call(d3.axisLeft(y).ticks(null, 's')) + .call(g => g.select('.domain').remove()) + .call(g => + g + .select('.tick:last-of-type text') + .clone() + .attr('x', 3) + .attr('text-anchor', 'start') + .attr('font-weight', 'bold') + .text('ms / activities') + ); // When zooming, redraw all areas and the x axis. function zoomed(event) { const xz = event.transform.rescaleX(x); - pathTNU.attr("d", areaTNU(data, xz)); - pathTU.attr("d", areaTU(data, xz)); - pathAU.attr("d", areaAU(data, xz)); - pathAM.attr("d", areaAM(data, xz)); - pathAC.attr("d", areaAC(data, xz)); + pathTNU.attr('d', areaTNU(data, xz)); + pathTU.attr('d', areaTU(data, xz)); + pathAU.attr('d', areaAU(data, xz)); + pathAM.attr('d', areaAM(data, xz)); + pathAC.attr('d', areaAC(data, xz)); gx.call(xAxis, xz); } // Initial zoom. - svg.call(zoom) + svg + .call(zoom) .transition() - .duration(750) - .call(zoom.scaleTo, 4, [x(200), 0]); + .duration(750) + .call(zoom.scaleTo, 4, [x(200), 0]); // Legend - svg.append("circle") - .attr("cx",80) - .attr("cy",50) - .attr("r", 6) - .style("fill", "SteelBlue"); - svg.append("text") - .attr("x", 95) - .attr("y", 52) - .text("Transcript nested-update ms") - .style("font-size", "15px") - .attr("alignment-baseline","middle"); - svg.append("circle") - .attr("cx",80) - .attr("cy",70) - .attr("r", 6) - .style("fill", "LightSteelBlue"); - svg.append("text") - .attr("x", 95) - .attr("y", 72) - .text("Transcript update ms") - .style("font-size", "15px") - .attr("alignment-baseline","middle"); - svg.append("circle") - .attr("cx",80) - .attr("cy",90) - .attr("r", 6) - .style("fill", "LightPink"); - svg.append("text") - .attr("x", 95) - .attr("y", 92) - .text("Activity update ms") - .style("font-size", "15px") - .attr("alignment-baseline","middle"); - svg.append("circle") - .attr("cx",80) - .attr("cy",110) - .attr("r", 6) - .attr("fill", "LavenderBlush") + svg.append('circle').attr('cx', 80).attr('cy', 50).attr('r', 6).style('fill', 'SteelBlue'); + svg + .append('text') + .attr('x', 95) + .attr('y', 52) + .text('Transcript nested-update ms') + .style('font-size', '15px') + .attr('alignment-baseline', 'middle'); + svg.append('circle').attr('cx', 80).attr('cy', 70).attr('r', 6).style('fill', 'LightSteelBlue'); + svg + .append('text') + .attr('x', 95) + .attr('y', 72) + .text('Transcript update ms') + .style('font-size', '15px') + .attr('alignment-baseline', 'middle'); + svg.append('circle').attr('cx', 80).attr('cy', 90).attr('r', 6).style('fill', 'LightPink'); + svg + .append('text') + .attr('x', 95) + .attr('y', 92) + .text('Activity update ms') + .style('font-size', '15px') + .attr('alignment-baseline', 'middle'); + svg + .append('circle') + .attr('cx', 80) + .attr('cy', 110) + .attr('r', 6) + .attr('fill', 'LavenderBlush') .attr('strokeWidth', '1') .style('stroke', 'LightPink'); - svg.append("text") - .attr("x", 95) - .attr("y", 112) - .text("Activity mount ms") - .style("font-size", "15px") - .attr("alignment-baseline","middle"); - svg.append("circle") - .attr("cx",80) - .attr("cy",130) - .attr("r", 6) - .attr("fill", "none") + svg + .append('text') + .attr('x', 95) + .attr('y', 112) + .text('Activity mount ms') + .style('font-size', '15px') + .attr('alignment-baseline', 'middle'); + svg + .append('circle') + .attr('cx', 80) + .attr('cy', 130) + .attr('r', 6) + .attr('fill', 'none') .attr('strokeWidth', '2') .style('stroke', 'Coral'); - svg.append("text") - .attr("x", 95) - .attr("y", 132) - .text("Activities shown count") - .style("font-size", "15px") - .attr("alignment-baseline","middle"); + svg + .append('text') + .attr('x', 95) + .attr('y', 132) + .text('Activities shown count') + .style('font-size', '15px') + .attr('alignment-baseline', 'middle'); document.body.appendChild(svg.node()); } diff --git a/package-lock.json b/package-lock.json index 6d0aebfa26..bbf4472a98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21170,6 +21170,7 @@ "redux": "5.0.1", "simple-update-in": "2.2.0", "use-propagate": "0.2.1", + "use-reduce-memo": "^0.1.0-main.1384637", "use-ref-from": "0.1.0", "use-state-with-ref": "0.1.0", "valibot": "1.1.0" diff --git a/packages/component/package.json b/packages/component/package.json index 223b6b9219..78c5664cad 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -145,6 +145,7 @@ "redux": "5.0.1", "simple-update-in": "2.2.0", "use-propagate": "0.2.1", + "use-reduce-memo": "^0.1.0-main.1384637", "use-ref-from": "0.1.0", "use-state-with-ref": "0.1.0", "valibot": "1.1.0" diff --git a/packages/component/src/Middleware/Activity/private/LegacyActivityBridge.tsx b/packages/component/src/Middleware/Activity/private/LegacyActivityBridge.tsx index d58dd08ed1..ce2ee5c09a 100644 --- a/packages/component/src/Middleware/Activity/private/LegacyActivityBridge.tsx +++ b/packages/component/src/Middleware/Activity/private/LegacyActivityBridge.tsx @@ -88,7 +88,7 @@ function LegacyActivityBridge(props: BridgeComponentProps) { showCallout = true; } - const node = useMemo( + const children = useMemo( () => render(renderAttachment, { hideTimestamp, @@ -96,12 +96,12 @@ function LegacyActivityBridge(props: BridgeComponentProps) { renderAvatar: renderAvatarForSenderGroup, showCallout }), - [render, hideTimestamp, renderActivityStatus, renderAttachment, renderAvatarForSenderGroup, showCallout] + [hideTimestamp, render, renderActivityStatus, renderAttachment, renderAvatarForSenderGroup, showCallout] ); return ( - {node} + {children} ); } diff --git a/packages/component/src/providers/RenderingActivities/RenderingActivitiesComposer.tsx b/packages/component/src/providers/RenderingActivities/RenderingActivitiesComposer.tsx index 3e8174c791..ec75f42851 100644 --- a/packages/component/src/providers/RenderingActivities/RenderingActivitiesComposer.tsx +++ b/packages/component/src/providers/RenderingActivities/RenderingActivitiesComposer.tsx @@ -1,7 +1,8 @@ import { hooks } from 'botframework-webchat-api'; import { type WebChatActivity } from 'botframework-webchat-core'; import { useBuildRenderActivityCallback, type ActivityPolyMiddlewareRenderer } from 'botframework-webchat-middleware'; -import React, { memo, useMemo, type ReactNode } from 'react'; +import React, { memo, useCallback, useMemo, type ReactNode } from 'react'; +import { useReduceMemo } from 'use-reduce-memo'; import RenderingActivitiesContext, { type RenderingActivitiesContextType } from './private/RenderingActivitiesContext'; @@ -42,16 +43,22 @@ const RenderingActivitiesComposer = ({ children }: RenderingActivitiesComposerPr const renderActivity = useBuildRenderActivityCallback(); - const activityRendererMap = useMemo>( - () => - Object.freeze( - new Map( - activitiesOfLatestRevision - .map(activity => [activity, renderActivity({ activity })] as const) - .filter((tuple): tuple is [WebChatActivity, ActivityPolyMiddlewareRenderer] => !!tuple[1]) - ) - ), - [activitiesOfLatestRevision, renderActivity] + const activityRendererMap = useReduceMemo( + activitiesOfLatestRevision, + useCallback< + ( + activityRendererMap: ReadonlyMap, + activity: WebChatActivity + ) => ReadonlyMap + >( + (activityRendererMap, activity) => { + const renderer = renderActivity({ activity }); + + return renderer ? Object.freeze(new Map(activityRendererMap).set(activity, renderer)) : activityRendererMap; + }, + [renderActivity] + ), + new Map() ); const renderingActivitiesState = useMemo( From c36bed05ea079e354ffb4062dbaf760d3c812367 Mon Sep 17 00:00:00 2001 From: William Wong Date: Tue, 29 Jul 2025 04:43:27 +0000 Subject: [PATCH 05/16] Update performance snapshot --- ...t-produce-unnecessary-rerenders-1-snap.png | Bin 37314 -> 53552 bytes ...mance-render-activity-profiling-1-snap.png | Bin 37447 -> 50952 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/__tests__/__image_snapshots__/html/render-activity-performance-js-activity-render-performance-does-not-produce-unnecessary-rerenders-1-snap.png b/__tests__/__image_snapshots__/html/render-activity-performance-js-activity-render-performance-does-not-produce-unnecessary-rerenders-1-snap.png index 5b230b71fdb56b587a7323d15afeff6e02605282..20423539dee1010192bdbac9f1ba1a3ccd249284 100644 GIT binary patch literal 53552 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 literal 37314 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 K2BCTjxBU-q6cI`Q literal 37447 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

8*r-rl1NuOw=EY{bscAk%amm;;H= zR9I1Xe?`>IO+FD3Oax$!j0njS(Gxbtv17+9Xhb8mC;&g2crdzi9l#8wrKQ+xF`+HMwRp@kJumX{6L7{i z6w^`^R~GlgN!#53YuNj17-_uYVq^LfMD+#agA|S4?;^U#WA*|ZCd-yALlT!0H7^fC zFyi&sue`uVNKd?acrc2YlQRTc16B+*3Q!GNb~7}yv~gKk*~wqOs_#^Po=%2?8b?zjTv$keUjbdVOxr z4Q80-z`Mv99={U>=lBjB;0BDSM)GeGCBmxq_Vzg(la#zXCW@Ahjz3Zp2s9;5aKWFL z$(TQU#;UO|gPe3y1@D(6tX5Vlv}+T!ZF|i9V|e%$EN}dCR9icw$!m7(ggYdV zcc5y3xa7e8WRpHSen-q)ke8PiKI=9Bu}-9!zLa^~!E)8Udlw`rDVdRz6NXr`26n(1 z2Ztlr;AvQk4HThWyZHF|gVj_JAAShpObQjsLNgv-#`EXKy%+ai?4$WRH+Ks~i4-(c zUR0U2>a>9`#gLj5FWrbVnTN_E8e=Sf1_1HwZmU6ThrYaP5-Wizv$Lt!~y zjvp6Zw`Z%_=LuXhByJl(J(6FQc)pW}ZCXL%LL~Te{rYf;S-PuB45y{4US9n@iB$Ld zIB5nnBC>a3wwere>ekHh0j#^P}h(i?yWz2CESJP{DC{ZbPuG3%~ZLQ)v{H~MW| zQEfQ?LoEK^A&a%LtVBBJrqS~UUI19!YQKRycx2yjaOiF$${ z3~{INBIkM>^;+R9Oc{x zI|WAP6KvV{338i27*vs(yMSSu&!$A) z1MboA7IrBrCItl{KA=kyRWERwA?7q=jcR*(%-jy_+!;g^8a1`0`I%J5^1m83zb7Y? zk=Sbj41#ePk1QfIC%_9SXqx=|l2TF+qodhSvmbz%h2lV|A~*w$0+|`xujOS`VGcDH4T-H2gq!p_c$*~Z1tdpg z2hia4pc?_6Z94$P!FPhf!{M+Lcskm`$QVn7Bm=b@ueuo@A20i-9(@Edj5-4D8sVG) z2u90)n%!)b#}u=n5V)p%;inuKoWQGqzp75<>IkaB*!QO~?T0-)GrJ>3dsOEJl4@%H zsPw`KcVZC^lCY#%Tv{?5c_9krx(fNoCqP=*`7y2M*QqbK{QiD;ar%=kauU@jdE#L3 zf~aP*BQ}#x&97fm<4C>*oQ+IOTu*_6^N-<#n3yzYMVO@fkbBN;ONfte!a&C&Ah`Ad zc?sIN;m?I65`;ndJB@unA5{Pyg-6jVr%+LSq$>{@(^NDH4jG3nv7)9~lS!ObV;>%r z3?#ko@qP}|361}HSlBwE`atYOq~*EGC_6Gj7H7`}pgS|OAj%trE%gm{ybW3ee&fZy zH)|2PPsz%n2bU%;5sc!X}KB>55|8{ z$^W}0{{Q0}nWvtZ{|nYwU|r6&&Pt~&BR=`}@83{=)|r}qc@!2Xt#bD4_Fwplg~ilU zRg16#nnFw^I5|74rPH#r>ys|v-A!3r{=a$j|M|BneZm(dR1sPR7~RN)MtGYjyO?9) zx^nLql6Nqpe1Wqn2xqm;IjGqL5P`H;R)IZwL(@n75~ELcB>9g76Fs!xz-K+~qh|wO za~!*LI4*f`*J)pI=O^ z8^_8c6T*Vyo(w>A^5jXKIH|RmJ*tuf96$C*=Q>J4Sa`U^x%V7!2bBd_`DA2{8=nJ+ zLPrV!btHD)=FORtZY0o$6bPmgY}>D+tVPC2DIYg9=H zTS%>afMaj5vGQF;jmD5r1dgkcL|Lu@+nKBvtM~@-m^>)bbn)e^&&wdp{Mg=rked4X zti=vrLZFSlPf|2*uR((gW#rm>nw&51WqBMN2>?^uhwZlnd64X#!~T0ZMni=pNusPq0)}zA>_f13>mnvG&dOi?^{|1Ya*rAr&E^D# zW5n?N)h7In*Py$4r%r`I0IOp7K8+hP=tfHU>QBfdZ=37?N zlPW_n7ebn#A_2SD&W>ZhRX#miFMjpc8S-6V=o-LFp*P^etAf>5fi{%Pf!q$nQjs~q z5@TR1MhaS`S7Km<#Ow`szM&G@)vK*wrJ=7?<2dGn2bGn@W(=hc-iUvBXlQ6alAXh-;nAaf&z9m? z_EL~hq+}>;NQ3QBWLZ>DVE3-qA}n9F?AE`aYq8Bu|B*5{8Us}2%WYgI{%3VMrQnD+1tu2$6Qi>nHQmJpb-b$&y$=%C~Nx_ zE-`?2JuEdJI7r?0B1p(DW= za0U{2MdWF@gqDSk?em%R;9Vmg$ORz-%KyDFDFQ9HA8JNTr26A(6H3*MiV8(wT7NRuOmPD81ih)mt^<~A+|CRo_|*J)t*-!MRSBiYiz3R` zTwtw>7uD{524f6!$`24)p!h((K?HPe%=*V!W8OHHS z=%kv+6z@Q0r*{Tz)}-JZBMZySXj=pcDyMvS{o3zH{61xXRDNjkb+xs8{14#5euUi} z9Q3ZK=@xONGctmf=SRgx5CKxx)ZD<*4BC^4fIR(z(Ew^ZhJIj`85VUysnh;F)VL{; ziBl>FA3VVGS$;lCCmj=y4%m;H+Vo_v>c^(0WF#)!|Jb3ENx{J7=NA$Yd5C0^a9EVY ztnDshBC8IvT*kA*;@kji-zq1^g>ZlyCKBD}#&Ki7zugN!MF9XJ(=boZtfk=e6*rV} zV-5kr@@h)_zKg3c&6-i9{wWA%+VMrHJz&gC@VR|~lr6RDh-6n<1te3+fG=1($G7!q$v$^h!B*=`ISC}Y~yY44yG`!z$YcehQ?6^2{cY*eNB6I zYUOKngh0uZ609n9d|V14FIpH#Ffxj*Tbi4j`}QVyLy_4PXJ-{i2?Xlx94A&Ey0&4- zgtx5M;y#iw$DR<@Y2f7KbVJzS<1$;OW5s$51Hq@afD?!D*32qzThzYL@3+0zd#v;# zGm5oe4RsB~XFX&$NDBS%95I(37!$K`6*aZ8n(=cdK5Vb+0NE)xBLpWv82mOg6o}Lo znR>^=9quUDLA*cGCz0mehfabyYG1qnT2Kn<gVL?*#5KWHmBPhd|+z;$jWte~~Kp zL+H#Bd#Sy^nrRuO3ldh1?o0Btt^t32hpz+#R3pA7F}o#G ze&DutT6}y5)o zda=|Sy!?U#zn44{y zal|Nq`FXkk<`9XPDvF~EKw^m#R+bPX6Edjmz4FIf6JUWbLrqgoD;^Gc7hWJnOso^I)_tDCiNSPBs0j_+H$HW0lcq#Ko;zEmq z!%??yuR>YE9I6#gHKqW^T>df0gxFu+xh@n%B{ld7rL=qMI6NNQ3h-K*IezE`E6Y*H zYh{1G-$#m4*4f2nyI3Rd-+m6D>E|%ni?m{DLj;347EzWtEstx z8mIwSEKp=}7$s{v<#Ttpj+z?eGV+#PtOAwO^z$tAt2C%(T;$(xH|;Vsq?MYP&`$XMrYh{ zprYgBFSPCu6ug1J$nL~cAQJKuaXmt4(j}QKO<}>GmT0DZ#0osV-?JvE&R(gvkPA{; zY^UZ&^X|{=xuse_bnc#zc~4}`&OQMkrIMy7~lj@(9QCnj_;L$fA8 zg$$Qqju)f&4F3p@Ftga&+9n$aeHghb zV^WmWbxOR3WAaw3GSYljNP;wt&diM!G$!lqL8AH;AWYwXBOOF5u5&|04#lfo;o4!xsCuOm-M?}DfO>a_T2 z$ZF?iNYr<67p?Emu*{c-#{rfD`#8C0XIbSA%IxTvga?rF$drX~S+@34%q( zboU$RF4*^R4xS`OX=OznqaA<@b;Zergl*sCJPm|4a&mD+MnzEmQSC4KfV-+01tvv zi;XLbx)BSt99CE4qkUrQ2Qen8iZ%c|!}N@eqHLlf&2vu{qAoc>W!{ zQ?&%u6ENjLW@c?p>-X>Hj8B~SGV_8<%(T@1O3&5RD}R$#YJ1;vjDAvG$hd+4R#tX4 z@sqJ5X4-c^--P2qq)hAyvJ}w37_`$^b8~Z_JbkKyq!9E|^i!%_qfqQqlI?{WJYHZb z6vMxB=Ng=_CL1Z(XK#e_UJ_mN)C*U}mX;Rmh@h}AHLy_-kCbK`;wk{Ct#OfsX94I` zTU$%|jZrpHvjL0UmzoYjLn691tcm2DocCj{)_=`_r`YIeVK(uO5kqj%cs}Xf^5eee zsYOLGSY8{D?yBOidElM%FcEic-Rc8@v3AfoBq#`4u29Mnkj*sGnYS@VOME*V%pdSF zpFS^p@W2ct1{_7G)S#(~I=K-wn5r zlbzFdi<#vF>%h#+476h2Wb+Mumf{>v$Wgo-bhbEYYb&(Zu_p`&RnxfR%K<_-BckL& z>F53O&b9dgeUU4XpgrMvx5L0Bucc*Rmmm4aVWoR($EdA3`#cusB1k|E%){{Ta749U zLHYMM=;!_Z(=GYw!*Q)99Y0}$ocHiHN0 z?POcCe!bV$04;&rt0f!C!gk-O4{#iDfPI9?W?bAtCP)jsAP11~3CJ^46sRwLNl9OQ zTIxBhULClmH<%QTOS0hYkkLx!CJZO|8)XOJVkJDQC!PzgFw*(JR4#pgUkQ!t;>?I4 zNu3deHAeV2GcPX_E*`+W5^TtIRLvo*hg)4LK{t;gdj&I^#6(c8c}QvxK;8n85)!vF z$-M#eiW8ELd#R8EN^Q$u>44bdzX10lP>o;N&Mx}|FAgszN>Lby{VrBE&HB$NMhnP5 zaNXY^v@->Zh8RV}O~GTar=?&-OS5Gl_e zVcb#$4USB(!e$hQ;ZR2fgr!S z$fq{9TXDqc2r{!^U|~UC%wF=|Bn)L71`{b)T`5k+y%*YjF!+wfaAG=sdDy;!NRQ<9 zv;6#g0v=UVs(e~1IXtd)|L%PNwT7@lnI1Q4oN|f}*0&E&C?hZLnOi(mREU_aT|Y6D z3fO?lpOPCTNKOZrnT5VxF2mmJN6xGg*w>xW8Uhn2ZFb|7`=w_~LZYHKkf+2J#GO)| zX5N_+HHjjs`d;4c3vw{=Ut@X)cnH^o5S9qcsG8cm6`PtMW(Edo7IIk$RLN=}2WDnw zAN)SN5pum3kTjvAxCDzh5jZK-Brb)UWC;Jn7~X^IYzBxBZnGWM#P&fV3i(=9(L4Q+ zP{>KdWE=}KGX)A3J}Ez-6pjT+)yc}qNkFC0xhI+SHvjeO*B@pNr|5mdxz*Cu)r1v9 z)Yz!n(p()0j&z>M-LKhZ%R6^tMa!?a&-uhBQSOQ;m^lc5`vpf^TVGUI zWHdL-NDOkzxqid-PXSQp2AuZ;xHNZnFJ{zRh6OkI@F>gSb`7NS-WCJL;4@syeWXkv zF&Td6U76D)w{?)56XrI|yq9MFjnE0Gsl{H-$;-p!{)=jA!X;%Kd)~k;L#tK%2f-sY z$#vW?b(7}+y|n!gJ)dlDDNn_1IEjtB|pWANtfG2H_#6ZsP}OYlOsAWO64^%L(3 zKX{wC_>-=qpyJX;k`%(B`(5!kVrIre0-iWLIF96+M3~OZoueZo#3Dfw{S`PJx%&_N zN5o-bWTXlXBe@?4g#a~atElMu@$qpL-qhwGt3& zyGIvZb9n5VcK57sq~>Wd$&IBnL7;}1c}6nw*)tg^PA&{K0V>K+WzY4)b-9nd21JVq zCX6AbJ$e*3gSnq{I6I-8qobo3NBWp^fRFDwQZ-`ovw!}iBX&}t03nTal}TP(6v!*? z<}6S^SZ-i+v}>pv5seKLPRTQCmVW4Z^GDTw9=bGNMfKtCzAw5;OmmVitP`)&pc7ZH7+m7zMjEqe#l`Hn?W3%r z)?=b9umV@C%)xV;s=#a+p+oAIYB@Zf%167PVl@-(Yuf#Q!ot{|oow`w^zP4;K*R{s z*oLNJVQsym8-_0Rw`bnIR7%KvqWU=vyko)WwX*1)ZM$|IG|p-}z5rM8R$N>NIT)HZ zwYdfsRhFq(ma5Yw=W0*O9>ykr;%p;z`H z7x(t-%JLtN#N+6LvwE>dK&AK;6nF@yf!+t|{V6vB;TQn*WRN_B#ID#?T%x&_9-*D4 zVr8`rYHhMnHW{RY5@gn8D72A8S3!I0#xe}^+8F*!T+1cW>Ibux2AGZe~bQy!V2*phl@(_!GnG|*B5%=J&)Dc!5O`q$%D@OL379;I6^bq5L))& zGcp3=tic6iG!R^RzxVf3qq#siXVCmc=STI*F(u*d-5?}aac{}v##!ia9N`)TslBbO zMx#pY{|qFxuXxK#k2TOnmzyy0lA#vbdVI?24jvh^YGjRK8I|=2}q7e zDA7v2ycB>pU>!IO)@@8Vg4>|TZ78^10XN1p*%rZ>Q8hN^Ai5gQpAU!!dN2P@ngT;t z$u;VS$)fJyAWH5@LBzQ~V3QezjKZj<=;2DyUzHVW!}8yTrmX~zO$gnTL%Xl7th|o7 z8qo_2OG4}UA0WUUziQPgEuom(r$o~MMzgZASUYv$aRBM?@0XK{LNUi+N20>~@V3w{ zVIiTN`}W<%)kff}5s=|jU0hrS`ukO(C~wO(I)rn>#?5^ghi6YcX4yteaUp_%^As~I33~KaKEKaHt3jusw4M#v;UjB(wZxzpR z*plr^y~nor5nD2NLlUtim4m2pZ=_nt|I(jzeMmtTYgk(kDYaJ3XBHG}Xl=drMYx8J zE7Uv7^wg>3Pe1&{%+K~gUbUZ8P~ce$TtEg9_`=t6ZXkSwC~E#ob!Q&ebKd^@Z_8A&BukQnjBUm)#3+?8 z_QW7dqBJQYYf5P(g@`fsrIK|>rA?@0Da%j@t&=QMsiaYAN}T7F`*+Sf&OPTozjNQ` zaenu?|6uM)efxah%XPiBi>T)1M5UrWXp5@s8U>{rdj%r)dTwq9VRBZkjPCDEydy_g ze-!o58Vi9##IYjM8f`l#GjejA zpNu{GSS){RaSGM63H=f`b(^r)jz&|kacp~06sK2^@?BhBs4PMO9y9z$D5hvg-7%<` z=hfT=7J$t0<054NVir*Hvv?33?>Asb*3K`uA;z7Vt{FrSi+C|6pXzFO3>nA+AnQ$Q zxN&)7iFxgXWi=*}l*o-h9ALTh%b_BraeBw)3PJa}xVST04S>ValA6sD`B4wUW0Yjp zXf(u`>;0i}tfrRMXRecW;v{jH)x*~yH~p;xb@ws+;jT;BshQ5PZ);b)rLxd5IW^KFH$vWedw=d^u!9g*O_PJ3B4{Y?PJgbF&gDo zeq22q1*C?03;p7ifwWiLJ{ixWHdL@SB~dGq64v&5HunHZme(t<{F=8k8I^8fad7}k z@Z7x1aZl&NJodP|^QVGN??2v`ly1)&SEpEsBu4kS03rs^f<-L>TwKFumg4aTa&Y_Y zm-gs!Lb&aCAsRfmnKRpoz?4h&7ldl;l&kmxL`)@$QWQJIuU}ihbsY72Z$l*)MtBHl z04K7bLb4O_HHyFosTIE|m6rua0R-e`s)ULA`zz5bl(?0FlSX?@>)FN_u{WNCU3>S6 zFzA-U1=p{qacS_Osq^P+?Y_MU*C#@n&X{f3+LCN7*=JugontyKxh+NChfBj1^;Ge<6ZT+&A4Lrb+ zZfB&|*as4x0>zY2vCMZt;FfJSmBJsD5CN|`XHM=9=lrWjQ7Wz)-tpy%Kf?M#I4W$` z-&$m(H|+4}J5QeM{d8dg5MlC`f4+fL>m5?0G4xx9|Ih;5Khl)02OX6`?xBMRg%;Ri z`TGs;iGKub_|TOC-`T~b)9PM<1GM!rx;mlvVCh$7?LBm;DM1VokBy3vNxY!ITA#V7 zf2!pYGnLtArb~)5r|rb}IEbMa2}MUc+}hVJ9L8WF(ieA5ysRe5A1=SbPCD+l9P+oV5dHVP<`A3>?0Y0>`A_PI%W|IdO;72g> zRRl4(y1|dkfaY^@bNx)Is(?d%!HS?O-$Lhwo<6Ms<4Fx@5e)Op21ub<6zzzkj;z^R=tGeB^c%63Qv3MyuS%dLrq7u(fy?zomr=!DEMm3-o@^`btnGS(w~}@GR&o#=?CnPZrG|0B{y01QWR!hcD}}SGE2k}o>Wbyd zLx7|%5kI)d(NU;C+Uly|{m|8R15X(4RcF*ducGQhVC1SPD|7#ADZPr}J%E{xTlFk1 z_*h;L5CHh%shs!MeVq;j=OvqwG=MJ_2eq?xSF+s1vQSpodR2cXVhw?JXa?Ns-!B!T zDeEEfG$y%1%0sYfVy^|42FYAro-T+hv#;nTx?Q9)w-M$!@va^XEe_# zXr&AokgSkV&d#q*?sc&*yIp1Ve@f|HUDgFR$Y$~tIs?hCNYG~?T(eG0%Gn2nO+mVz zlm=s>{^<;Ux&KA{|BlYPbk7_xjP>=gH2Gsw@QIqi!JOjPN7)qV0CMo|)8Z|Jnwb^t zI4Rx~-3*SPAI6_}?Y8>*|5YxJCJtt~xY~o}akUE~b_^=2OcvPYx|G-RbW{_kgt!VYX0iuUk`bq~)aZLp2j*VWbF^CXNMc$|}$*9>{0LGtAR zhK9-jpo4N}pa~fHp-@X&ag+!ZZZq23`{w@}lY4KO`%rap*%XuNHga!9=N@@96kf+~ z-@bMdhODpYr%Z{ZRol@n&}2>ThA9pkX1e7)$jKScTOI>(vLLr&rdvyGi6?2(vZa~z znV&%SMq65fgzm}k#@pdhd9&T^?Cb+{X-??xs)+Tn$+Mk0)f{IP!GdFUrA%ef@Ot6e^nm%voAG^w)I)NEYE5z4ObGl5uWsZpWXN z!r334;if;reWxO0ZGqSKmByA=VAG;51%IcH{(Qp5D=^ESWxD+;`^!Q8{R%H^N=|X$go0C+|oA-_s-Ww*`;h046>}r z?=Nk`Pq(AV?jl7uT+uN^b5xXwrxpaAodTT}^0wCw^NkqLZ)}<3Mi5+7#uyg0iqVx_?e^>`AdEx5^8CsBedTKyW&{)K@ zN6GSDl6~RT4{d?s2iV=c;nBg9cnX=Fz&U~Oe;ab=_gU1a&w&Bj8mOzPb{;x(t=a2E zfl=So%~dx+GXcOw&146Hu8_$vG-T~qV5|hK<2d(!H%m>}7xijI!rMvtv=DOhQq|EM zxjey~r6me$@LPo)H`3d8?>aizItVWj{r^p{Nsn?2ZbdhlXmR|SxF!26zq{^6` zHE02>&OXMUI2s$x4AKGH4|1#;{(j_~3l3YBZyrkpujrzH%p$bZ*87h|5<)=zY{6p&^01mQLk!zp`}M0hU%!aR7oy{?(OI$p zxp{mnLW|RLuR-YEQiiJMV)sjfk^FM^?Qc&#($;Py^R8`bVsY6bG=w*ni0?LNYG!8{ zXlt8O0daRTY#~D&?J`pVpMT!UHZd2g zwx7TMG@v1E?Y8aOJ;HG2(ed(7NyHL+FItWGA$!J;c8;Cx{vpRD0{rA;WCgr%s~nW1 zy>N!W;@KvSr+dxJ%v|-f0r%xGjBflCxO0gmeG@x5HS5Lbh^IC6j+NRmXb)hN#rXx6 z+i0#I*eWl3(9#Mrh{%O%5}r;WtP%#fOVww%QzuN=w%oK96#%}QwmIcx zWzUEeMLsLKQnsda$P{OVZ{GY`nzc6UQHl6L;G$;PcpN6@1*vq}}uegqTV@N8C87i@8_awPhy~qs%uH z*95;Qwit%+Xpt5uO^Q_Di0-P4*SNc1%uednMpJNk9F|O-83n)RFwiD`PcHylJ_Pyc zf7`(V^bKKNOH37rb{gRkoE*|v5(o|yDkHk2OoGN9fVY{~Uv+Ul$N}Mj9od0qSdvou_{(DbATXh#|D zT6GzS*hEAxh7OfCf2_NXcba#w_EGT1%OT za!CRGiApqlM$?E&`4r*fA{vtsJ@C|q$jB6%wmnDMxR9%{R)k1z@`fR<>kJta%yBTU z2+dYoo_u1u&7we)0e|0gqPkM`!38_T(z5&REsP86)u+#PX*(6|o2eNZT9fv!r{_H6 zK5v-JpqV$Xqqa6psG9~NUek)vQ=Y)&c90035-_U}@*iKFPmjt>Od1&& z$PmK|O9Tl5Zr+7LF4>IqDm?A`qE+=@`nie=*HPtpzP01p$CAw7`|$?|h9hBMqLIoz)!K05moe2s#r? zKuJhBB%(H}Q!EK&6(W$qTfn-f6BcK&IhL;*}ou}xvcY(H&n&X(C=!H9Wwp(9!6;b9G~R#IJe z`TlLFGbsAH){^f6@Pz0B99Vx|viH2=m2aP!E1MN~)#RJp8yf8EIPvhCBK??6#h*Sc zE%2|a^PtnwD(=@bs=sbCIAOJyD-t66;7gJ4ft4%Ybv;-1=IHSn|ERQa6DCYZ4!%1x zx-Q|GfqLQ@s!! zQMoX%c?+FJKshV%MWdhaFIv!UTYdU|_0>iocuI4Fvr+BsmvpXfpCNS?WUoknfqby6 zK4V^%l%EE(c;~fM;wGMpj{@x8Mu!6bYY&b>I)Y131LR6l!}~dfZj403q@jM?7UnQ4 zeRT%%34iM|7;)4Y*`uluXRQP@TJ=PR{lQF=fp=OChdtZR>ASCKK_+t^H7JS&?MDXc z*nsz2QsD5&Dl4Gw)JBd>RywtZOEOGB5z0NBvf3NkfE-58eE8wf2igcfVZJ= zx4`lv!(yj1$%ueSa9EFcF1|Z=AVq zBhJl10c&wyvgAv{V|2{3v9M@sH}5$7C+c3g zMyg2rwsvWD)RL6yRXhC@sdbArVy5fh_P}&`(RkO}E$6;WhQDa5hQ6M~vrR%r~LLPK00 zmk+!GgJDdxez5&SBG=$1b%Gi^MU~Vec)et{SLunC3|;Yz;1|`uxz>L-cWyH-GM1o> zuuc4EZ69c2v-$XkkC8QN5h9B*c+}a)RN-K&S}@C-$Ap=)s42;aVo@_}97X|yD&Z<< zyZL#%kkLH2OUNe)^P_@ah)#o@q9%e?bVSmWLl}&v1w#z*7W0ZoP3T3jg8I+xn+8gN zA;eeeSiYeoZJe4sdd3VLISPQNR;^jH5U_*ni}6AB{{H@l$`4tDHV)lI%>!@800^R7 zR?j(rnqLGyod5eLcIWcMe5gX-kVPOgCtm->55J@jT>yqFW8lQQDo8pPtWY!b-$C_{ zi6?-#mz)}=6PifY9xuwYcJ09d@4wAUUv%&fv1||VscrmAx8f_^ifIYeRqZ7ic-Jzd{p1C z2e%4)Tch^)u&=qjZ_7`Ue)*^I$H8_=CaCyH4rDfBRo1VWnW~(y$ntx z$Ln4rsxzmFARY_a)3Nh(#C=z#k@LA7G>Lol+i%yf?z#+at_f7ac-7NC|7-!aB=0HN z8H`J-n7R)GM5xjv;_UumO9%_`9D%4hf`&1Hx@f*JXDz3?q5Pvl9r0YVz5T+0|8}N5 z843ru+N7|V0{Gj#CV%a)!|mC>9?E1wf(!3l3f9@}t)4N`rAJ78%1wR+fIw9J%-JwnviAIKk+KEfC8Nz+rqL@JDC z1a36{T<~p&*Of78P5WI@7=E5YCk9idfd#ewdd4#Z5Es4bhj3POKRl%P?0<&;+wgI$ zErLqdf~0)=aFqGD1xnre!bK1+vI8m=)#jD=IWEM7f0Oj-qbJI_f=5>drm7|TUuXep zr6=Jgwh?K>@vGq+Ed_xEkIp@TOKGQ|G0Au_ zqFIL_&A`4L3pz?Jq>2Kq%7&omJGvdp4DJk%hgRdhmiSHP3#6cVzEthDe7Q_7r>tiJ zS%Yi&+8sU>Ra4JQcUrQ-E-dMTahB%OckSGn;c|KWiEA|aqQjBY z&}&&9iMAIv6uBfy5B~dJo+1|I zh8xkV!Xx&(aYvI&vN5Z?sHjIClg0#JWO8w#MxI@NMQxv$)~#DJQd)+${k_-khX93) z@0Rh#!1VWC*F!J*)k{K8Z0zW1({|g0L&cywS=;a?b)T(A6MB&&Hs9{svqwn*l36z> z7VTbHs8{AUyrrm@#d2=O5W;apM10%_d>ol2 zv!B(eh2LlQ-7}tWk4>x~>IkaQ=X++_np3I)2y8~yfHu`~%ou{26e8h7Y%^|L3RKmm z;UvH@XK))LXjCfP>oP+FFxS24$l5o<)saQ-yzRi8p%$Xf`cNIQL5y7DMM4zcQej0x zo}a}J+@9+7YM^l=_kQegAsNH~=A#=30aJ=EN0!%|H4X@nrdd-%qcaO1sE%rI!u_gJ$PU#VaYb zDR4BVA_=xp3PG(!Dxq#OnW4Z{V#v+(dsWqOa<3kiRwv#^4%!D2V>gOp6RszjH%IBz zI)@bfzj6aMd`d2_w?gWJ2}r47Q5PLH2Tg^~+`jGNP?|4<5^{2N_1?56KYw@C$Nuk) z6QWOtjO6w=nU(Bq!iu6MR?xtcDlcCPagY+$mv~Usbr;Uha0r#Objki9!4Qst_Fju! z7@78%c+eqMwQbTmOFZcF8xjxtSB|`YY{dBUXD=9sAT)T8ojq)4Y^3*V4vE;o^J}(7 zrKCO&cBaHFUNyY%o2vcpxGoNPZEt74WO-y`36uJ!aqLz<#4F=GBG*wdEhw$263~`i(ubAn;A4D5oyuo!vUf)|EfT$w=!#$2p%877`DI=5!prsb%a>oCV}Mdu zunw@qt#x&E22a1{M20CO;5UU6g`4a5StHSvu18l&0VD$fWnE!ugMZa%)Rm%E{8F`Q z6r_4t?8yINKWx>iRh>zr{SaYD=bk-p9ZT{&hDRJr2-gc9i#b(CQ;Hj}Vkyjp+7Ran zNZQWrG^b^{ZwsHJRTZrG3f@mdx=IT89ChjOmM#sy*}z5$fyK&Q!IlX$Fwvt&YK!dg&fX;MR!3_1)J3JY6b9?>r`d?sg(;58C9gSw~uU67vr5{LGCYKs?C zbQM+~?-;PBSYOtyOCe1gaFh|iJVkS40|6MmmW~3pz&P0qrHS-_C`7rnQT|<%8FG1f zfea6oibEnr`C-!V0uBj-Ns}T?D^CZL)+T@k$XhO2Lnid%^pc!$iBaUY5Ok1pAi3dv z2P!t1go5zACTs1iS^KjuaaD<$fEUFTY>veXpKkmCAt?FK>F{tr2uk`OFj4Gc+i)Mt zqukF3ZE2r3eDcmq^lZneIU1E77o0G^s%mQQFWQ2 z58pL<*J$k4`0Vf2{qybcul)Cg+KmnLZ?t`5!rXeMU00*iPI>U4GO9xDx^C-s+f-(s zdPJ3_gi2@BFoPi)MDasrqE(*a*)uTe)6SdzbH{}3x^8nE15oYA4yl{Fd<=^T~#d6@nq?6~YdUoFRt;hP)k6)iM8IijG)~xkW$5R*w zG>UT+oAV3TSeSCN?z^||U#=!IipaXy-{&%cwxAT^$2n#6RqFm(;{pPDFXoCw2Mv1k+ynFkp-T(46gYs)Sz}L9hPBP1^b^6N=vbF8zl!EKh~LhPvp+P{+=+Tq z17QvSLY&1Q*2;uO}aI_k&8sEPzmoISk&0gTO@ zIlB>;Qm8WAOH~A|G;o`ONiLoYHucki1LL?=+jQzQrQeme%1R-PGL@~{x63rJJNNIO zn8Hvp0>_-l5tW?tiS3I&(g(EnNc7GK>Y{}oV1mRU+gnym=`WMO z(7VL@%s4Z>jlTYpCsQb@=|EBX=xB@fK&DIB9AdZAlSZGmMLUb2+_9SAn}=|5o1Ow@ zp91+l7z@{>-DoLkIpNpO~eHk+WRkR(^OSBtcv!%N&k|F?5gS6Sxrui~G)I0vJ40ad)>FwKR z3gQBzc9m@T#n%?l7e%~UqHPdRj?PzmG`T$-ml}+4S3o;4KL=e#;ebVV+9*D)*HBY{ znaV-NaYO?jI{hnP${xR{l|&>Vn){E@1bmVfA(EPWfj+P=`+@;Rf!VbL{NKw_NGA6$ zsdXr$l@t&|b$JFHC#adEUVIzp5V&1`ioxSSaN?xN&k@=V(*Zq7?K5Ak zOOHDlD2-e~)8-4p#{v~VAJr=Nl6`PB_r6RNgqc7-HLFXNkdQ@M%Sx+>u#FD>^j4#T z0#k5Pg|Kie=*!z5-@6x{UJiSwfnSq2n1u8-JfQL|sxeR(+$G|itoYT0rfQ8#S9&kZ z>`0_k5dB>H9W%iPh|q zS#RQxQYa_|@NOVeyOLci5I|u9#wN~3+HbodPgwe7hIv=y*J7>|@f^UjO770UDD78U zydHbvM5eL2#v?`XlxKg4@=bJ$6db3|pC5xmK~zT)%*IayN#%?Xa+i{Uj<{%&LBF!*Ov_5U?A_%lDme z_9kVbHiImH5aJ7#qH%ao-X?t%Dj};g)4_K;HuMb`KjMr?e! z@2kD{N*6C~9G-)+i&~PIIic8&HY=$x}J{h>|iKO>O8N;doFHeABZ#ji>$Zdx8XAa%^nUF(zry+xme ztSk+ksW?q22a#4?N|V76L#ERi_D`wzAK>n`bAu2~b8 z?PAKfr>Gu`{eI&zOq59`3xYPgEepLTD%2?{*R9_4Ijkg(5l`nj&F_609ffzF`;bD9 z$xio9RHQl1E}x01tyk!FaP9pud<~P=$dPePx^n~L)B%|(F=WK7Mq{A8N?K!b-h$!{ Z6Mj1Na^Z)c7Ag4OxY3g>&s*4S{BJ!Ar1<~< From 58c6f7eba754e9d51f0483c8bd7ad8a71c11e05b Mon Sep 17 00:00:00 2001 From: William Wong Date: Tue, 29 Jul 2025 05:52:45 +0000 Subject: [PATCH 06/16] Fix ESLint --- packages/middleware/src/private/templateMiddleware.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/middleware/src/private/templateMiddleware.tsx b/packages/middleware/src/private/templateMiddleware.tsx index c5d393b6cd..23ae88d503 100644 --- a/packages/middleware/src/private/templateMiddleware.tsx +++ b/packages/middleware/src/private/templateMiddleware.tsx @@ -121,21 +121,13 @@ type InferenceHelper = { }; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any type InferHandler> = T['~types']['handler']; -// eslint-disable-next-line @typescript-eslint/no-explicit-any type InferHandlerResult> = T['~types']['handlerResult']; -// eslint-disable-next-line @typescript-eslint/no-explicit-any type InferMiddleware> = T['~types']['middleware']; -// eslint-disable-next-line @typescript-eslint/no-explicit-any type InferProps> = T['~types']['props']; -// eslint-disable-next-line @typescript-eslint/no-explicit-any type InferProviderProps> = T['~types']['providerProps']; -// eslint-disable-next-line @typescript-eslint/no-explicit-any type InferProxyProps> = T['~types']['proxyProps']; -// eslint-disable-next-line @typescript-eslint/no-explicit-any type InferRenderer> = T['~types']['renderer']; -// eslint-disable-next-line @typescript-eslint/no-explicit-any type InferRequest> = T['~types']['request']; export default templateMiddleware; From 138146b19123409b016922aff388d4b7a751a0f5 Mon Sep 17 00:00:00 2001 From: William Wong Date: Mon, 11 Aug 2025 23:55:33 +0000 Subject: [PATCH 07/16] Fix test --- ...usManagement.disableHeroCard.obsolete.html | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/__tests__/html/focusManagement.disableHeroCard.obsolete.html b/__tests__/html/focusManagement.disableHeroCard.obsolete.html index 9f8ec98ede..cd667bd124 100644 --- a/__tests__/html/focusManagement.disableHeroCard.obsolete.html +++ b/__tests__/html/focusManagement.disableHeroCard.obsolete.html @@ -1,4 +1,4 @@ - + @@ -15,7 +15,8 @@ From bebeba332fcfc6aa6f69e59c2789e54f9c605924 Mon Sep 17 00:00:00 2001 From: William Wong Date: Tue, 12 Aug 2025 08:25:15 +0000 Subject: [PATCH 15/16] Group activities should invalidate callback on style options change --- .../providers/GroupActivities/GroupActivitiesComposer.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/api/src/providers/GroupActivities/GroupActivitiesComposer.tsx b/packages/api/src/providers/GroupActivities/GroupActivitiesComposer.tsx index 16d843eb46..e90bed6b71 100644 --- a/packages/api/src/providers/GroupActivities/GroupActivitiesComposer.tsx +++ b/packages/api/src/providers/GroupActivities/GroupActivitiesComposer.tsx @@ -64,14 +64,15 @@ function GroupActivitiesComposer({ children, groupActivitiesMiddleware }: GroupA [groupActivitiesBy, runMiddleware] ); - const groupActivitiesByGroupRef = useRefFrom(groupActivitiesByGroup); const groupActivitiesByRef = useRefFrom(groupActivitiesBy); + // When `groupActivitiesMiddleware` or `styleOptions.groupActivities` changed, the callback should be invalidated. + // The invalidation should cause downstreamers to re-render. const groupActivitiesByName = useCallback< (activities: readonly WebChatActivity[], groupingName: string) => readonly (readonly WebChatActivity[])[] >( (activities, groupingName) => { - const group = groupActivitiesByGroupRef.current.get(groupingName); + const group = groupActivitiesByGroup.get(groupingName); if (group) { const result: ReadonlyMap = new Map( @@ -97,7 +98,7 @@ function GroupActivitiesComposer({ children, groupActivitiesMiddleware }: GroupA return Object.freeze(activities.map(activity => Object.freeze([activity]))); }, - [groupActivitiesByGroupRef, groupActivitiesByRef] + [groupActivitiesByGroup, groupActivitiesByRef] ); const context = useMemo( From df90db2bc060f7be2f571e6f183f42174ac6d3a3 Mon Sep 17 00:00:00 2001 From: William Wong Date: Tue, 12 Aug 2025 08:51:24 +0000 Subject: [PATCH 16/16] Add barrel file for Webpack4 --- packages/middleware/legacy.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/middleware/legacy.js diff --git a/packages/middleware/legacy.js b/packages/middleware/legacy.js new file mode 100644 index 0000000000..59ad0bafbf --- /dev/null +++ b/packages/middleware/legacy.js @@ -0,0 +1,3 @@ +// This is required for Webpack 4 which does not support named exports. +// eslint-disable-next-line no-undef +module.exports = require('./dist/botframework-webchat-middleware.legacy.js');