@ekanos/sdk 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -33
- package/api-report.md +7 -3
- package/dist/components/base-activation-dialog.d.ts +15 -5
- package/dist/components/base-activation-dialog.js +31 -5
- package/dist/components/base-activation-dialog.js.map +1 -1
- package/dist/components/base-activation-form.d.ts +20 -5
- package/dist/components/base-activation-form.js +12 -5
- package/dist/components/base-activation-form.js.map +1 -1
- package/dist/components/base-marketplace-tile.js +12 -10
- package/dist/components/base-marketplace-tile.js.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/oauth-activation-form.d.ts +23 -16
- package/dist/components/oauth-activation-form.js +34 -20
- package/dist/components/oauth-activation-form.js.map +1 -1
- package/dist/components/widgets/use-relative-time.d.ts +7 -0
- package/dist/components/widgets/use-relative-time.js +55 -0
- package/dist/components/widgets/use-relative-time.js.map +1 -0
- package/dist/components/widgets/widget-ask-bar.js.map +1 -1
- package/dist/components/widgets/widget-context.d.ts +21 -0
- package/dist/components/widgets/widget-context.js.map +1 -1
- package/dist/components/widgets/widget-preview-provider.d.ts +1 -1
- package/dist/components/widgets/widget-preview-provider.js +1 -1
- package/dist/components/widgets/widget-preview-provider.js.map +1 -1
- package/dist/components/widgets/widget-state-components.d.ts +4 -1
- package/dist/components/widgets/widget-state-components.js +6 -4
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/dist/components/widgets/widget.d.ts +47 -2
- package/dist/components/widgets/widget.js +111 -23
- package/dist/components/widgets/widget.js.map +1 -1
- package/dist/context/egress.js.map +1 -1
- package/dist/context/storage-validation.d.ts +24 -4
- package/dist/context/storage-validation.js +5 -2
- package/dist/context/storage-validation.js.map +1 -1
- package/dist/hooks/ask-assistant-context.js.map +1 -1
- package/dist/hooks/fetch-integration-storage.js.map +1 -1
- package/dist/hooks/partner-oauth.js +3 -0
- package/dist/hooks/partner-oauth.js.map +1 -1
- package/dist/hooks/use-integration-action.js.map +1 -1
- package/dist/hooks/use-oauth-connection-status.d.ts +78 -78
- package/dist/hooks/use-oauth-connection-status.js +17 -10
- package/dist/hooks/use-oauth-connection-status.js.map +1 -1
- package/dist/hooks/widget-event-context.js.map +1 -1
- package/dist/integration/event-surfaces.js.map +1 -1
- package/dist/integration/index.d.ts +2 -2
- package/dist/integration/index.js +1 -1
- package/dist/integration/index.js.map +1 -1
- package/dist/testing/invoke.js +9 -5
- package/dist/testing/invoke.js.map +1 -1
- package/dist/testing/mock-context.js +8 -7
- package/dist/testing/mock-context.js.map +1 -1
- package/dist/testing/mock-fetch-provider.js.map +1 -1
- package/dist/types/integration.d.ts +27 -21
- package/dist/types/integration.js.map +1 -1
- package/dist/types/widget-ask-context.d.ts +9 -0
- package/dist/types/widget-ask-context.js.map +1 -1
- package/dist/types/workspace-target.d.ts +3 -2
- package/dist/types/workspace-target.js.map +1 -1
- package/eslint.preset.mjs +9 -8
- package/package.json +37 -41
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget.js","sourceRoot":"","sources":["../../../src/components/widgets/widget.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { Component, type ReactNode, use, useMemo } from 'react';\n\nimport { Button } from '@ekanos/ui/button';\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@ekanos/ui/tooltip';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { useAnimatedHeight } from './use-animated-height';\nimport { WidgetAskBar } from './widget-ask-bar';\nimport { WidgetContext, type WidgetRenderState } from './widget-context';\nimport {\n WidgetDisabledState,\n WidgetErrorState,\n WidgetInactiveState,\n WidgetLoadingState,\n} from './widget-state-components';\n\n// ─── Error Boundary ─────────────────────────────────────────────────────────\n\nclass WidgetErrorBoundary extends Component<\n { widgetId: string; children: ReactNode },\n { hasError: boolean }\n> {\n state = { hasError: false };\n static getDerivedStateFromError() {\n return { hasError: true };\n }\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n console.error(`Widget \"${this.props.widgetId}\" crashed:`, error, info);\n }\n render() {\n if (this.state.hasError) {\n return (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-3 p-8\">\n <div className=\"bg-destructive/10 flex h-16 w-16 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"text-destructive h-8 w-8\"\n />\n </div>\n <p className=\"text-muted-foreground text-base font-medium\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedTitle\"\n defaults=\"Something went wrong\"\n />\n </p>\n <p className=\"text-muted-foreground max-w-xs text-center text-sm\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedBody\"\n defaults=\"This widget encountered an error. Try refreshing the page.\"\n />\n </p>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\n// ─── Hook ───────────────────────────────────────────────────────────────────\n\nfunction useWidget() {\n const ctx = use(WidgetContext);\n if (!ctx) {\n throw new Error(\n 'Widget.* compound components must be rendered inside a widget provider. ' +\n 'On the Fusion dashboard the host supplies one. Anywhere else — your own ' +\n 'app, a story, a component test — wrap the widget in ' +\n \"`WidgetPreviewProvider` from '@ekanos/sdk/components'.\",\n );\n }\n return ctx;\n}\n\n// ─── Card ───────────────────────────────────────────────────────────────────\n\nfunction WidgetCard({ children }: { children: ReactNode }) {\n const { meta } = useWidget();\n return (\n <Card\n className={cn(\n // Neutralize the shadcn card's own py-6/gap-6 box model — widget\n // chrome owns all vertical spacing via its header/content/footer.\n 'shadow-widget relative flex h-full flex-col gap-0 overflow-hidden rounded-lg border-0 py-0',\n meta.className,\n )}\n data-widget-id={meta.widgetId}\n >\n {children}\n </Card>\n );\n}\n\n// ─── Header ─────────────────────────────────────────────────────────────────\n\nfunction CollapseButton() {\n const { state, actions, meta } = useWidget();\n return (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={actions.toggleCollapsed}\n disabled={state.pendingCollapse}\n className=\"bg-background dark:bg-secondary relative h-6 w-6 rounded-full before:absolute before:top-1/2 before:left-1/2 before:h-11 before:w-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\"\n aria-expanded={!state.collapsed}\n aria-controls={`widget-content-${meta.widgetId}`}\n aria-label={\n state.collapsed\n ? `Expand ${meta.title ?? 'widget'}`\n : `Collapse ${meta.title ?? 'widget'}`\n }\n >\n <Icon\n name=\"fa-light fa-chevron-right\"\n className={cn(\n 'h-4 w-4 transition-transform duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n !state.collapsed && 'rotate-90',\n )}\n />\n </Button>\n );\n}\n\nfunction WidgetHeader({\n title,\n subtitle,\n description,\n children,\n}: {\n /** Override the provider's title — useful when the rendered name differs from the config name. */\n title?: string;\n /** Override the provider's subtitle — useful for widget-computed values. */\n subtitle?: string;\n /** Override the provider's description — useful for widget-computed values. */\n description?: string;\n children?: ReactNode;\n}) {\n const { meta } = useWidget();\n const effectiveTitle = title ?? meta.title;\n const effectiveSubtitle = subtitle ?? meta.subtitle;\n const effectiveDescription = description ?? meta.description;\n if (!effectiveTitle) return null;\n return (\n <CardHeader className=\"flex-shrink-0 border-b pt-6 pb-4 [.border-b]:pb-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n {meta.isCollapsible ? <CollapseButton /> : null}\n {effectiveDescription ? (\n <>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger\n render={\n <CardTitle className=\"cursor-help text-lg font-semibold\" />\n }\n >\n {effectiveTitle}\n </TooltipTrigger>\n <TooltipContent>\n <p>{effectiveDescription}</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n <span className=\"sr-only\">{effectiveDescription}</span>\n </>\n ) : (\n <CardTitle className=\"text-lg font-semibold\">\n {effectiveTitle}\n </CardTitle>\n )}\n {effectiveSubtitle && (\n <span className=\"text-muted-foreground text-base font-medium\">\n {effectiveSubtitle}\n </span>\n )}\n </div>\n {children && (\n <div className=\"flex shrink-0 items-center gap-2\">{children}</div>\n )}\n </div>\n </CardHeader>\n );\n}\n\n// ─── State-gated content ────────────────────────────────────────────────────\n\nfunction CollapsibleBody({\n collapsed,\n maxContentHeight,\n widgetId,\n children,\n}: {\n collapsed: boolean;\n maxContentHeight?: string;\n widgetId: string;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn(\n 'grid min-h-0 flex-1 transition-[grid-template-rows] duration-300 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n collapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]',\n )}\n >\n <div className=\"min-h-0 overflow-hidden\">\n <CardContent\n id={`widget-content-${widgetId}`}\n className={cn(\n 'flex h-full min-h-0 flex-1 flex-col overflow-y-auto p-0',\n 'transition-opacity duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n collapsed ? 'opacity-0' : 'opacity-100',\n )}\n style={maxContentHeight ? { maxHeight: maxContentHeight } : undefined}\n >\n {children}\n </CardContent>\n </div>\n </div>\n );\n}\n\nfunction StaticBody({\n maxContentHeight,\n children,\n}: {\n maxContentHeight?: string;\n children: ReactNode;\n}) {\n const { innerRef, height } = useAnimatedHeight();\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n <div\n className=\"overflow-hidden transition-[height] duration-250 ease-out motion-reduce:transition-none\"\n style={{\n height: height === 'auto' ? ('auto' as const) : height,\n maxHeight: maxContentHeight ?? undefined,\n }}\n >\n <div ref={innerRef}>{children}</div>\n </div>\n </CardContent>\n );\n}\n\n/**\n * The standard padded body for content rendered inside `Widget.Active`.\n *\n * `Widget.Active`'s own `CardContent` renders `p-0` — deliberately, so a\n * full-bleed chart or table can run edge-to-edge inside the card. That means\n * ordinary content (text, rows, a KPI readout) is left flush against the\n * card's sides while `Widget.Header` stays inset (`px-5`) unless the body\n * supplies its own padding. `Widget.Body` is that padding: `px-5` matches\n * the header's horizontal inset so header and body align, `pt-4 pb-5`\n * balances the header's `pb-4`/card's bottom edge.\n *\n * Use it for any widget whose content is NOT already full-bleed:\n *\n * ```tsx\n * <Widget.Active>\n * <Widget.Body>{/* rows, text, a KPI readout *\\/}</Widget.Body>\n * </Widget.Active>\n * ```\n *\n * Skip it — render children directly inside `Widget.Active` — when the\n * content itself needs to run flush to the card edges (a chart, a table with\n * its own header row, an image).\n */\nfunction WidgetBody({\n className,\n children,\n}: {\n className?: string;\n children: ReactNode;\n}) {\n return (\n <div className={cn('flex flex-1 flex-col gap-4 px-5 pt-4 pb-5', className)}>\n {children}\n </div>\n );\n}\n\n/**\n * Renders `children` once the widget's state is `active`. Intentionally\n * flush: its `CardContent` renders `p-0` (no horizontal/vertical inset) so a\n * chart, table, or other full-bleed content can run edge-to-edge — existing\n * widgets depend on this. For ordinary padded content, wrap `children` in\n * `Widget.Body` rather than hand-rolling a `px-*` wrapper.\n */\nfunction WidgetActive({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n const body = (\n <WidgetErrorBoundary widgetId={meta.widgetId}>\n {children}\n </WidgetErrorBoundary>\n );\n return meta.isCollapsible ? (\n <CollapsibleBody\n collapsed={state.collapsed}\n maxContentHeight={meta.maxContentHeight}\n widgetId={meta.widgetId}\n >\n {body}\n </CollapsibleBody>\n ) : (\n <StaticBody maxContentHeight={meta.maxContentHeight}>{body}</StaticBody>\n );\n}\n\nfunction WidgetLoading({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'loading') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetLoadingState />}\n </CardContent>\n );\n}\n\nfunction WidgetError({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'error') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetErrorState />}\n </CardContent>\n );\n}\n\nfunction WidgetInactive({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'inactive') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetInactiveState />}\n </CardContent>\n );\n}\n\nfunction WidgetDisabled({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'disabled') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetDisabledState />}\n </CardContent>\n );\n}\n\n// ─── Footers ────────────────────────────────────────────────────────────────\n\nfunction WidgetFooter({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n return (\n <CardFooter\n className={cn(\n 'flex-shrink-0 pb-6 transition-opacity duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n meta.isCollapsible && state.collapsed\n ? 'pointer-events-none opacity-0'\n : 'opacity-100',\n )}\n aria-hidden={meta.isCollapsible && state.collapsed}\n >\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetActions({ children }: { children: ReactNode }) {\n return (\n <CardFooter className=\"bg-muted flex-shrink-0 justify-end border-t py-3\">\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetAskFooter({ context }: { context?: WidgetAskContext }) {\n const { state, meta } = useWidget();\n const effectiveContext = context ?? meta.askContext;\n const shouldShow =\n (meta.aiFooterEnabled || !!effectiveContext) &&\n state.state === 'active' &&\n !state.collapsed;\n if (!shouldShow) return null;\n return (\n <WidgetAskBar\n context={\n effectiveContext ?? {\n widgetId: meta.widgetId,\n title: meta.title ?? meta.widgetId,\n snapshot: null,\n suggestedPrompt: `Tell me about ${meta.title ?? 'this widget'}.`,\n }\n }\n />\n );\n}\n\n/**\n * Renders a subtle \"data may be stale\" banner when the widget's integration is\n * `degraded` (but still active). Unhealthy integrations route to `disabled`\n * instead, so this only fires for the degraded middle ground.\n */\nfunction WidgetHealthFooter() {\n const { state, meta } = useWidget();\n const shouldShow =\n state.state === 'active' &&\n !state.collapsed &&\n meta.health?.status === 'degraded';\n if (!shouldShow) return null;\n return (\n <div className=\"text-warning border-warning/30 bg-warning/5 flex items-center gap-2 border-t px-4 py-2 text-xs\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"h-4 w-4 shrink-0\"\n aria-hidden\n />\n <span>\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNotice\"\n defaults=\"This integration is degraded — data may be stale.\"\n />\n </span>\n </div>\n );\n}\n\n// ─── Data state override ────────────────────────────────────────────────────\n\n/**\n * Wraps children in a sub-provider that overrides the effective render state\n * with the widget's own data loading / error state. Only overrides when the\n * parent state is `active` — so the integration's `inactive` / `disabled` /\n * `loading` (initial) states still win.\n *\n * Read the parent integration state BEFORE wrapping in this component if you\n * need it to gate data fetching:\n *\n * ```tsx\n * const { state } = use(WidgetContext);\n * const integrationActive = state.state === 'active';\n * const { isLoading, isError } = useMyData(accountId, integrationActive);\n * return (\n * <Widget.DataState loading={isLoading} error={isError}>\n * <Widget.Card>…</Widget.Card>\n * </Widget.DataState>\n * );\n * ```\n */\nfunction WidgetDataState({\n loading,\n error,\n inactive,\n children,\n}: {\n loading?: boolean;\n error?: boolean;\n /**\n * Force the `inactive` render state even though the integration itself is\n * active. Use for widget-level \"not connected\" states (e.g. the product is\n * activated but the upstream OAuth connection is missing). Takes priority\n * over `loading` / `error`.\n */\n inactive?: boolean;\n children: ReactNode;\n}) {\n const parent = useWidget();\n const overrideState: WidgetRenderState =\n parent.state.state !== 'active'\n ? parent.state.state\n : inactive\n ? 'inactive'\n : loading\n ? 'loading'\n : error\n ? 'error'\n : 'active';\n const value = useMemo(\n () => ({\n state: { ...parent.state, state: overrideState },\n actions: parent.actions,\n meta: parent.meta,\n }),\n [parent.state, parent.actions, parent.meta, overrideState],\n );\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n\n// ─── Compound export ────────────────────────────────────────────────────────\n\nexport const Widget = {\n Card: WidgetCard,\n Header: WidgetHeader,\n DataState: WidgetDataState,\n Active: WidgetActive,\n Body: WidgetBody,\n Loading: WidgetLoading,\n Error: WidgetError,\n Inactive: WidgetInactive,\n Disabled: WidgetDisabled,\n Footer: WidgetFooter,\n Actions: WidgetActions,\n AskFooter: WidgetAskFooter,\n HealthFooter: WidgetHealthFooter,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"widget.js","sourceRoot":"","sources":["../../../src/components/widgets/widget.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n Component,\n type CSSProperties,\n type ReactNode,\n use,\n useMemo,\n} from 'react';\n\nimport Link from 'next/link.js';\n\nimport { Button } from '@ekanos/ui/button';\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@ekanos/ui/dropdown-menu';\nimport { Icon } from '@ekanos/ui/icon';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@ekanos/ui/tooltip';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { useAnimatedHeight } from './use-animated-height';\nimport { useRelativeTime } from './use-relative-time';\nimport { WidgetAskBar } from './widget-ask-bar';\nimport { WidgetContext, type WidgetRenderState } from './widget-context';\nimport {\n WidgetDisabledState,\n WidgetErrorState,\n WidgetInactiveState,\n WidgetLoadingState,\n} from './widget-state-components';\n\n// ─── Error Boundary ─────────────────────────────────────────────────────────\n\nclass WidgetErrorBoundary extends Component<\n { widgetId: string; onRetry?: () => void; children: ReactNode },\n { hasError: boolean }\n> {\n state = { hasError: false };\n static getDerivedStateFromError() {\n return { hasError: true };\n }\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n console.error(`Widget \"${this.props.widgetId}\" crashed:`, error, info);\n }\n retry = () => {\n this.props.onRetry?.();\n this.setState({ hasError: false });\n };\n render() {\n if (this.state.hasError) {\n return (\n <div\n role=\"alert\"\n className=\"flex flex-1 flex-col items-center justify-center gap-3 px-5 py-8 text-center\"\n >\n <div className=\"bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"text-destructive h-5 w-5\"\n aria-hidden\n />\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-foreground text-sm font-medium\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedTitle\"\n defaults=\"This widget ran into a problem\"\n />\n </p>\n <p className=\"text-muted-foreground max-w-xs text-sm\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedBody\"\n defaults=\"Try again. If it keeps happening, reload the page.\"\n />\n </p>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={this.retry}\n data-test=\"widget-crash-retry\"\n >\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className=\"h-4 w-4\"\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRetry\" defaults=\"Try again\" />\n </Button>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\n// ─── Hook ───────────────────────────────────────────────────────────────────\n\nfunction useWidget() {\n const ctx = use(WidgetContext);\n if (!ctx) {\n throw new Error(\n 'Widget.* compound components must be rendered inside a widget provider. ' +\n 'On the Ekanos dashboard the host supplies one. Anywhere else — your own ' +\n 'app, a story, a component test — wrap the widget in ' +\n \"`WidgetPreviewProvider` from '@ekanos/sdk/components'.\",\n );\n }\n return ctx;\n}\n\n// ─── Card ───────────────────────────────────────────────────────────────────\n\nfunction WidgetCard({ children }: { children: ReactNode }) {\n const { meta } = useWidget();\n return (\n <Card\n className={cn(\n // Neutralize the shadcn card's own py-6/gap-6 box model — widget\n // chrome owns all vertical spacing via its header/content/footer.\n 'shadow-widget relative flex h-full flex-col gap-0 overflow-hidden rounded-lg border-0 py-0',\n meta.className,\n )}\n data-widget-id={meta.widgetId}\n >\n {children}\n <WidgetFreshness />\n </Card>\n );\n}\n\n// ─── Header ─────────────────────────────────────────────────────────────────\n\nfunction CollapseButton() {\n const { state, actions, meta } = useWidget();\n return (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={actions.toggleCollapsed}\n disabled={state.pendingCollapse}\n className=\"bg-background dark:bg-secondary relative h-6 w-6 rounded-full before:absolute before:top-1/2 before:left-1/2 before:h-11 before:w-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\"\n aria-expanded={!state.collapsed}\n aria-controls={`widget-content-${meta.widgetId}`}\n aria-label={\n state.collapsed\n ? `Expand ${meta.title ?? 'widget'}`\n : `Collapse ${meta.title ?? 'widget'}`\n }\n >\n <Icon\n name=\"fa-light fa-chevron-right\"\n className={cn(\n 'ease-out-strong h-4 w-4 transition-transform duration-200 motion-reduce:transition-none',\n !state.collapsed && 'rotate-90',\n )}\n />\n </Button>\n );\n}\n\nfunction WidgetHeader({\n title,\n subtitle,\n description,\n menu,\n children,\n}: {\n /** Override the provider's title — useful when the rendered name differs from the config name. */\n title?: string;\n /** Override the provider's subtitle — useful for widget-computed values. */\n subtitle?: string;\n /** Override the provider's description — useful for widget-computed values. */\n description?: string;\n /**\n * `Widget.MenuItem`s placed at the top of the header's ⋯ menu. Use it for\n * occasional actions — \"Open in QuickBooks\", \"Open in Outlook\" — and keep\n * `children` for the controls people reach for every visit, like filters.\n */\n menu?: ReactNode;\n children?: ReactNode;\n}) {\n const { meta } = useWidget();\n const effectiveTitle = title ?? meta.title;\n const effectiveSubtitle = subtitle ?? meta.subtitle;\n const effectiveDescription = description ?? meta.description;\n if (!effectiveTitle) return null;\n return (\n <CardHeader className=\"flex-shrink-0 border-b pt-6 pb-4 [.border-b]:pb-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n {meta.isCollapsible ? <CollapseButton /> : null}\n {effectiveDescription ? (\n <>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger\n render={\n <CardTitle className=\"cursor-help text-lg font-semibold\" />\n }\n >\n {effectiveTitle}\n </TooltipTrigger>\n <TooltipContent>\n <p>{effectiveDescription}</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n <span className=\"sr-only\">{effectiveDescription}</span>\n </>\n ) : (\n <CardTitle className=\"text-lg font-semibold\">\n {effectiveTitle}\n </CardTitle>\n )}\n {effectiveSubtitle && (\n <span className=\"text-muted-foreground text-base font-medium\">\n {effectiveSubtitle}\n </span>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {children}\n <WidgetMenu items={menu} />\n </div>\n </div>\n </CardHeader>\n );\n}\n\n/**\n * The actions every widget shares, whatever it renders: open its full\n * surface, reload it, pin or unpin it. Each item needs something from the host\n * or the widget's data layer, so the menu shows only what applies — and\n * nothing at all when nothing does.\n */\nfunction WidgetMenu({ items }: { items?: ReactNode }) {\n const { state, actions, meta, data } = useWidget();\n const refresh = state.state === 'active' ? data?.refresh : undefined;\n const hasNavigation = !!items || !!meta.openHref || !!refresh;\n\n if (!hasNavigation && !meta.isPinnable) return null;\n\n const title = meta.title ?? 'widget';\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n aria-label={`Actions for ${title}`}\n data-test={`widget-menu-${meta.widgetId}`}\n />\n }\n >\n <Icon name=\"fa-light fa-ellipsis\" className=\"h-4 w-4\" aria-hidden />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-48\">\n {items}\n {meta.openHref ? (\n <DropdownMenuItem render={<Link href={meta.openHref} />}>\n <Icon name=\"fa-light fa-arrow-up-right-from-square\" aria-hidden />\n <Trans\n i18nKey=\"dashboard:widgetOpenInWorkspace\"\n defaults=\"Open in workspace\"\n />\n </DropdownMenuItem>\n ) : null}\n {refresh ? (\n <DropdownMenuItem onClick={refresh} disabled={data?.refreshing}>\n <Icon name=\"fa-light fa-arrows-rotate\" aria-hidden />\n <Trans i18nKey=\"dashboard:widgetRefresh\" defaults=\"Refresh\" />\n </DropdownMenuItem>\n ) : null}\n {hasNavigation && meta.isPinnable ? <DropdownMenuSeparator /> : null}\n {meta.isPinnable ? (\n <DropdownMenuItem\n onClick={actions.togglePinned}\n disabled={state.pendingPin}\n data-test={`widget-menu-pin-${meta.widgetId}`}\n >\n <Icon\n name={\n state.pinned\n ? 'fa-light fa-thumbtack-slash'\n : 'fa-light fa-thumbtack'\n }\n aria-hidden\n />\n {state.pinned ? (\n <Trans\n i18nKey=\"dashboard:widgetUnpin\"\n defaults=\"Unpin from board\"\n />\n ) : (\n <Trans i18nKey=\"dashboard:widgetPin\" defaults=\"Pin to board\" />\n )}\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/**\n * One widget-specific entry in the header's ⋯ menu; pass it through\n * `Widget.Header`'s `menu` prop. With `href` it is a link, opened in a new tab\n * when `external`; otherwise it runs `onSelect`.\n */\nfunction WidgetMenuItem({\n icon,\n href,\n external = false,\n onSelect,\n disabled,\n children,\n}: {\n /** A Font Awesome class, e.g. `fa-light fa-envelope`. */\n icon?: string;\n href?: string;\n external?: boolean;\n onSelect?: () => void;\n disabled?: boolean;\n children: ReactNode;\n}) {\n const link = !href ? undefined : external ? (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" />\n ) : (\n <Link href={href} />\n );\n\n return (\n <DropdownMenuItem render={link} onClick={onSelect} disabled={disabled}>\n <Icon\n name={\n icon ??\n (external\n ? 'fa-light fa-arrow-up-right-from-square'\n : 'fa-light fa-arrow-right')\n }\n aria-hidden\n />\n {children}\n </DropdownMenuItem>\n );\n}\n\n/**\n * \"Updated 3 minutes ago\" under an active widget whose data layer reports\n * when it last loaded (see `Widget.DataState`'s `updatedAt`).\n */\nfunction WidgetFreshness() {\n const { state, data } = useWidget();\n const updated = useRelativeTime(data?.updatedAt);\n\n if (state.state !== 'active' || state.collapsed || !updated) return null;\n\n return (\n <div\n className=\"text-muted-foreground border-border/60 flex shrink-0 items-center gap-1.5 border-t px-5 py-2.5 text-xs\"\n aria-live=\"polite\"\n >\n {data?.refreshing ? (\n <>\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className=\"h-3 w-3 animate-spin\"\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRefreshing\" defaults=\"Refreshing…\" />\n </>\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetUpdated\"\n defaults=\"Updated {{time}}\"\n values={{ time: updated }}\n />\n )}\n </div>\n );\n}\n\n// ─── State-gated content ────────────────────────────────────────────────────\n\nfunction CollapsibleBody({\n collapsed,\n maxContentHeight,\n widgetId,\n children,\n}: {\n collapsed: boolean;\n maxContentHeight?: string;\n widgetId: string;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn(\n 'ease-out-strong grid min-h-0 flex-1 transition-all duration-300 motion-reduce:transition-none',\n collapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]',\n )}\n >\n <div className=\"min-h-0 overflow-hidden\">\n <CardContent\n id={`widget-content-${widgetId}`}\n className={cn(\n 'flex h-full min-h-0 flex-1 flex-col overflow-y-auto p-0',\n 'ease-out-strong transition-opacity duration-200 motion-reduce:transition-none',\n collapsed ? 'opacity-0' : 'opacity-100',\n maxContentHeight && 'max-h-(--widget-max-height)',\n )}\n style={\n maxContentHeight\n ? ({ '--widget-max-height': maxContentHeight } as CSSProperties)\n : undefined\n }\n >\n {children}\n </CardContent>\n </div>\n </div>\n );\n}\n\nfunction StaticBody({\n maxContentHeight,\n children,\n}: {\n maxContentHeight?: string;\n children: ReactNode;\n}) {\n const { innerRef, height } = useAnimatedHeight();\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n <div\n className={cn(\n 'h-(--body-height) overflow-hidden transition-all duration-250 ease-out motion-reduce:transition-none',\n maxContentHeight && 'max-h-(--widget-max-height)',\n )}\n style={\n {\n '--body-height': height === 'auto' ? 'auto' : `${height}px`,\n '--widget-max-height': maxContentHeight,\n } as CSSProperties\n }\n >\n <div ref={innerRef}>{children}</div>\n </div>\n </CardContent>\n );\n}\n\n/**\n * The standard padded body for content rendered inside `Widget.Active`.\n *\n * `Widget.Active`'s own `CardContent` renders `p-0` — deliberately, so a\n * full-bleed chart or table can run edge-to-edge inside the card. That means\n * ordinary content (text, rows, a KPI readout) is left flush against the\n * card's sides while `Widget.Header` stays inset (`px-5`) unless the body\n * supplies its own padding. `Widget.Body` is that padding: `px-5` matches\n * the header's horizontal inset so header and body align, `pt-4 pb-5`\n * balances the header's `pb-4`/card's bottom edge.\n *\n * Use it for any widget whose content is NOT already full-bleed:\n *\n * ```tsx\n * <Widget.Active>\n * <Widget.Body>{/* rows, text, a KPI readout *\\/}</Widget.Body>\n * </Widget.Active>\n * ```\n *\n * Skip it — render children directly inside `Widget.Active` — when the\n * content itself needs to run flush to the card edges (a chart, a table with\n * its own header row, an image).\n */\nfunction WidgetBody({\n className,\n children,\n}: {\n className?: string;\n children: ReactNode;\n}) {\n return (\n <div className={cn('flex flex-1 flex-col gap-4 px-5 pt-4 pb-5', className)}>\n {children}\n </div>\n );\n}\n\n/**\n * Renders `children` once the widget's state is `active`. Intentionally\n * flush: its `CardContent` renders `p-0` (no horizontal/vertical inset) so a\n * chart, table, or other full-bleed content can run edge-to-edge — existing\n * widgets depend on this. For ordinary padded content, wrap `children` in\n * `Widget.Body` rather than hand-rolling a `px-*` wrapper.\n */\nfunction WidgetActive({ children }: { children: ReactNode }) {\n const { state, meta, data } = useWidget();\n if (state.state !== 'active') return null;\n const body = (\n <WidgetErrorBoundary widgetId={meta.widgetId} onRetry={data?.refresh}>\n {children}\n </WidgetErrorBoundary>\n );\n return meta.isCollapsible ? (\n <CollapsibleBody\n collapsed={state.collapsed}\n maxContentHeight={meta.maxContentHeight}\n widgetId={meta.widgetId}\n >\n {body}\n </CollapsibleBody>\n ) : (\n <StaticBody maxContentHeight={meta.maxContentHeight}>{body}</StaticBody>\n );\n}\n\nfunction WidgetLoading({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'loading') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetLoadingState />}\n </CardContent>\n );\n}\n\nfunction WidgetError({ children }: { children?: ReactNode }) {\n const { state, data } = useWidget();\n if (state.state !== 'error') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? (\n <WidgetErrorState onRetry={data?.refresh} retrying={data?.refreshing} />\n )}\n </CardContent>\n );\n}\n\nfunction WidgetInactive({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'inactive') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetInactiveState />}\n </CardContent>\n );\n}\n\nfunction WidgetDisabled({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'disabled') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetDisabledState />}\n </CardContent>\n );\n}\n\n// ─── Footers ────────────────────────────────────────────────────────────────\n\nfunction WidgetFooter({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n return (\n <CardFooter\n className={cn(\n 'ease-out-strong flex-shrink-0 pb-6 transition-opacity duration-200 motion-reduce:transition-none',\n meta.isCollapsible && state.collapsed\n ? 'pointer-events-none opacity-0'\n : 'opacity-100',\n )}\n aria-hidden={meta.isCollapsible && state.collapsed}\n >\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetActions({ children }: { children: ReactNode }) {\n return (\n <CardFooter className=\"bg-muted flex-shrink-0 justify-end border-t py-3\">\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetAskFooter({ context }: { context?: WidgetAskContext }) {\n const { state, meta } = useWidget();\n const effectiveContext = context ?? meta.askContext;\n const shouldShow =\n (meta.aiFooterEnabled || !!effectiveContext) &&\n state.state === 'active' &&\n !state.collapsed;\n if (!shouldShow) return null;\n return (\n <WidgetAskBar\n context={\n effectiveContext ?? {\n widgetId: meta.widgetId,\n title: meta.title ?? meta.widgetId,\n snapshot: null,\n suggestedPrompt: `Tell me about ${meta.title ?? 'this widget'}.`,\n }\n }\n />\n );\n}\n\n/**\n * Renders a subtle \"data may be stale\" banner when the widget's integration is\n * `degraded` (but still active). Unhealthy integrations route to `disabled`\n * instead, so this only fires for the degraded middle ground.\n */\nfunction WidgetHealthFooter() {\n const { state, meta } = useWidget();\n const lastSuccessAt = toEpochMs(meta.health?.lastSuccessAt);\n const lastSynced = useRelativeTime(lastSuccessAt);\n const shouldShow =\n state.state === 'active' &&\n !state.collapsed &&\n meta.health?.status === 'degraded';\n if (!shouldShow) return null;\n return (\n <div className=\"text-warning border-warning/30 bg-warning/5 flex items-center gap-2 border-t px-4 py-2 text-xs\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"h-4 w-4 shrink-0\"\n aria-hidden\n />\n <span>\n {lastSynced ? (\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNoticeSince\"\n defaults=\"Data may be out of date. Last synced {{time}}.\"\n values={{ time: lastSynced }}\n />\n ) : (\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNotice\"\n defaults=\"This integration is degraded — data may be stale.\"\n />\n )}\n </span>\n </div>\n );\n}\n\nfunction toEpochMs(\n value: number | string | Date | null | undefined,\n): number | undefined {\n if (value === null || value === undefined) return undefined;\n const ms =\n value instanceof Date ? value.getTime() : new Date(value).getTime();\n return Number.isFinite(ms) && ms > 0 ? ms : undefined;\n}\n\n// ─── Data state override ────────────────────────────────────────────────────\n\n/**\n * Wraps children in a sub-provider that overrides the effective render state\n * with the widget's own data loading / error state. Only overrides when the\n * parent state is `active` — so the integration's `inactive` / `disabled` /\n * `loading` (initial) states still win.\n *\n * Read the parent integration state BEFORE wrapping in this component if you\n * need it to gate data fetching:\n *\n * ```tsx\n * const { state } = use(WidgetContext);\n * const integrationActive = state.state === 'active';\n * const { isLoading, isError } = useMyData(accountId, integrationActive);\n * return (\n * <Widget.DataState loading={isLoading} error={isError}>\n * <Widget.Card>…</Widget.Card>\n * </Widget.DataState>\n * );\n * ```\n *\n * Pass `onRefresh`, `refreshing` and `updatedAt` to give the widget the\n * shared Refresh menu item, a Try again button on its error view, and an\n * \"Updated 3 minutes ago\" footer. With TanStack Query:\n *\n * ```tsx\n * <Widget.DataState\n * loading={query.isLoading}\n * error={query.isError}\n * onRefresh={() => void query.refetch()}\n * refreshing={query.isFetching}\n * updatedAt={query.dataUpdatedAt}\n * >\n * ```\n */\nfunction WidgetDataState({\n loading,\n error,\n inactive,\n onRefresh,\n refreshing = false,\n updatedAt,\n children,\n}: {\n loading?: boolean;\n error?: boolean;\n /**\n * Force the `inactive` render state even though the integration itself is\n * active. Use for widget-level \"not connected\" states (e.g. the product is\n * activated but the upstream OAuth connection is missing). Takes priority\n * over `loading` / `error`.\n */\n inactive?: boolean;\n /** Reloads the widget's data. */\n onRefresh?: () => void;\n /** A reload is in flight. */\n refreshing?: boolean;\n /**\n * When the data on screen last loaded successfully: epoch milliseconds, an\n * ISO string or a `Date`. `0` means never, which TanStack Query's\n * `dataUpdatedAt` reports before the first success.\n */\n updatedAt?: number | string | Date | null;\n children: ReactNode;\n}) {\n const parent = useWidget();\n const overrideState: WidgetRenderState =\n parent.state.state !== 'active'\n ? parent.state.state\n : inactive\n ? 'inactive'\n : loading\n ? 'loading'\n : error\n ? 'error'\n : 'active';\n const updatedAtMs = toEpochMs(updatedAt);\n const reportsData = onRefresh !== undefined || updatedAtMs !== undefined;\n const value = useMemo(\n () => ({\n state: { ...parent.state, state: overrideState },\n actions: parent.actions,\n meta: parent.meta,\n data: reportsData\n ? { updatedAt: updatedAtMs, refreshing, refresh: onRefresh }\n : parent.data,\n }),\n [\n parent.state,\n parent.actions,\n parent.meta,\n parent.data,\n overrideState,\n reportsData,\n updatedAtMs,\n refreshing,\n onRefresh,\n ],\n );\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n\n// ─── Compound export ────────────────────────────────────────────────────────\n\nexport const Widget = {\n Card: WidgetCard,\n Header: WidgetHeader,\n MenuItem: WidgetMenuItem,\n DataState: WidgetDataState,\n Active: WidgetActive,\n Body: WidgetBody,\n Loading: WidgetLoading,\n Error: WidgetError,\n Inactive: WidgetInactive,\n Disabled: WidgetDisabled,\n Footer: WidgetFooter,\n Actions: WidgetActions,\n AskFooter: WidgetAskFooter,\n HealthFooter: WidgetHealthFooter,\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"egress.js","sourceRoot":"","sources":["../../src/context/egress.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,eAAe,GAAG,OAAO,CAAC;AAShC,SAAS,YAAY,CAAC,KAAa,EAAE,MAAc;IACjD,OAAO,IAAI,SAAS,CAClB,2BAA2B,KAAK,iBAAiB,MAAM,IAAI;QACzD,kEAAkE;QAClE,yEAAyE;QACzE,mEAAmE;QACnE,mCAAmC,CACtC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAE3E,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;
|
|
1
|
+
{"version":3,"file":"egress.js","sourceRoot":"","sources":["../../src/context/egress.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,eAAe,GAAG,OAAO,CAAC;AAShC,SAAS,YAAY,CAAC,KAAa,EAAE,MAAc;IACjD,OAAO,IAAI,SAAS,CAClB,2BAA2B,KAAK,iBAAiB,MAAM,IAAI;QACzD,kEAAkE;QAClE,yEAAyE;QACzE,mEAAmE;QACnE,mCAAmC,CACtC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAE3E,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;eAAO,CAAC;QACP,MAAM,YAAY,CAAC,KAAK,EAAE,mCAAmC,CAAC,CAAC;IACjE,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,YAAY,CAChB,KAAK,EACL,gEAAgE,CACjE,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QAC1E,MAAM,YAAY,CAAC,KAAK,EAAE,mCAAmC,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,YAAY,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QAC3B,MAAM,YAAY,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC;IAClD,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,eAAe,CAC7B,GAAiB,EACjB,SAA4B;IAE5B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAE5B,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9B,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QACtD,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpB,OAAO,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The egress allowlist matcher (ruling 3) — ONE pure function, shared by the\n * harness mock, the promote-gate check, and the production `ctx.fetch`\n * wrapper, so a test that passes the mock cannot be an egress surprise later.\n */\n\nconst WILDCARD_MARKER = '://*.';\n\ninterface ParsedEntry {\n protocol: string;\n hostname: string;\n port: string;\n wildcard: boolean;\n}\n\nfunction invalidEntry(entry: string, detail: string): TypeError {\n return new TypeError(\n `Egress allowlist entry \"${entry}\" is invalid: ${detail}. ` +\n `Entries are origins only — \"https://api.example.com\" (exact) or ` +\n `\"https://*.example.com\" (subdomain wildcard); scheme + host + optional ` +\n `port, no path, query, hash, or credentials. Fix the entry in the ` +\n `integration's egress declaration.`,\n );\n}\n\nfunction parseEntry(entry: string): ParsedEntry {\n const wildcard = entry.includes(WILDCARD_MARKER);\n const candidate = wildcard ? entry.replace(WILDCARD_MARKER, '://') : entry;\n\n let parsed: URL;\n try {\n parsed = new URL(candidate);\n } catch {\n throw invalidEntry(entry, 'it does not parse as a URL origin');\n }\n\n if (parsed.hostname.includes('*')) {\n throw invalidEntry(\n entry,\n 'wildcards are only supported as a leading \"*.\" on the hostname',\n );\n }\n if (parsed.pathname !== '/' || parsed.search !== '' || parsed.hash !== '') {\n throw invalidEntry(entry, 'it carries a path, query, or hash');\n }\n if (parsed.username !== '' || parsed.password !== '') {\n throw invalidEntry(entry, 'it carries credentials');\n }\n if (parsed.hostname === '') {\n throw invalidEntry(entry, 'it has no hostname');\n }\n\n return {\n protocol: parsed.protocol,\n hostname: parsed.hostname,\n port: parsed.port,\n wildcard,\n };\n}\n\n/**\n * Returns whether `url`'s ORIGIN is covered by the egress allowlist.\n *\n * Semantics (ruling 3 — origin-only, explicit subdomain wildcards):\n * - Matching is on the origin (scheme + host + port) of the RESOLVED URL;\n * path, query, and hash never participate. Default ports are normalized\n * (`https://x.com:443` and `https://x.com` are the same origin).\n * - An exact entry (`https://api.example.com`) matches that origin only.\n * - A wildcard entry (`https://*.example.com`) matches subdomains at any\n * depth (`api.example.com`, `a.b.example.com`) but NOT the apex\n * (`example.com`) — list the apex explicitly if it is also allowed. This\n * keeps the reviewed surface literal: a wildcard grant is a statement\n * about hosts UNDER a domain, never silently about the domain itself.\n * - Scheme and port must match exactly; a wildcard never crosses either.\n *\n * Redirects: this function checks one URL. The caller (the fetch wrapper)\n * must re-check the target origin of every redirect hop — a redirect to a\n * non-allowlisted origin is also a denial.\n *\n * Throws `TypeError` (with remediation) on a malformed `url` — the same\n * input `fetch` would reject — or on a malformed allowlist entry, so a typo\n * in the egress declaration fails loudly in the harness rather than\n * silently never matching.\n */\nexport function isEgressAllowed(\n url: string | URL,\n allowlist: readonly string[],\n): boolean {\n const target = new URL(url);\n\n return allowlist.some((entry) => {\n const parsed = parseEntry(entry);\n if (parsed.protocol !== target.protocol) return false;\n if (parsed.port !== target.port) return false;\n if (parsed.wildcard) {\n return target.hostname.endsWith(`.${parsed.hostname}`);\n }\n return target.hostname === parsed.hostname;\n });\n}\n"]}
|
|
@@ -1,10 +1,30 @@
|
|
|
1
|
+
import type { StorageKeyDeclarationInput, StorageSchemaMap } from './types.js';
|
|
1
2
|
/**
|
|
2
3
|
* The shared storage validator (ruling 1) — ONE implementation, used by the
|
|
3
4
|
* `@ekanos/sdk/testing` mock today and the production `ctx.storage` wrapper
|
|
4
5
|
* when host wiring lands, so the mock enforces exactly what prod enforces.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The synchronous parse every zod schema provides, in both majors.
|
|
9
|
+
* Declarations are typed as Standard Schema (see `@ekanos/integration-schema`)
|
|
10
|
+
* so a partner's zod 3 schema satisfies them in a zod 4 host; storage reads
|
|
11
|
+
* and writes are synchronous, and definition parsing already refuses a schema
|
|
12
|
+
* without `safeParse`, so this is what the storage path calls.
|
|
13
|
+
*/
|
|
14
|
+
interface SafeParseSchema {
|
|
15
|
+
safeParse(value: unknown): {
|
|
16
|
+
success: true;
|
|
17
|
+
data: unknown;
|
|
18
|
+
} | {
|
|
19
|
+
success: false;
|
|
20
|
+
error: {
|
|
21
|
+
issues: ReadonlyArray<{
|
|
22
|
+
path: ReadonlyArray<PropertyKey>;
|
|
23
|
+
message: string;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
8
28
|
type StorageScope = 'account' | 'user';
|
|
9
29
|
/**
|
|
10
30
|
* Normalizes either declaration form — a bare zod schema, or a
|
|
@@ -21,7 +41,7 @@ type StorageScope = 'account' | 'user';
|
|
|
21
41
|
* both resolve to `false`. Widening exposure is always an explicit act.
|
|
22
42
|
*/
|
|
23
43
|
export declare function resolveStorageKeyDeclaration(declaration: StorageKeyDeclarationInput): {
|
|
24
|
-
schema:
|
|
44
|
+
schema: SafeParseSchema;
|
|
25
45
|
clientReadable: boolean;
|
|
26
46
|
};
|
|
27
47
|
/**
|
|
@@ -43,7 +63,7 @@ export declare function assertStorageKeyDeclared(args: {
|
|
|
43
63
|
scope: StorageScope;
|
|
44
64
|
key: string;
|
|
45
65
|
schemas: StorageSchemaMap | undefined;
|
|
46
|
-
}):
|
|
66
|
+
}): SafeParseSchema;
|
|
47
67
|
/**
|
|
48
68
|
* Rejects writes and deletes against host-managed read-only data_types
|
|
49
69
|
* (finding C). ONE implementation for the `@ekanos/sdk/testing` mock and the
|
|
@@ -13,7 +13,7 @@ const READ_ONLY_ACCOUNT_DATA_TYPES = new Set(['activation']);
|
|
|
13
13
|
function formatIssues(issues) {
|
|
14
14
|
return issues
|
|
15
15
|
.map((issue) => {
|
|
16
|
-
const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
16
|
+
const path = issue.path.length > 0 ? issue.path.map(String).join('.') : '(root)';
|
|
17
17
|
return ` - ${path}: ${issue.message}`;
|
|
18
18
|
})
|
|
19
19
|
.join('\n');
|
|
@@ -35,7 +35,10 @@ function formatIssues(issues) {
|
|
|
35
35
|
export function resolveStorageKeyDeclaration(declaration) {
|
|
36
36
|
if (typeof (declaration === null || declaration === void 0 ? void 0 : declaration.safeParse) ===
|
|
37
37
|
'function') {
|
|
38
|
-
return {
|
|
38
|
+
return {
|
|
39
|
+
schema: declaration,
|
|
40
|
+
clientReadable: false,
|
|
41
|
+
};
|
|
39
42
|
}
|
|
40
43
|
const descriptor = declaration;
|
|
41
44
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-validation.js","sourceRoot":"","sources":["../../src/context/storage-validation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared storage validator (ruling 1) — ONE implementation, used by the\n * `@ekanos/sdk/testing` mock today and the production `ctx.storage` wrapper\n * when host wiring lands, so the mock enforces exactly what prod enforces.\n */\nimport type { z } from 'zod';\n\nimport { StorageValidationError } from './errors';\nimport type {\n StorageKeyDeclaration,\n StorageKeyDeclarationInput,\n StorageSchemaMap,\n} from './types';\n\ntype StorageScope = 'account' | 'user';\n\n/**\n * Account data_types a partner may READ through `ctx.storage` but never\n * write or delete (authz-hardening finding C). `activation` rows are host\n * lifecycle state: the activation flow creates them and admin deactivation\n * removes them. A partner that could write `activation` (any subtype) could\n * re-create its own activation row after an admin deactivated it —\n * self-reactivation. Reads stay allowed: the capability-context proposal\n * (§2.1) deliberately exposes the non-secret activation payload through\n * `ctx.storage.account.get('activation')`.\n */\nconst READ_ONLY_ACCOUNT_DATA_TYPES = new Set(['activation']);\n\nfunction formatIssues(issues: readonly z.ZodIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${path}: ${issue.message}`;\n })\n .join('\\n');\n}\n\n/**\n * Normalizes either declaration form — a bare zod schema, or a\n * `{ schema, clientReadable }` descriptor — into the resolved pair. ONE\n * implementation, so the mock, the production `ctx.storage` wrapper, and the\n * browser-facing storage route all read a declaration the same way.\n *\n * The zod-schema test is duck-typed on `safeParse` rather than `instanceof\n * z.ZodType` on purpose: a partner package may bundle its own zod copy, and a\n * cross-realm schema must not be misread as a descriptor (which would then\n * fail the descriptor branch and report a nonsense error).\n *\n * `clientReadable` FAILS CLOSED: the bare-schema form and an omitted flag\n * both resolve to `false`. Widening exposure is always an explicit act.\n */\nexport function resolveStorageKeyDeclaration(\n declaration: StorageKeyDeclarationInput,\n): { schema: z.ZodType; clientReadable: boolean } {\n if (\n typeof (declaration as { safeParse?: unknown } | null)?.safeParse ===\n 'function'\n ) {\n return { schema: declaration as z.ZodType, clientReadable: false };\n }\n\n const descriptor = declaration as StorageKeyDeclaration;\n\n return {\n schema: descriptor.schema,\n clientReadable: descriptor.clientReadable === true,\n };\n}\n\n/**\n * Whether a declared key opted in to the generic browser-readable storage\n * route. An UNDECLARED key and a declared-but-not-opted-in key both answer\n * `false` — the route must not distinguish them, or it becomes an existence\n * oracle for a partner's server-only key names.\n */\nexport function isStorageKeyClientReadable(args: {\n key: string;\n schemas: StorageSchemaMap | undefined;\n}): boolean {\n const declaration = args.schemas?.[args.key];\n if (!declaration) return false;\n\n return resolveStorageKeyDeclaration(declaration).clientReadable;\n}\n\n/**\n * Resolves the declared schema for a storage key, throwing\n * `StorageValidationError` when the key is undeclared (strict keys). Used\n * directly by `delete`, and by `parseStorageValue` for reads and writes.\n */\nexport function assertStorageKeyDeclared(args: {\n scope: StorageScope;\n key: string;\n schemas: StorageSchemaMap | undefined;\n}): z.ZodType {\n const declaration = args.schemas?.[args.key];\n if (!declaration) {\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint:\n `No schema is declared for ${args.scope} storage key \"${args.key}\". ` +\n `Storage keys are strict: declare the key and its zod schema in ` +\n `defineIntegration({ storage: { ${args.scope}: { '${args.key}': z.object({ … }) } } }) ` +\n `(or in createMockContext({ storageSchemas }) in tests) before reading, ` +\n `writing, or deleting it.`,\n });\n }\n\n return resolveStorageKeyDeclaration(declaration).schema;\n}\n\n/**\n * Rejects writes and deletes against host-managed read-only data_types\n * (finding C). ONE implementation for the `@ekanos/sdk/testing` mock and the\n * production `ctx.storage` wrapper, so a write that prod refuses also fails\n * in partner tests. Runs BEFORE the declared-key check so a read-only key\n * reports the real reason rather than \"undeclared\".\n */\nexport function assertStorageKeyWritable(args: {\n scope: StorageScope;\n key: string;\n}): void {\n if (args.scope !== 'account') return;\n\n const slashIndex = args.key.indexOf('/');\n const dataType = slashIndex === -1 ? args.key : args.key.slice(0, slashIndex);\n\n if (READ_ONLY_ACCOUNT_DATA_TYPES.has(dataType)) {\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint:\n `Storage key \"${args.key}\" uses the host-managed \"${dataType}\" ` +\n `data_type, which ctx.storage can read but never write or delete. ` +\n `Activation rows are created by the host activation flow and ` +\n `removed by deactivation — an integration cannot (re)create or ` +\n `drop its own activation. Persist integration state under a ` +\n `\"settings\", \"sync_state\", \"metrics_summary\", or \"cache\" key instead.`,\n });\n }\n}\n\n/**\n * Validates one storage value against its declared schema and returns the\n * schema's OUTPUT value (defaults and transforms applied). Throws\n * `StorageValidationError` on an undeclared key or a schema mismatch, with\n * the zod issues formatted into the remediation hint.\n */\nexport function parseStorageValue(args: {\n scope: StorageScope;\n key: string;\n operation: 'read' | 'write';\n schemas: StorageSchemaMap | undefined;\n value: unknown;\n}): unknown {\n const schema = assertStorageKeyDeclared(args);\n const result = schema.safeParse(args.value);\n\n if (!result.success) {\n const issues = formatIssues(result.error.issues);\n const hint =\n args.operation === 'write'\n ? `The value written to ${args.scope} storage key \"${args.key}\" fails ` +\n `its declared schema. Fix the write payload to match the schema ` +\n `declared in defineIntegration({ storage }):\\n${issues}`\n : `The data stored at ${args.scope} storage key \"${args.key}\" no longer ` +\n `matches its declared schema. Handle schema evolution explicitly: ` +\n `widen the schema (z.union with the legacy shape) or migrate to a ` +\n `versioned key, then rewrite the row via set(). Issues:\\n${issues}`;\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint,\n });\n }\n\n return result.data;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"storage-validation.js","sourceRoot":"","sources":["../../src/context/storage-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAmClD;;;;;;;;;GASG;AACH,MAAM,4BAA4B,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;AAE7D,SAAS,YAAY,CACnB,MAA4E;IAE5E,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,IAAI,GACR,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACtE,OAAO,OAAO,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;IACzC,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,4BAA4B,CAC1C,WAAuC;IAEvC,IACE,QAAQ,WAA8C,aAA9C,WAAW,uBAAX,WAAW,CAAqC,SAAS,CAAA;QACjE,UAAU,EACV,CAAC;QACD,OAAO;YACL,MAAM,EAAE,WAAyC;YACjD,cAAc,EAAE,KAAK;SACtB,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,WAAoC,CAAC;IAExD,OAAO;QACL,MAAM,EAAE,UAAU,CAAC,MAAoC;QACvD,cAAc,EAAE,UAAU,CAAC,cAAc,KAAK,IAAI;KACnD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAG1C;;IACC,MAAM,WAAW,GAAG,MAAA,IAAI,CAAC,OAAO,0CAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAE/B,OAAO,4BAA4B,CAAC,WAAW,CAAC,CAAC,cAAc,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAIxC;;IACC,MAAM,WAAW,GAAG,MAAA,IAAI,CAAC,OAAO,0CAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,sBAAsB,CAAC;YAC/B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EACF,6BAA6B,IAAI,CAAC,KAAK,iBAAiB,IAAI,CAAC,GAAG,KAAK;gBACrE,iEAAiE;gBACjE,kCAAkC,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,GAAG,4BAA4B;gBACxF,yEAAyE;gBACzE,0BAA0B;SAC7B,CAAC,CAAC;IACL,CAAC;IAED,OAAO,4BAA4B,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAGxC;IACC,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO;IAErC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAE9E,IAAI,4BAA4B,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,sBAAsB,CAAC;YAC/B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EACF,gBAAgB,IAAI,CAAC,GAAG,4BAA4B,QAAQ,IAAI;gBAChE,mEAAmE;gBACnE,8DAA8D;gBAC9D,gEAAgE;gBAChE,6DAA6D;gBAC7D,sEAAsE;SACzE,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAMjC;IACC,MAAM,MAAM,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAE5C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACjD,MAAM,IAAI,GACR,IAAI,CAAC,SAAS,KAAK,OAAO;YACxB,CAAC,CAAC,wBAAwB,IAAI,CAAC,KAAK,iBAAiB,IAAI,CAAC,GAAG,UAAU;gBACrE,iEAAiE;gBACjE,gDAAgD,MAAM,EAAE;YAC1D,CAAC,CAAC,sBAAsB,IAAI,CAAC,KAAK,iBAAiB,IAAI,CAAC,GAAG,cAAc;gBACvE,mEAAmE;gBACnE,mEAAmE;gBACnE,2DAA2D,MAAM,EAAE,CAAC;QAC1E,MAAM,IAAI,sBAAsB,CAAC;YAC/B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC","sourcesContent":["import { StorageValidationError } from './errors';\nimport type {\n StorageKeyDeclaration,\n StorageKeyDeclarationInput,\n StorageSchemaMap,\n} from './types';\n\n/**\n * The shared storage validator (ruling 1) — ONE implementation, used by the\n * `@ekanos/sdk/testing` mock today and the production `ctx.storage` wrapper\n * when host wiring lands, so the mock enforces exactly what prod enforces.\n */\n/**\n * The synchronous parse every zod schema provides, in both majors.\n * Declarations are typed as Standard Schema (see `@ekanos/integration-schema`)\n * so a partner's zod 3 schema satisfies them in a zod 4 host; storage reads\n * and writes are synchronous, and definition parsing already refuses a schema\n * without `safeParse`, so this is what the storage path calls.\n */\ninterface SafeParseSchema {\n safeParse(value: unknown):\n | { success: true; data: unknown }\n | {\n success: false;\n error: {\n issues: ReadonlyArray<{\n path: ReadonlyArray<PropertyKey>;\n message: string;\n }>;\n };\n };\n}\n\ntype StorageScope = 'account' | 'user';\n\n/**\n * Account data_types a partner may READ through `ctx.storage` but never\n * write or delete (authz-hardening finding C). `activation` rows are host\n * lifecycle state: the activation flow creates them and admin deactivation\n * removes them. A partner that could write `activation` (any subtype) could\n * re-create its own activation row after an admin deactivated it —\n * self-reactivation. Reads stay allowed: the capability-context proposal\n * (§2.1) deliberately exposes the non-secret activation payload through\n * `ctx.storage.account.get('activation')`.\n */\nconst READ_ONLY_ACCOUNT_DATA_TYPES = new Set(['activation']);\n\nfunction formatIssues(\n issues: ReadonlyArray<{ path: ReadonlyArray<PropertyKey>; message: string }>,\n): string {\n return issues\n .map((issue) => {\n const path =\n issue.path.length > 0 ? issue.path.map(String).join('.') : '(root)';\n return ` - ${path}: ${issue.message}`;\n })\n .join('\\n');\n}\n\n/**\n * Normalizes either declaration form — a bare zod schema, or a\n * `{ schema, clientReadable }` descriptor — into the resolved pair. ONE\n * implementation, so the mock, the production `ctx.storage` wrapper, and the\n * browser-facing storage route all read a declaration the same way.\n *\n * The zod-schema test is duck-typed on `safeParse` rather than `instanceof\n * z.ZodType` on purpose: a partner package may bundle its own zod copy, and a\n * cross-realm schema must not be misread as a descriptor (which would then\n * fail the descriptor branch and report a nonsense error).\n *\n * `clientReadable` FAILS CLOSED: the bare-schema form and an omitted flag\n * both resolve to `false`. Widening exposure is always an explicit act.\n */\nexport function resolveStorageKeyDeclaration(\n declaration: StorageKeyDeclarationInput,\n): { schema: SafeParseSchema; clientReadable: boolean } {\n if (\n typeof (declaration as { safeParse?: unknown } | null)?.safeParse ===\n 'function'\n ) {\n return {\n schema: declaration as unknown as SafeParseSchema,\n clientReadable: false,\n };\n }\n\n const descriptor = declaration as StorageKeyDeclaration;\n\n return {\n schema: descriptor.schema as unknown as SafeParseSchema,\n clientReadable: descriptor.clientReadable === true,\n };\n}\n\n/**\n * Whether a declared key opted in to the generic browser-readable storage\n * route. An UNDECLARED key and a declared-but-not-opted-in key both answer\n * `false` — the route must not distinguish them, or it becomes an existence\n * oracle for a partner's server-only key names.\n */\nexport function isStorageKeyClientReadable(args: {\n key: string;\n schemas: StorageSchemaMap | undefined;\n}): boolean {\n const declaration = args.schemas?.[args.key];\n if (!declaration) return false;\n\n return resolveStorageKeyDeclaration(declaration).clientReadable;\n}\n\n/**\n * Resolves the declared schema for a storage key, throwing\n * `StorageValidationError` when the key is undeclared (strict keys). Used\n * directly by `delete`, and by `parseStorageValue` for reads and writes.\n */\nexport function assertStorageKeyDeclared(args: {\n scope: StorageScope;\n key: string;\n schemas: StorageSchemaMap | undefined;\n}): SafeParseSchema {\n const declaration = args.schemas?.[args.key];\n if (!declaration) {\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint:\n `No schema is declared for ${args.scope} storage key \"${args.key}\". ` +\n `Storage keys are strict: declare the key and its zod schema in ` +\n `defineIntegration({ storage: { ${args.scope}: { '${args.key}': z.object({ … }) } } }) ` +\n `(or in createMockContext({ storageSchemas }) in tests) before reading, ` +\n `writing, or deleting it.`,\n });\n }\n\n return resolveStorageKeyDeclaration(declaration).schema;\n}\n\n/**\n * Rejects writes and deletes against host-managed read-only data_types\n * (finding C). ONE implementation for the `@ekanos/sdk/testing` mock and the\n * production `ctx.storage` wrapper, so a write that prod refuses also fails\n * in partner tests. Runs BEFORE the declared-key check so a read-only key\n * reports the real reason rather than \"undeclared\".\n */\nexport function assertStorageKeyWritable(args: {\n scope: StorageScope;\n key: string;\n}): void {\n if (args.scope !== 'account') return;\n\n const slashIndex = args.key.indexOf('/');\n const dataType = slashIndex === -1 ? args.key : args.key.slice(0, slashIndex);\n\n if (READ_ONLY_ACCOUNT_DATA_TYPES.has(dataType)) {\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint:\n `Storage key \"${args.key}\" uses the host-managed \"${dataType}\" ` +\n `data_type, which ctx.storage can read but never write or delete. ` +\n `Activation rows are created by the host activation flow and ` +\n `removed by deactivation — an integration cannot (re)create or ` +\n `drop its own activation. Persist integration state under a ` +\n `\"settings\", \"sync_state\", \"metrics_summary\", or \"cache\" key instead.`,\n });\n }\n}\n\n/**\n * Validates one storage value against its declared schema and returns the\n * schema's OUTPUT value (defaults and transforms applied). Throws\n * `StorageValidationError` on an undeclared key or a schema mismatch, with\n * the zod issues formatted into the remediation hint.\n */\nexport function parseStorageValue(args: {\n scope: StorageScope;\n key: string;\n operation: 'read' | 'write';\n schemas: StorageSchemaMap | undefined;\n value: unknown;\n}): unknown {\n const schema = assertStorageKeyDeclared(args);\n const result = schema.safeParse(args.value);\n\n if (!result.success) {\n const issues = formatIssues(result.error.issues);\n const hint =\n args.operation === 'write'\n ? `The value written to ${args.scope} storage key \"${args.key}\" fails ` +\n `its declared schema. Fix the write payload to match the schema ` +\n `declared in defineIntegration({ storage }):\\n${issues}`\n : `The data stored at ${args.scope} storage key \"${args.key}\" no longer ` +\n `matches its declared schema. Handle schema evolution explicitly: ` +\n `widen the schema (z.union with the legacy shape) or migrate to a ` +\n `versioned key, then rewrite the row via set(). Issues:\\n${issues}`;\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint,\n });\n }\n\n return result.data;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-assistant-context.js","sourceRoot":"","sources":["../../src/hooks/ask-assistant-context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAEL,aAAa,EACb,GAAG,EACH,WAAW,EACX,eAAe,EACf,OAAO,EACP,MAAM,GACP,MAAM,OAAO,CAAC;AAaf,MAAM,mBAAmB,GAAG,aAAa,CACvC,IAAI,CACL,CAAC;AAMF;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,QAAQ,EAA6B;IAC1E,MAAM,UAAU,GAAG,MAAM,CAA6B,IAAI,CAAC,CAAC;IAE5D,MAAM,GAAG,GAAG,WAAW,CAAsB,CAAC,GAAG,EAAE,EAAE;;QACnD,MAAA,UAAU,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"ask-assistant-context.js","sourceRoot":"","sources":["../../src/hooks/ask-assistant-context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAEL,aAAa,EACb,GAAG,EACH,WAAW,EACX,eAAe,EACf,OAAO,EACP,MAAM,GACP,MAAM,OAAO,CAAC;AAaf,MAAM,mBAAmB,GAAG,aAAa,CACvC,IAAI,CACL,CAAC;AAMF;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,QAAQ,EAA6B;IAC1E,MAAM,UAAU,GAAG,MAAM,CAA6B,IAAI,CAAC,CAAC;IAE5D,MAAM,GAAG,GAAG,WAAW,CAAsB,CAAC,GAAG,EAAE,EAAE;;QACnD,MAAA,UAAU,CAAC,OAAO,+CAAlB,UAAU,EAAW,GAAG,CAAC,CAAC;IAC5B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,OAAmC,EAAE,EAAE;QAC1E,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC,EAChC,CAAC,GAAG,EAAE,eAAe,CAAC,CACvB,CAAC;IAEF,OAAO,KAAC,mBAAmB,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAuB,CAAC;AAC7E,CAAC;AAED,MAAM,QAAQ,GAAwB,GAAG,EAAE,GAAE,CAAC,CAAC;AAE/C;;;;GAIG;AACH,MAAM,UAAU,eAAe;;IAC7B,MAAM,OAAO,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACzC,aAAO,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,GAAG,mCAAI,QAAQ,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA4B;IAClE,MAAM,OAAO,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAEzC,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACjC,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AACzB,CAAC","sourcesContent":["'use client';\n\nimport {\n type ReactNode,\n createContext,\n use,\n useCallback,\n useLayoutEffect,\n useMemo,\n useRef,\n} from 'react';\n\nimport type { WidgetAskContext } from '../types/widget-ask-context';\n\ntype AskAssistantHandler = (ctx: WidgetAskContext) => void;\n\ninterface AskAssistantContextValue {\n /** Stable dispatcher widgets call. Routes to the registered handler at invocation time. */\n ask: AskAssistantHandler;\n /** Internal — used by the dock side of the bridge to install its handler. */\n registerHandler: (handler: AskAssistantHandler | null) => void;\n}\n\nconst AskAssistantContext = createContext<AskAssistantContextValue | null>(\n null,\n);\n\ninterface AskAssistantProviderProps {\n children: ReactNode;\n}\n\n/**\n * Hosts the bridge between widget ask-bars and the AI assistant dock.\n *\n * Widgets call `useAskAssistant()` to get a stable dispatcher. The dock\n * registers its actual handler via `useRegisterAskAssistant()` once it\n * has access to its chat hook. Until a handler is registered (e.g. on\n * routes without a dock), calls are no-ops.\n */\nexport function AskAssistantProvider({ children }: AskAssistantProviderProps) {\n const handlerRef = useRef<AskAssistantHandler | null>(null);\n\n const ask = useCallback<AskAssistantHandler>((ctx) => {\n handlerRef.current?.(ctx);\n }, []);\n\n const registerHandler = useCallback((handler: AskAssistantHandler | null) => {\n handlerRef.current = handler;\n }, []);\n\n const value = useMemo<AskAssistantContextValue>(\n () => ({ ask, registerHandler }),\n [ask, registerHandler],\n );\n\n return <AskAssistantContext value={value}>{children}</AskAssistantContext>;\n}\n\nconst NOOP_ASK: AskAssistantHandler = () => {};\n\n/**\n * Returns a stable dispatcher widgets can call to send a question about\n * themselves to the assistant. Outside an `AskAssistantProvider` (e.g.\n * widget preview pages), this is a no-op.\n */\nexport function useAskAssistant(): AskAssistantHandler {\n const context = use(AskAssistantContext);\n return context?.ask ?? NOOP_ASK;\n}\n\n/**\n * Installs the dock-side handler that the widget dispatcher will call.\n * Re-registers whenever `handler` changes; clears on unmount.\n *\n * Uses useLayoutEffect so the handler is wired up before the browser\n * paints the dock — otherwise a chip click that fires during the first\n * paint frame (e.g. user opens a drawer fast) finds a null handler and\n * silently no-ops.\n */\nexport function useRegisterAskAssistant(handler: AskAssistantHandler): void {\n const context = use(AskAssistantContext);\n\n useLayoutEffect(() => {\n if (!context) return;\n context.registerHandler(handler);\n return () => {\n context.registerHandler(null);\n };\n }, [context, handler]);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-integration-storage.js","sourceRoot":"","sources":["../../src/hooks/fetch-integration-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,oDAAoD;AACpD,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAgBhC;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,eAAuB,EACvB,OAEC;;IAED,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;QAChC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;KAC7B,CAAC,CAAC;IAEH,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"fetch-integration-storage.js","sourceRoot":"","sources":["../../src/hooks/fetch-integration-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,oDAAoD;AACpD,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAgBhC;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,eAAuB,EACvB,OAEC;;IAED,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;QAChC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;KAC7B,CAAC,CAAC;IAEH,MAAM,IAAI,SAAG,OAAO,CAAC,OAAO,mCAAI,mBAAmB,CAAC;IAEpD,OAAO,GAAG,IAAI,IAAI,eAAe,YAAY,KAAK,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,eAAuB,EACvB,OAAuC;;IAEvC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,4BAA4B,eAAe,oCAAoC,CAChF,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,4BAA4B,eAAe,oBAAoB,OAAO,CAAC,IAAI,CAAC,MAAM,SAAS;YACzF,gCAAgC,oBAAoB,+BAA+B,CACtF,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,qBAAqB,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IAE5D,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE;QAC3C,MAAM,QAAE,OAAO,CAAC,MAAM,mCAAI,IAAI;QAC9B,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;KACxC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,mBAAmB,eAAe,cAAc,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,IAAI,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAE/C,IACE,OAAO,KAAK,IAAI;QAChB,OAAO,OAAO,KAAK,QAAQ;QAC3B,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EACtB,CAAC;QACD,MAAM,IAAI,KAAK,CACb,QAAQ,eAAe,gDAAgD,CACxE,CAAC;IACJ,CAAC;IAED,OAAO,OAAkC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * The browser half of the generic storage bridge.\n *\n * `ctx.storage` is a server capability and widgets are client components, so\n * a saved setting has to round-trip through HTTP. The host serves that\n * round-trip once, generically, at\n * `GET /api/integrations/[slug]/storage?accountId=…&keys=a,b`; this is the\n * client that calls it, so an integration never hand-writes the URL, the\n * query string, or the not-ok handling.\n *\n * Only keys whose declaration opted in with `clientReadable: true` come back.\n * A key that did not opt in, was never declared, or simply has no stored row\n * is ABSENT from the result — all three are indistinguishable by design, so\n * callers must treat a missing key as \"no value\", never as an error.\n *\n * Values are returned raw and unvalidated: the integration owns the shape of\n * its own storage, so it validates with the same zod schemas it declared:\n *\n * ```ts\n * const raw = await fetchIntegrationStorage('acme-weather', {\n * accountId,\n * keys: ['settings/location'],\n * });\n *\n * const parsed = LocationSchema.safeParse(raw['settings/location']);\n * const location = parsed.success ? parsed.data : null;\n * ```\n */\n\n/** Mirrors the host route's own per-request cap. */\nconst MAX_KEYS_PER_REQUEST = 20;\n\nexport interface FetchIntegrationStorageOptions {\n /** The account whose storage to read. Must be one the caller belongs to. */\n accountId: string;\n /** Declared, `clientReadable` account-scope keys, e.g. `['settings/units']`. */\n keys: readonly string[];\n signal?: AbortSignal;\n /**\n * Overrides the base path. Exists for harnesses and tests that serve the\n * route from somewhere other than the host's own origin; production code\n * should leave it alone.\n */\n baseUrl?: string;\n}\n\n/**\n * Builds the host storage-route URL for one integration. Exported because a\n * harness or a test double needs to recognize the exact URL this client will\n * request.\n */\nexport function integrationStorageUrl(\n integrationSlug: string,\n options: Pick<FetchIntegrationStorageOptions, 'accountId' | 'keys'> & {\n baseUrl?: string;\n },\n): string {\n const query = new URLSearchParams({\n accountId: options.accountId,\n keys: options.keys.join(','),\n });\n\n const base = options.baseUrl ?? '/api/integrations';\n\n return `${base}/${integrationSlug}/storage?${query}`;\n}\n\n/**\n * Reads declared, client-readable account-scope storage for one integration.\n *\n * Throws when the request itself fails — a 403 (not signed in, not a member,\n * not activated, or no such integration, all deliberately indistinguishable)\n * or a transport error. It does NOT throw for a key that came back absent.\n */\nexport async function fetchIntegrationStorage(\n integrationSlug: string,\n options: FetchIntegrationStorageOptions,\n): Promise<Record<string, unknown>> {\n if (options.keys.length === 0) {\n throw new Error(\n `fetchIntegrationStorage('${integrationSlug}') needs at least one storage key.`,\n );\n }\n\n if (options.keys.length > MAX_KEYS_PER_REQUEST) {\n throw new Error(\n `fetchIntegrationStorage('${integrationSlug}') was asked for ${options.keys.length} keys; ` +\n `the host route reads at most ${MAX_KEYS_PER_REQUEST} per request. Split the read.`,\n );\n }\n\n const url = integrationStorageUrl(integrationSlug, options);\n\n const response = await globalThis.fetch(url, {\n signal: options.signal ?? null,\n headers: { accept: 'application/json' },\n });\n\n if (!response.ok) {\n throw new Error(\n `Could not read '${integrationSlug}' storage (${response.status} ${response.statusText}).`,\n );\n }\n\n const payload: unknown = await response.json();\n\n if (\n payload === null ||\n typeof payload !== 'object' ||\n Array.isArray(payload)\n ) {\n throw new Error(\n `The '${integrationSlug}' storage route returned a non-object payload.`,\n );\n }\n\n return payload as Record<string, unknown>;\n}\n"]}
|
|
@@ -31,6 +31,9 @@ const PARTNER_OAUTH_ERROR_MESSAGES = {
|
|
|
31
31
|
not_configured: 'This integration is not configured for your workspace yet. Ask an administrator to add its OAuth credentials.',
|
|
32
32
|
exchange_failed: 'The provider rejected the connection. Try again, or contact support if it keeps failing.',
|
|
33
33
|
save_failed: 'Connected, but the credentials could not be saved. Try again.',
|
|
34
|
+
wrong_workspace: 'You signed in to a different account or workspace than this one is set up for. Switch to that account at the provider and try again.',
|
|
35
|
+
app_not_set_up: 'The provider refused this integration’s app settings, so trying again won’t help. Ask an administrator to check the app’s scopes and credentials.',
|
|
36
|
+
organization_access: 'Connect with personal access — this integration only uses your own account. Try again and choose personal access.',
|
|
34
37
|
};
|
|
35
38
|
/**
|
|
36
39
|
* A `ConnectionCheckAction` backed by the hosted status route. The route
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"partner-oauth.js","sourceRoot":"","sources":["../../src/hooks/partner-oauth.ts"],"names":[],"mappings":"AAiCA,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAW7C,yEAAyE;AACzE,MAAM,UAAU,uBAAuB,CACrC,eAAuB,EACvB,
|
|
1
|
+
{"version":3,"file":"partner-oauth.js","sourceRoot":"","sources":["../../src/hooks/partner-oauth.ts"],"names":[],"mappings":"AAiCA,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAW7C,yEAAyE;AACzE,MAAM,UAAU,uBAAuB,CACrC,eAAuB,EACvB,OAAO,GAA4B,EAAE;;IAErC,MAAM,IAAI,SAAG,OAAO,CAAC,OAAO,mCAAI,gBAAgB,CAAC;IACjD,OAAO,GAAG,IAAI,IAAI,eAAe,gBAAgB,CAAC;AACpD,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,qBAAqB,CACnC,eAAuB,EACvB,OAAwD;;IAExD,MAAM,IAAI,SAAG,OAAO,CAAC,OAAO,mCAAI,gBAAgB,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACpE,OAAO,GAAG,IAAI,IAAI,eAAe,iBAAiB,KAAK,EAAE,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,eAAuB;IAEvB,OAAO;QACL,IAAI,EAAE,GAAG,eAAe,cAAc;QACtC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,wBAAC,4BAA4B,CAAC,KAAK,CAAC,mCAAI,IAAI,GAAA;KAClE,CAAC;AACJ,CAAC;AAED,MAAM,4BAA4B,GAA2B;IAC3D,MAAM,EAAE,0DAA0D;IAClE,OAAO,EAAE,uDAAuD;IAChE,OAAO,EACL,qEAAqE;IACvE,YAAY,EACV,oEAAoE;IACtE,cAAc,EACZ,+GAA+G;IACjH,eAAe,EACb,0FAA0F;IAC5F,WAAW,EAAE,+DAA+D;IAC5E,eAAe,EACb,sIAAsI;IACxI,cAAc,EACZ,mJAAmJ;IACrJ,mBAAmB,EACjB,mHAAmH;CACtH,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAC1C,eAAuB,EACvB,OAAO,GAA4B,EAAE;IAErC,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,qBAAqB,CAAC,eAAe,kCAC5C,OAAO,KACV,SAAS,IACT,CAAC;QAEH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE;YAC3C,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,uBAAuB,eAAe,wBAAwB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,IAAI,CACzG,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAE/C,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CACb,QAAQ,eAAe,gDAAgD,CACxE,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAC9B,KAAc;IAEd,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,OAAQ,KAA+B,CAAC,OAAO,KAAK,SAAS,CAC9D,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The browser half of the hosted partner OAuth transport.\n *\n * A partner's `oauth` declaration names the provider endpoints and the\n * client-credential secrets; the HOST owns the flow — authorize redirect,\n * `state`, callback, code exchange, and the `onTokens` call — behind three\n * generic routes it serves for every partner integration:\n *\n * GET /api/integrations/[slug]/oauth/connect?accountId=…&returnPath=…\n * GET /api/integrations/[slug]/oauth/callback (the provider redirects here)\n * GET /api/integrations/[slug]/oauth/status?accountId=…\n *\n * These helpers are the only place a partner package should reference those\n * paths, so a route move is one SDK change rather than a hunt through every\n * integration. `OAuthActivationForm`'s `connectPath` default still points at\n * the per-integration `/connect` route first-party packages ship themselves;\n * a partner passes `connectPath: partnerOAuthConnectPath(slug)` explicitly.\n *\n * ```tsx\n * <OAuthActivationForm\n * {...props}\n * config={{ slug, displayName: 'Acme', connectPath: partnerOAuthConnectPath(slug), … }}\n * checkConnection={createPartnerConnectionCheck(slug)}\n * errorParam={partnerOAuthErrorParam(slug)}\n * />\n * ```\n */\nimport type {\n ConnectionCheckAction,\n ConnectionCheckResult,\n OAuthErrorParam,\n} from './use-oauth-connection-status';\n\nconst DEFAULT_BASE_URL = '/api/integrations';\n\nexport interface PartnerOAuthPathOptions {\n /**\n * Overrides the base path. Exists for harnesses and tests that serve the\n * routes from somewhere other than the host's own origin; production code\n * should leave it alone.\n */\n baseUrl?: string;\n}\n\n/** The hosted connect route the activation form sends the browser to. */\nexport function partnerOAuthConnectPath(\n integrationSlug: string,\n options: PartnerOAuthPathOptions = {},\n): string {\n const base = options.baseUrl ?? DEFAULT_BASE_URL;\n return `${base}/${integrationSlug}/oauth/connect`;\n}\n\n/** The hosted status route `createPartnerConnectionCheck` reads. */\nexport function partnerOAuthStatusUrl(\n integrationSlug: string,\n options: PartnerOAuthPathOptions & { accountId: string },\n): string {\n const base = options.baseUrl ?? DEFAULT_BASE_URL;\n const query = new URLSearchParams({ accountId: options.accountId });\n return `${base}/${integrationSlug}/oauth/status?${query}`;\n}\n\n/**\n * The error query param the hosted callback appends on failure:\n * `<slug>_oauth_error=<code>`. Codes are the transport's, so the messages\n * live here rather than in every partner package.\n */\nexport function partnerOAuthErrorParam(\n integrationSlug: string,\n): OAuthErrorParam {\n return {\n name: `${integrationSlug}_oauth_error`,\n toMessage: (value) => PARTNER_OAUTH_ERROR_MESSAGES[value] ?? null,\n };\n}\n\nconst PARTNER_OAUTH_ERROR_MESSAGES: Record<string, string> = {\n denied: 'Access was denied at the provider. Try connecting again.',\n expired: 'The connection attempt expired. Try connecting again.',\n invalid:\n 'The connection attempt could not be verified. Try connecting again.',\n unauthorized:\n 'You need to be signed in as the person who started the connection.',\n not_configured:\n 'This integration is not configured for your workspace yet. Ask an administrator to add its OAuth credentials.',\n exchange_failed:\n 'The provider rejected the connection. Try again, or contact support if it keeps failing.',\n save_failed: 'Connected, but the credentials could not be saved. Try again.',\n wrong_workspace:\n 'You signed in to a different account or workspace than this one is set up for. Switch to that account at the provider and try again.',\n app_not_set_up:\n 'The provider refused this integration’s app settings, so trying again won’t help. Ask an administrator to check the app’s scopes and credentials.',\n organization_access:\n 'Connect with personal access — this integration only uses your own account. Try again and choose personal access.',\n};\n\n/**\n * A `ConnectionCheckAction` backed by the hosted status route. The route\n * answers 200 with the standard envelope for both outcomes — `{ success:\n * true, data: { obtainedAt } }` or `{ success: false, error }` — and a non-OK\n * status only when the request itself is unauthorized, so a query error\n * surfaces as an error rather than as a misleading \"not connected\".\n */\nexport function createPartnerConnectionCheck(\n integrationSlug: string,\n options: PartnerOAuthPathOptions = {},\n): ConnectionCheckAction {\n return async ({ accountId }) => {\n const url = partnerOAuthStatusUrl(integrationSlug, {\n ...options,\n accountId,\n });\n\n const response = await globalThis.fetch(url, {\n headers: { accept: 'application/json' },\n });\n\n if (!response.ok) {\n throw new Error(\n `Could not read the '${integrationSlug}' connection status (${response.status} ${response.statusText}).`,\n );\n }\n\n const payload: unknown = await response.json();\n\n if (!isConnectionCheckResult(payload)) {\n throw new Error(\n `The '${integrationSlug}' status route returned an unexpected payload.`,\n );\n }\n\n return payload;\n };\n}\n\nfunction isConnectionCheckResult(\n value: unknown,\n): value is ConnectionCheckResult {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { success?: unknown }).success === 'boolean'\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-integration-action.js","sourceRoot":"","sources":["../../src/hooks/use-integration-action.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n/**\n * The browser half of the widget-actions transport.\n *\n * A partner's `actions` declaration on `IntegrationDefinition` is a\n * record of named, deterministic server handlers —\n * `(ctx, input) => Promise<unknown>` — invoked by a widget's own UI (a Save\n * button), never synthesized by the AI assistant. The host serves one\n * generic route per action:\n *\n * POST /api/integrations/[slug]/actions/[actionName]\n *\n * and this hook is the only place a partner package should build that\n * request, so a route move is one SDK change rather than a hunt through\n * every widget — the same discipline `partnerOAuthConnectPath`/\n * `fetchIntegrationStorage` already apply to their own routes.\n *\n * ```tsx\n * const createRecord = useIntegrationAction<CreateRecordInput, CreateRecordOutput>(\n * 'acme-crm',\n * 'create-record',\n * { accountId },\n * );\n *\n * <Button onClick={() => createRecord.mutate(formValues)}>Save</Button>\n * ```\n *\n * ── Typing is a convention, not a cross-package mechanism ───────────────────\n *\n * `@ekanos/sdk` is a separately published package — it cannot see a specific\n * partner's `z.ZodTypeAny` instance, since that object lives in the\n * partner's own server bundle and never ships to the browser, let alone into\n * the SDK's own compiled types. `TInput`/`TOutput` are generic parameters\n * the CALLER supplies by hand at the call site — typically by also exporting\n * `type CreateRecordInput = z.infer<typeof createRecordSchema>` from the same\n * package that declares the action and importing it into the widget. This\n * hook treats the wire payload as `unknown` at runtime — it never parses it\n * against the partner's own zod schema, only against the success/error\n * envelope shape — so drift between the declared action and the widget's\n * type import is a partner-package refactor problem, not a network-boundary\n * one. Same discipline `fetchIntegrationStorage` already documents for\n * storage (\"Values are returned raw and unvalidated\").\n *\n * ── Errors are typed, never raw fetch/transport exceptions ──────────────────\n *\n * A rejected mutation's `error` is always an `IntegrationActionError` —\n * never a bare `Error`, a `Response`, or an unwrapped parse/network\n * exception. `status` and `code` are always present; `issues` (the partner's\n * own zod issues) is present only for the route's 400 `invalid_input`\n * response, so a form can map failures back onto fields without\n * string-matching a message.\n */\n\nimport { useMutation, type UseMutationResult } from '@tanstack/react-query';\n\nconst DEFAULT_BASE_URL = '/api/integrations';\n\nexport interface UseIntegrationActionOptions {\n /** The account the action runs against. Must be one the caller belongs to. */\n accountId: string;\n /**\n * Overrides the base path. Exists for harnesses and tests that serve the\n * route from somewhere other than the host's own origin; production code\n * should leave it alone.\n */\n baseUrl?: string;\n}\n\n/**\n * Builds the host actions-route URL for one integration/action pair.\n * Exported because a harness or a test double needs to recognize the exact\n * URL this hook will request.\n */\nexport function integrationActionUrl(\n integrationSlug: string,\n actionName: string,\n options: { baseUrl?: string } = {},\n): string {\n const base = options.baseUrl ?? DEFAULT_BASE_URL;\n return `${base}/${integrationSlug}/actions/${actionName}`;\n}\n\n/**\n * Route-defined failure codes the host actions route answers with, plus two\n * transport-level codes this hook assigns itself when the response never\n * reaches a valid envelope: `'network_error'` (the request itself failed —\n * offline, DNS, aborted) and `'invalid_response'` (a response came back but\n * its body isn't the envelope shape this transport expects).\n */\nexport type IntegrationActionErrorCode =\n | 'action_not_found'\n | 'rate_limited'\n | 'invalid_input'\n | 'handler_error'\n | 'network_error'\n | 'invalid_response'\n | (string & {});\n\nexport interface IntegrationActionErrorDetails {\n /** HTTP status; `0` means the request never reached the server. */\n status: number;\n code: IntegrationActionErrorCode;\n /** The partner's own zod issues — present only for a 400 `invalid_input` response. */\n issues?: unknown[];\n}\n\n/**\n * The typed failure a rejected `useIntegrationAction` mutation always throws.\n * `status`/`code` let a caller branch without string-matching `message`;\n * `issues` lets a form map the partner's own validation failures back onto\n * fields.\n */\nexport class IntegrationActionError extends Error {\n readonly status: number;\n readonly code: IntegrationActionErrorCode;\n readonly issues?: unknown[];\n\n constructor(message: string, details: IntegrationActionErrorDetails) {\n super(message);\n this.name = 'IntegrationActionError';\n this.status = details.status;\n this.code = details.code;\n this.issues = details.issues;\n }\n}\n\ninterface ActionSuccessEnvelope {\n success: true;\n data: unknown;\n}\n\ninterface ActionErrorEnvelope {\n success: false;\n error: string;\n code?: string;\n issues?: unknown[];\n}\n\nfunction isSuccessEnvelope(value: unknown): value is ActionSuccessEnvelope {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { success?: unknown }).success === true &&\n 'data' in value\n );\n}\n\nfunction isErrorEnvelope(value: unknown): value is ActionErrorEnvelope {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { success?: unknown }).success === false &&\n typeof (value as { error?: unknown }).error === 'string'\n );\n}\n\nasync function runIntegrationAction<TInput, TOutput>(\n integrationSlug: string,\n actionName: string,\n input: TInput,\n options: UseIntegrationActionOptions,\n): Promise<TOutput> {\n const url = integrationActionUrl(integrationSlug, actionName, options);\n\n let response: Response;\n try {\n response = await globalThis.fetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'application/json',\n },\n body: JSON.stringify({ accountId: options.accountId, input }),\n });\n } catch (error) {\n throw new IntegrationActionError(\n `Could not reach '${integrationSlug}/${actionName}' (${\n error instanceof Error ? error.message : 'network error'\n }).`,\n { status: 0, code: 'network_error' },\n );\n }\n\n const payload: unknown = await response.json().catch(() => null);\n\n if (response.ok && isSuccessEnvelope(payload)) {\n return payload.data as TOutput;\n }\n\n if (isErrorEnvelope(payload)) {\n throw new IntegrationActionError(payload.error, {\n status: response.status,\n code: payload.code ?? 'handler_error',\n issues: payload.issues,\n });\n }\n\n throw new IntegrationActionError(\n `Could not run '${integrationSlug}/${actionName}' (${response.status} ${response.statusText}).`,\n { status: response.status, code: 'invalid_response' },\n );\n}\n\n/**\n * Invokes a declared partner action from a widget. `mutate`/`mutateAsync`\n * take the action's `TInput` directly — the caller's own already-validated\n * values, no assistant and no natural-language round trip in between. A\n * resolved mutation's `data` is `TOutput`; a rejected one's `error` is always\n * an `IntegrationActionError` (see the module doc comment above).\n */\nexport function useIntegrationAction<TInput = unknown, TOutput = unknown>(\n integrationSlug: string,\n actionName: string,\n options: UseIntegrationActionOptions,\n): UseMutationResult<TOutput, IntegrationActionError, TInput> {\n return useMutation({\n mutationFn: (input: TInput) =>\n runIntegrationAction<TInput, TOutput>(\n integrationSlug,\n actionName,\n input,\n options,\n ),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-integration-action.js","sourceRoot":"","sources":["../../src/hooks/use-integration-action.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n/**\n * The browser half of the widget-actions transport.\n *\n * A partner's `actions` declaration on `IntegrationDefinition` is a\n * record of named, deterministic server handlers —\n * `(ctx, input) => Promise<unknown>` — invoked by a widget's own UI (a Save\n * button), never synthesized by the AI assistant. The host serves one\n * generic route per action:\n *\n * POST /api/integrations/[slug]/actions/[actionName]\n *\n * and this hook is the only place a partner package should build that\n * request, so a route move is one SDK change rather than a hunt through\n * every widget — the same discipline `partnerOAuthConnectPath`/\n * `fetchIntegrationStorage` already apply to their own routes.\n *\n * ```tsx\n * const createRecord = useIntegrationAction<CreateRecordInput, CreateRecordOutput>(\n * 'acme-crm',\n * 'create-record',\n * { accountId },\n * );\n *\n * <Button onClick={() => createRecord.mutate(formValues)}>Save</Button>\n * ```\n *\n * ── Typing is a convention, not a cross-package mechanism ───────────────────\n *\n * `@ekanos/sdk` is a separately published package — it cannot see a specific\n * partner's `z.ZodTypeAny` instance, since that object lives in the\n * partner's own server bundle and never ships to the browser, let alone into\n * the SDK's own compiled types. `TInput`/`TOutput` are generic parameters\n * the CALLER supplies by hand at the call site — typically by also exporting\n * `type CreateRecordInput = z.infer<typeof createRecordSchema>` from the same\n * package that declares the action and importing it into the widget. This\n * hook treats the wire payload as `unknown` at runtime — it never parses it\n * against the partner's own zod schema, only against the success/error\n * envelope shape — so drift between the declared action and the widget's\n * type import is a partner-package refactor problem, not a network-boundary\n * one. Same discipline `fetchIntegrationStorage` already documents for\n * storage (\"Values are returned raw and unvalidated\").\n *\n * ── Errors are typed, never raw fetch/transport exceptions ──────────────────\n *\n * A rejected mutation's `error` is always an `IntegrationActionError` —\n * never a bare `Error`, a `Response`, or an unwrapped parse/network\n * exception. `status` and `code` are always present; `issues` (the partner's\n * own zod issues) is present only for the route's 400 `invalid_input`\n * response, so a form can map failures back onto fields without\n * string-matching a message.\n */\nimport { type UseMutationResult, useMutation } from '@tanstack/react-query';\n\nconst DEFAULT_BASE_URL = '/api/integrations';\n\nexport interface UseIntegrationActionOptions {\n /** The account the action runs against. Must be one the caller belongs to. */\n accountId: string;\n /**\n * Overrides the base path. Exists for harnesses and tests that serve the\n * route from somewhere other than the host's own origin; production code\n * should leave it alone.\n */\n baseUrl?: string;\n}\n\n/**\n * Builds the host actions-route URL for one integration/action pair.\n * Exported because a harness or a test double needs to recognize the exact\n * URL this hook will request.\n */\nexport function integrationActionUrl(\n integrationSlug: string,\n actionName: string,\n options: { baseUrl?: string } = {},\n): string {\n const base = options.baseUrl ?? DEFAULT_BASE_URL;\n return `${base}/${integrationSlug}/actions/${actionName}`;\n}\n\n/**\n * Route-defined failure codes the host actions route answers with, plus two\n * transport-level codes this hook assigns itself when the response never\n * reaches a valid envelope: `'network_error'` (the request itself failed —\n * offline, DNS, aborted) and `'invalid_response'` (a response came back but\n * its body isn't the envelope shape this transport expects).\n */\nexport type IntegrationActionErrorCode =\n | 'action_not_found'\n | 'rate_limited'\n | 'invalid_input'\n | 'handler_error'\n | 'network_error'\n | 'invalid_response'\n | (string & {});\n\nexport interface IntegrationActionErrorDetails {\n /** HTTP status; `0` means the request never reached the server. */\n status: number;\n code: IntegrationActionErrorCode;\n /** The partner's own zod issues — present only for a 400 `invalid_input` response. */\n issues?: unknown[];\n}\n\n/**\n * The typed failure a rejected `useIntegrationAction` mutation always throws.\n * `status`/`code` let a caller branch without string-matching `message`;\n * `issues` lets a form map the partner's own validation failures back onto\n * fields.\n */\nexport class IntegrationActionError extends Error {\n readonly status: number;\n readonly code: IntegrationActionErrorCode;\n readonly issues?: unknown[];\n\n constructor(message: string, details: IntegrationActionErrorDetails) {\n super(message);\n this.name = 'IntegrationActionError';\n this.status = details.status;\n this.code = details.code;\n this.issues = details.issues;\n }\n}\n\ninterface ActionSuccessEnvelope {\n success: true;\n data: unknown;\n}\n\ninterface ActionErrorEnvelope {\n success: false;\n error: string;\n code?: string;\n issues?: unknown[];\n}\n\nfunction isSuccessEnvelope(value: unknown): value is ActionSuccessEnvelope {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { success?: unknown }).success === true &&\n 'data' in value\n );\n}\n\nfunction isErrorEnvelope(value: unknown): value is ActionErrorEnvelope {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { success?: unknown }).success === false &&\n typeof (value as { error?: unknown }).error === 'string'\n );\n}\n\nasync function runIntegrationAction<TInput, TOutput>(\n integrationSlug: string,\n actionName: string,\n input: TInput,\n options: UseIntegrationActionOptions,\n): Promise<TOutput> {\n const url = integrationActionUrl(integrationSlug, actionName, options);\n\n let response: Response;\n try {\n response = await globalThis.fetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'application/json',\n },\n body: JSON.stringify({ accountId: options.accountId, input }),\n });\n } catch (error) {\n throw new IntegrationActionError(\n `Could not reach '${integrationSlug}/${actionName}' (${\n error instanceof Error ? error.message : 'network error'\n }).`,\n { status: 0, code: 'network_error' },\n );\n }\n\n const payload: unknown = await response.json().catch(() => null);\n\n if (response.ok && isSuccessEnvelope(payload)) {\n return payload.data as TOutput;\n }\n\n if (isErrorEnvelope(payload)) {\n throw new IntegrationActionError(payload.error, {\n status: response.status,\n code: payload.code ?? 'handler_error',\n issues: payload.issues,\n });\n }\n\n throw new IntegrationActionError(\n `Could not run '${integrationSlug}/${actionName}' (${response.status} ${response.statusText}).`,\n { status: response.status, code: 'invalid_response' },\n );\n}\n\n/**\n * Invokes a declared partner action from a widget. `mutate`/`mutateAsync`\n * take the action's `TInput` directly — the caller's own already-validated\n * values, no assistant and no natural-language round trip in between. A\n * resolved mutation's `data` is `TOutput`; a rejected one's `error` is always\n * an `IntegrationActionError` (see the module doc comment above).\n */\nexport function useIntegrationAction<TInput = unknown, TOutput = unknown>(\n integrationSlug: string,\n actionName: string,\n options: UseIntegrationActionOptions,\n): UseMutationResult<TOutput, IntegrationActionError, TInput> {\n return useMutation({\n mutationFn: (input: TInput) =>\n runIntegrationAction<TInput, TOutput>(\n integrationSlug,\n actionName,\n input,\n options,\n ),\n });\n}\n"]}
|