@hubspot/app-connect-sdk 1.0.0-alpha.20 → 1.0.0-alpha.21

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.
Files changed (41) hide show
  1. package/.turbo/turbo-format$colon$check.log +1 -1
  2. package/.turbo/turbo-test.log +59 -56
  3. package/.turbo/turbo-tsdown.log +18 -16
  4. package/build/tsconfig.browser.tsbuildinfo +1 -1
  5. package/build/tsconfig.server.tsbuildinfo +1 -1
  6. package/dist/browser/{HubSpotAppConnect-DFe9b90e.js → HubSpotAppConnect-721kYr9d.js} +14 -21
  7. package/dist/browser/{HubSpotAppConnect-DFe9b90e.js.map → HubSpotAppConnect-721kYr9d.js.map} +1 -1
  8. package/dist/browser/{create-BNQazCF-.js → create-DxEyGG-k.js} +82 -37
  9. package/dist/browser/create-DxEyGG-k.js.map +1 -0
  10. package/dist/browser/index.d.ts +1 -1
  11. package/dist/browser/index.js +1 -1
  12. package/dist/browser/react/lovable.d.ts +1 -1
  13. package/dist/browser/react/lovable.js +2 -2
  14. package/dist/browser/react.d.ts +1 -1
  15. package/dist/browser/react.js +1 -1
  16. package/dist/browser/{types-DkAmHcZt.d.ts → types-C3wed8dU.d.ts} +31 -1
  17. package/dist/server/hono/hubspot-connect-routes/auth-complete.js +4 -1
  18. package/dist/server/hono/hubspot-connect-routes/auth-complete.js.map +1 -1
  19. package/dist/server/hono/hubspot-connect-routes/whoami.js +51 -0
  20. package/dist/server/hono/hubspot-connect-routes/whoami.js.map +1 -0
  21. package/package.json +3 -3
  22. package/src/browser/app-connect-controller/connect-start.test.ts +1 -0
  23. package/src/browser/app-connect-controller/constants.ts +6 -4
  24. package/src/browser/app-connect-controller/create.ts +8 -2
  25. package/src/browser/app-connect-controller/disconnect.ts +1 -0
  26. package/src/browser/app-connect-controller/init.test.ts +30 -15
  27. package/src/browser/app-connect-controller/init.ts +2 -2
  28. package/src/browser/app-connect-controller/oauth-complete.test.ts +7 -3
  29. package/src/browser/app-connect-controller/oauth-popup.test.ts +1 -0
  30. package/src/browser/app-connect-controller/types.ts +3 -0
  31. package/src/browser/app-connect-controller/utils/session-utils.test.ts +73 -22
  32. package/src/browser/app-connect-controller/utils/session-utils.ts +74 -33
  33. package/src/browser/app-connect-controller/view-state.test.ts +1 -0
  34. package/src/browser/app-connect-controller/view-state.ts +1 -0
  35. package/src/browser/react/components/AppConnectHeader/AppConnectHeader.tsx +18 -29
  36. package/src/browser/types.ts +9 -0
  37. package/src/server/hono/hubspot-connect-routes/auth-complete.test.ts +129 -20
  38. package/src/server/hono/hubspot-connect-routes/auth-complete.ts +8 -1
  39. package/src/server/hono/hubspot-connect-routes/whoami.ts +74 -0
  40. package/src/shared/wire-types.ts +30 -0
  41. package/dist/browser/create-BNQazCF-.js.map +0 -1
@@ -289,17 +289,6 @@ var styles$3 = {
289
289
  };
290
290
  //#endregion
291
291
  //#region src/browser/react/components/AppConnectHeader/AppConnectHeader.tsx
292
- const FAKE_USER = {
293
- firstName: "Gabby",
294
- lastName: "Martinez",
295
- email: "gabby.martinez@acmecorp.com"
296
- };
297
- function getUserInitials(user) {
298
- return `${user.firstName.charAt(0).toUpperCase()}${user.lastName.charAt(0).toUpperCase()}`;
299
- }
300
- function getFullName(user) {
301
- return `${user.firstName} ${user.lastName}`;
302
- }
303
292
  function AppConnectHeader({ title }) {
304
293
  const { status } = useHubSpotAppConnect();
305
294
  const connectButton = status === "initializing" ? null : /* @__PURE__ */ jsx(ConnectButton, { variant: "secondary" });
@@ -321,6 +310,8 @@ function AppConnectHeader({ title }) {
321
310
  });
322
311
  }
323
312
  function ViewingHubSpotContextRow() {
313
+ const { whoami } = useHubSpotAppConnect();
314
+ const hubLabel = whoami?.hub?.domain ?? whoami?.hub?.id ?? "HubSpot";
324
315
  return /* @__PURE__ */ jsxs("div", {
325
316
  className: styles$3.contextRow,
326
317
  children: [
@@ -335,15 +326,17 @@ function ViewingHubSpotContextRow() {
335
326
  onClick: (event) => {
336
327
  event.preventDefault();
337
328
  },
338
- children: ["Acme Corp · HubSpot", /* @__PURE__ */ jsx(ExternalLinkIcon, { className: styles$3.contextExternalIcon })]
329
+ children: [hubLabel, /* @__PURE__ */ jsx(ExternalLinkIcon, { className: styles$3.contextExternalIcon })]
339
330
  })
340
331
  ]
341
332
  });
342
333
  }
343
334
  function UserMenu() {
344
- const { disconnectFromHubSpot } = useHubSpotAppConnect();
345
- const initials = getUserInitials(FAKE_USER);
346
- const fullName = getFullName(FAKE_USER);
335
+ const { whoami, disconnectFromHubSpot } = useHubSpotAppConnect();
336
+ const user = whoami?.user;
337
+ const nameParts = [user?.firstName, user?.lastName].filter((p) => typeof p === "string" && p.length > 0);
338
+ const initials = nameParts.map((n) => n[0].toUpperCase()).join("") || user?.email?.[0]?.toUpperCase() || "?";
339
+ const displayName = nameParts.join(" ") || user?.email || "HubSpot User";
347
340
  return /* @__PURE__ */ jsxs(Menu.Root, {
348
341
  modal: false,
349
342
  children: [/* @__PURE__ */ jsxs(Menu.Trigger, {
@@ -356,7 +349,7 @@ function UserMenu() {
356
349
  }),
357
350
  /* @__PURE__ */ jsx("span", {
358
351
  className: styles$3.triggerName,
359
- children: fullName
352
+ children: displayName
360
353
  }),
361
354
  /* @__PURE__ */ jsx(ChevronDownIcon, { className: styles$3.chevron })
362
355
  ]
@@ -376,11 +369,11 @@ function UserMenu() {
376
369
  className: styles$3.userInfoText,
377
370
  children: [/* @__PURE__ */ jsx("span", {
378
371
  className: styles$3.userInfoName,
379
- children: fullName
380
- }), /* @__PURE__ */ jsx("span", {
372
+ children: displayName
373
+ }), user?.email ? /* @__PURE__ */ jsx("span", {
381
374
  className: styles$3.userInfoEmail,
382
- children: FAKE_USER.email
383
- })]
375
+ children: user.email
376
+ }) : null]
384
377
  })]
385
378
  }),
386
379
  /* @__PURE__ */ jsx(Menu.Separator, { className: styles$3.separator }),
@@ -487,4 +480,4 @@ function HubSpotAppConnectContent({ connected, disconnectedMessage }) {
487
480
  //#endregion
488
481
  export { useHubSpotAppConnect as n, HubSpotAppConnect as t };
489
482
 
490
- //# sourceMappingURL=HubSpotAppConnect-DFe9b90e.js.map
483
+ //# sourceMappingURL=HubSpotAppConnect-721kYr9d.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"HubSpotAppConnect-DFe9b90e.js","names":["styles.root","styles.variant","variant","styles.size","size","styles","styles","styles","styles"],"sources":["../../src/browser/react/context.ts","../../src/browser/react/hooks.ts","../../src/browser/react/components/Button/Button.css.ts","../../src/browser/react/components/Button/Button.tsx","../../src/browser/react/components/ConnectButton/ConnectButton.css.ts","../../src/browser/react/components/ConnectButton/ConnectButton.tsx","../../src/browser/react/components/icons/ChevronDownIcon.tsx","../../src/browser/react/components/icons/ExternalLinkIcon.tsx","../../src/browser/react/components/icons/HubSpotDataSourceIcon.tsx","../../src/browser/react/components/icons/LogoutIcon.tsx","../../src/browser/react/components/icons/ShareIcon.tsx","../../src/browser/react/components/ShareButton/ShareButton.css.ts","../../src/browser/react/components/ShareButton/ShareButton.tsx","../../src/browser/react/components/AppConnectHeader/AppConnectHeader.css.ts","../../src/browser/react/components/AppConnectHeader/AppConnectHeader.tsx","../../src/browser/react/components/DisconnectedBody/DisconnectedBody.css.ts","../../src/browser/react/components/DisconnectedBody/DisconnectedBody.tsx","../../src/browser/react/components/LoadingIndicator/LoadingIndicator.css.ts","../../src/browser/react/components/LoadingIndicator/LoadingIndicator.tsx","../../src/browser/react/components/HubSpotAppConnect/HubSpotAppConnect.css.ts","../../src/browser/react/components/HubSpotAppConnect/HubSpotAppConnect.tsx"],"sourcesContent":["import { createContext } from 'react';\n\nimport type { AppConnectController } from '../types.ts';\n\n/**\n * React context that carries the `AppConnectController` from the\n * `HubSpotAppConnect` provider down to consumers of\n * `useHubSpotAppConnect`. `null` indicates the hook is being used\n * outside a provider.\n */\nexport const HubSpotAppConnectControllerContext =\n createContext<AppConnectController | null>(null);\n","import { useContext, useSyncExternalStore } from 'react';\n\nimport type { AppConnectState } from '../types.ts';\nimport { HubSpotAppConnectControllerContext } from './context.ts';\n\nexport type UseHubSpotAppConnectResult = AppConnectState;\n\n/**\n * React hook that returns the current `AppConnectState`. Must be\n * called inside a {@link HubSpotAppConnect} provider — throws when\n * no controller is available.\n *\n * The hook subscribes to the controller via `useSyncExternalStore`\n * so React 18+ batched updates and SSR work correctly.\n */\nexport function useHubSpotAppConnect(): UseHubSpotAppConnectResult {\n const controller = useContext(HubSpotAppConnectControllerContext);\n if (controller == null) {\n throw new Error(\n 'useHubSpotAppConnect must be used within HubSpotAppConnect'\n );\n }\n return useSyncExternalStore(\n controller.subscribe,\n controller.getSnapshot,\n controller.getServerSnapshot\n );\n}\n","import { style, styleVariants } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nexport const root = style({\n appearance: 'none',\n WebkitAppearance: 'none',\n margin: 0,\n font: 'inherit',\n fontWeight: 500,\n lineHeight: 'inherit',\n textAlign: 'center',\n cursor: 'pointer',\n borderWidth: 1,\n borderStyle: 'solid',\n borderRadius: themeVars.borderRadius[100],\n transition:\n 'background-color 120ms ease, border-color 120ms ease, color 120ms ease, opacity 120ms ease',\n selectors: {\n '&:disabled': {\n cursor: 'wait',\n opacity: 0.55,\n },\n },\n});\n\nexport const variant = styleVariants({\n primary: {\n backgroundColor: themeVars.fill.primary.default,\n color: themeVars.text.primary.default,\n borderColor: themeVars.border.primary.default,\n selectors: {\n '&:hover:not(:disabled)': {\n filter: 'brightness(0.95)',\n },\n },\n },\n secondary: {\n backgroundColor: themeVars.fill.surface.default.default,\n color: themeVars.text.core.default,\n borderColor: themeVars.border.core.default,\n selectors: {\n '&:hover:not(:disabled)': {\n backgroundColor: '#f7f7f7',\n },\n },\n },\n});\n\nexport const size = styleVariants({\n md: {\n padding: `${themeVars.space[200]} ${themeVars.space[300]}`,\n fontSize: 14,\n },\n lg: {\n padding: `${themeVars.space[300]} ${themeVars.space[500]}`,\n fontSize: 16,\n },\n});\n","import type { ButtonHTMLAttributes, ReactNode } from 'react';\n\nimport * as styles from './Button.css.ts';\n\nexport type ButtonVariant = 'primary' | 'secondary';\nexport type ButtonSize = 'md' | 'lg';\n\nexport interface ButtonProps extends Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'children'\n> {\n children: ReactNode;\n variant?: ButtonVariant;\n size?: ButtonSize;\n}\n\nexport function Button({\n children,\n className,\n variant = 'primary',\n size = 'md',\n type = 'button',\n ...rest\n}: ButtonProps) {\n const composedClassName = [\n styles.root,\n styles.variant[variant],\n styles.size[size],\n className,\n ]\n .filter(Boolean)\n .join(' ');\n return (\n <button {...rest} type={type} className={composedClassName}>\n {children}\n </button>\n );\n}\n","import { keyframes, style, styleVariants } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nconst spin = keyframes({\n from: { transform: 'rotate(0deg)' },\n to: { transform: 'rotate(360deg)' },\n});\n\nexport const root = style({\n position: 'relative',\n});\n\nexport const label = style({\n position: 'relative',\n zIndex: 0,\n});\n\nexport const labelMuted = style({\n opacity: 0.22,\n});\n\nexport const loadingBackdrop = styleVariants({\n primary: {\n position: 'absolute',\n left: '50%',\n top: '50%',\n zIndex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 36,\n height: 36,\n marginLeft: -18,\n marginTop: -18,\n borderRadius: '50%',\n backgroundColor: 'rgba(255, 255, 255, 0.88)',\n boxShadow: '0 1px 6px rgba(0, 0, 0, 0.14)',\n pointerEvents: 'none',\n },\n secondary: {\n position: 'absolute',\n left: '50%',\n top: '50%',\n zIndex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 36,\n height: 36,\n marginLeft: -18,\n marginTop: -18,\n borderRadius: '50%',\n backgroundColor: 'rgba(255, 255, 255, 0.82)',\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.subtle.default,\n boxShadow: '0 1px 4px rgba(0, 0, 0, 0.08)',\n pointerEvents: 'none',\n },\n});\n\nexport const spinner = style({\n display: 'block',\n width: 14,\n height: 14,\n borderWidth: 2,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.subtle.default,\n borderTopColor: themeVars.fill.primary.default,\n borderRadius: '50%',\n animation: `${spin} 0.8s linear infinite`,\n});\n","import { useHubSpotAppConnect } from '../../hooks.ts';\nimport type { ButtonProps } from '../Button/Button.tsx';\nimport { Button } from '../Button/Button.tsx';\nimport {\n label,\n labelMuted,\n loadingBackdrop,\n root,\n spinner,\n} from './ConnectButton.css.ts';\n\nexport interface ConnectButtonProps extends Pick<\n ButtonProps,\n 'variant' | 'size' | 'className'\n> {}\n\nexport function ConnectButton({\n variant = 'primary',\n size = 'md',\n className,\n}: ConnectButtonProps) {\n const { status, connectToHubSpot } = useHubSpotAppConnect();\n const isConnecting = status === 'connecting' || status === 'initializing';\n const composedClassName = [root, className].filter(Boolean).join(' ');\n const labelClassName = isConnecting ? `${label} ${labelMuted}` : label;\n return (\n <Button\n variant={variant}\n size={size}\n className={composedClassName}\n aria-busy={isConnecting}\n onClick={() => void connectToHubSpot()}\n disabled={isConnecting}\n >\n <span className={labelClassName}>Connect to HubSpot</span>\n {isConnecting ? (\n <span className={loadingBackdrop[variant]} aria-hidden=\"true\">\n <span className={spinner} />\n </span>\n ) : null}\n </Button>\n );\n}\n","export interface ChevronDownIconProps {\n className?: string;\n}\n\nexport function ChevronDownIcon({ className }: ChevronDownIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 6l4 4 4-4\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","export interface ExternalLinkIconProps {\n className?: string;\n}\n\nexport function ExternalLinkIcon({ className }: ExternalLinkIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6.5 3.5H4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2.5M10 2.5h3.5V6M9 7l5-5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.25\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","export interface HubSpotDataSourceIconProps {\n className?: string;\n}\n\nexport function HubSpotDataSourceIcon({\n className,\n}: HubSpotDataSourceIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"2.5\"\n y=\"2.5\"\n width=\"11\"\n height=\"11\"\n rx=\"1.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.25\"\n />\n <path\n d=\"M5 6h6M5 8.25h6M5 10.5h4\"\n stroke=\"currentColor\"\n strokeWidth=\"1.25\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n","export interface LogoutIconProps {\n className?: string;\n}\n\nexport function LogoutIcon({ className }: LogoutIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6.5 2.5h-3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h3\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M10.5 11l3-3-3-3\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M13.5 8h-7\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","export interface ShareIconProps {\n className?: string;\n}\n\nexport function ShareIcon({ className }: ShareIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <circle cx=\"18\" cy=\"5\" r=\"2.75\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <circle cx=\"6\" cy=\"12\" r=\"2.75\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <circle\n cx=\"18\"\n cy=\"19\"\n r=\"2.75\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <path\n d=\"M8.6 10.5L15.4 6.5M8.6 13.5L15.4 17.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n","import { style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nconst shareBorder = '#cbd6e2';\nconst shareText = '#33475b';\n\nexport const styles = {\n shareButton: style({\n flexShrink: 0,\n appearance: 'none',\n WebkitAppearance: 'none',\n margin: 0,\n fontFamily: 'inherit',\n fontSize: 14,\n fontWeight: 500,\n lineHeight: 1,\n cursor: 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: themeVars.space[200],\n padding: `8px ${themeVars.space[300]}`,\n color: shareText,\n backgroundColor: themeVars.fill.surface.default.default,\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: shareBorder,\n borderRadius: 100,\n transition:\n 'background-color 120ms ease, border-color 120ms ease, color 120ms ease',\n selectors: {\n '&:hover:not(:disabled)': {\n backgroundColor: '#f5f8fa',\n },\n '&:focus-visible': {\n outline: `2px solid ${themeVars.border.tertiary.default}`,\n outlineOffset: 2,\n },\n '&:disabled': {\n cursor: 'not-allowed',\n opacity: 0.55,\n },\n },\n }),\n shareIcon: style({\n width: 16,\n height: 16,\n flexShrink: 0,\n display: 'block',\n }),\n} as const;\n","import { ShareIcon } from '../icons/ShareIcon.tsx';\nimport { styles } from './ShareButton.css.ts';\n\ninterface ShareAppConnectPageOptions {\n pageTitle: string;\n}\n\nasync function shareAppConnectPage(\n options: ShareAppConnectPageOptions\n): Promise<void> {\n const { pageTitle } = options;\n const url = window.location.href;\n if (typeof navigator.share === 'function') {\n try {\n await navigator.share({ title: pageTitle, text: pageTitle, url });\n return;\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n return;\n }\n }\n }\n if (typeof navigator.clipboard?.writeText === 'function') {\n await navigator.clipboard.writeText(url);\n }\n}\n\nexport interface ShareButtonProps {\n pageTitle: string;\n}\n\nexport function ShareButton({ pageTitle }: ShareButtonProps) {\n return (\n <button\n type=\"button\"\n className={styles.shareButton}\n onClick={() => void shareAppConnectPage({ pageTitle })}\n >\n <ShareIcon className={styles.shareIcon} />\n <span>Share</span>\n </button>\n );\n}\n","import { style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nconst avatarOrange = themeVars.fill.brand.default;\nconst avatarTextColor = themeVars.text.primary.default;\n\nexport const styles = {\n header: style({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: themeVars.space[300],\n paddingBottom: themeVars.space[300],\n borderBottomWidth: 1,\n borderBottomStyle: 'solid',\n borderBottomColor: themeVars.border.core.subtle.default,\n }),\n titleRow: style({\n display: 'flex',\n alignItems: 'center',\n flex: 1,\n minWidth: 0,\n }),\n leftStack: style({\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n gap: themeVars.space[200],\n minWidth: 0,\n flex: 1,\n }),\n contextRow: style({\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n fontSize: 14,\n lineHeight: 1.35,\n color: themeVars.text.core.subtle,\n minWidth: 0,\n }),\n contextIcon: style({\n width: 16,\n height: 16,\n flexShrink: 0,\n color: themeVars.text.core.subtle,\n }),\n contextPrefix: style({\n flexShrink: 0,\n }),\n contextLink: style({\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n color: themeVars.fill.brand.default,\n textDecoration: 'none',\n fontWeight: 500,\n selectors: {\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n }),\n contextExternalIcon: style({\n width: 14,\n height: 14,\n flexShrink: 0,\n }),\n titleCluster: style({\n display: 'flex',\n alignItems: 'center',\n gap: themeVars.space[200],\n width: 'max-content',\n maxWidth: '100%',\n minWidth: 0,\n }),\n title: style({\n flex: '0 1 auto',\n fontSize: 20,\n fontWeight: 600,\n margin: 0,\n color: themeVars.text.core.default,\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }),\n userTrigger: style({\n appearance: 'none',\n WebkitAppearance: 'none',\n margin: 0,\n font: 'inherit',\n lineHeight: 1,\n cursor: 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n gap: themeVars.space[200],\n padding: `6px ${themeVars.space[300]} 6px 6px`,\n backgroundColor: themeVars.fill.surface.default.default,\n color: themeVars.text.core.default,\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.default,\n borderRadius: 999,\n transition: 'background-color 120ms ease, border-color 120ms ease',\n selectors: {\n '&:hover': {\n backgroundColor: '#f7f7f7',\n },\n '&[data-popup-open]': {\n backgroundColor: '#f7f7f7',\n },\n },\n }),\n triggerName: style({\n fontWeight: 500,\n fontSize: 14,\n }),\n chevron: style({\n width: 14,\n height: 14,\n color: themeVars.text.core.subtle,\n transition: 'transform 150ms ease',\n selectors: {\n '[data-popup-open] &': {\n transform: 'rotate(180deg)',\n },\n },\n }),\n avatar: style({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: avatarOrange,\n color: avatarTextColor,\n fontWeight: 600,\n flexShrink: 0,\n borderRadius: '50%',\n userSelect: 'none',\n }),\n avatarSm: style({\n width: 28,\n height: 28,\n fontSize: 11,\n }),\n avatarLg: style({\n width: 40,\n height: 40,\n fontSize: 14,\n }),\n popup: style({\n minWidth: 240,\n backgroundColor: themeVars.fill.surface.default.default,\n color: themeVars.text.core.default,\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.subtle.default,\n borderRadius: themeVars.borderRadius[300],\n boxShadow:\n '0 6px 16px rgba(17, 17, 17, 0.08), 0 2px 6px rgba(17, 17, 17, 0.04)',\n padding: `${themeVars.space[200]} 0`,\n outline: 'none',\n transformOrigin: 'top right',\n opacity: 0,\n transform: 'scale(0.96)',\n transition: 'opacity 120ms ease, transform 120ms ease',\n selectors: {\n '&[data-starting-style]': {\n opacity: 0,\n transform: 'scale(0.96)',\n },\n '&[data-open]': {\n opacity: 1,\n transform: 'scale(1)',\n },\n },\n }),\n userInfo: style({\n display: 'flex',\n alignItems: 'center',\n gap: themeVars.space[300],\n padding: `${themeVars.space[200]} ${themeVars.space[300]}`,\n }),\n userInfoText: style({\n display: 'flex',\n flexDirection: 'column',\n minWidth: 0,\n }),\n userInfoName: style({\n fontWeight: 600,\n fontSize: 14,\n color: themeVars.text.core.default,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }),\n userInfoEmail: style({\n fontSize: 13,\n color: themeVars.text.core.subtle,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }),\n separator: style({\n height: 1,\n margin: `${themeVars.space[200]} 0`,\n backgroundColor: themeVars.border.core.subtle.default,\n border: 'none',\n }),\n disconnectItem: style({\n display: 'flex',\n alignItems: 'center',\n gap: themeVars.space[200],\n padding: `${themeVars.space[200]} ${themeVars.space[300]}`,\n color: themeVars.text.alert.default,\n fontSize: 14,\n fontWeight: 500,\n cursor: 'pointer',\n outline: 'none',\n userSelect: 'none',\n selectors: {\n '&[data-highlighted]': {\n backgroundColor: themeVars.fill.accent.red.subtle.default,\n },\n },\n }),\n disconnectIcon: style({\n width: 16,\n height: 16,\n }),\n} as const;\n","import { Menu } from '@base-ui/react/menu';\n\nimport { useHubSpotAppConnect } from '../../hooks.ts';\nimport { ConnectButton } from '../ConnectButton/ConnectButton.tsx';\nimport { ChevronDownIcon } from '../icons/ChevronDownIcon.tsx';\nimport { ExternalLinkIcon } from '../icons/ExternalLinkIcon.tsx';\nimport { HubSpotDataSourceIcon } from '../icons/HubSpotDataSourceIcon.tsx';\nimport { LogoutIcon } from '../icons/LogoutIcon.tsx';\nimport { ShareButton } from '../ShareButton/ShareButton.tsx';\nimport { styles } from './AppConnectHeader.css.ts';\n\ninterface FakeUser {\n firstName: string;\n lastName: string;\n email: string;\n}\n\nconst FAKE_USER: FakeUser = {\n firstName: 'Gabby',\n lastName: 'Martinez',\n email: 'gabby.martinez@acmecorp.com',\n};\n\nfunction getUserInitials(user: FakeUser): string {\n const first = user.firstName.charAt(0).toUpperCase();\n const last = user.lastName.charAt(0).toUpperCase();\n return `${first}${last}`;\n}\n\nfunction getFullName(user: FakeUser): string {\n return `${user.firstName} ${user.lastName}`;\n}\n\ninterface AppConnectHeaderProps {\n title: string;\n}\n\nexport function AppConnectHeader({ title }: AppConnectHeaderProps) {\n const { status } = useHubSpotAppConnect();\n const connectButton =\n status === 'initializing' ? null : <ConnectButton variant=\"secondary\" />;\n return (\n <header className={styles.header}>\n <div className={styles.titleRow}>\n <div className={styles.leftStack}>\n <div className={styles.titleCluster}>\n <h1 className={styles.title}>{title}</h1>\n <ShareButton pageTitle={title} />\n </div>\n {status === 'connected' ? <ViewingHubSpotContextRow /> : null}\n </div>\n </div>\n {status === 'connected' ? <UserMenu /> : connectButton}\n </header>\n );\n}\n\nfunction ViewingHubSpotContextRow() {\n return (\n <div className={styles.contextRow}>\n <HubSpotDataSourceIcon className={styles.contextIcon} />\n <span className={styles.contextPrefix}>Viewing HubSpot data from </span>\n <a\n className={styles.contextLink}\n href=\"#\"\n onClick={(event) => {\n event.preventDefault();\n }}\n >\n Acme Corp · HubSpot\n <ExternalLinkIcon className={styles.contextExternalIcon} />\n </a>\n </div>\n );\n}\n\nfunction UserMenu() {\n const { disconnectFromHubSpot } = useHubSpotAppConnect();\n const initials = getUserInitials(FAKE_USER);\n const fullName = getFullName(FAKE_USER);\n\n return (\n <Menu.Root modal={false}>\n <Menu.Trigger className={styles.userTrigger}>\n <span\n className={`${styles.avatar} ${styles.avatarSm}`}\n aria-hidden=\"true\"\n >\n {initials}\n </span>\n <span className={styles.triggerName}>{fullName}</span>\n <ChevronDownIcon className={styles.chevron} />\n </Menu.Trigger>\n <Menu.Portal>\n <Menu.Positioner sideOffset={8} align=\"end\">\n <Menu.Popup className={styles.popup}>\n <div className={styles.userInfo}>\n <span\n className={`${styles.avatar} ${styles.avatarLg}`}\n aria-hidden=\"true\"\n >\n {initials}\n </span>\n <div className={styles.userInfoText}>\n <span className={styles.userInfoName}>{fullName}</span>\n <span className={styles.userInfoEmail}>{FAKE_USER.email}</span>\n </div>\n </div>\n <Menu.Separator className={styles.separator} />\n <Menu.Item\n className={styles.disconnectItem}\n onClick={() => void disconnectFromHubSpot()}\n >\n <LogoutIcon className={styles.disconnectIcon} />\n Disconnect\n </Menu.Item>\n </Menu.Popup>\n </Menu.Positioner>\n </Menu.Portal>\n </Menu.Root>\n );\n}\n","import { style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nexport const styles = {\n card: style({\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.default,\n borderRadius: themeVars.borderRadius[400],\n padding: themeVars.space[500],\n textAlign: 'center',\n backgroundColor: themeVars.fill.surface.default.default,\n color: themeVars.text.core.default,\n }),\n message: style({\n marginTop: 0,\n marginBottom: themeVars.space[400],\n }),\n errorText: style({\n marginTop: themeVars.space[300],\n marginBottom: 0,\n color: themeVars.text.alert.default,\n }),\n} as const;\n","import type { ReactNode } from 'react';\n\nimport { useHubSpotAppConnect } from '../../hooks.ts';\nimport { ConnectButton } from '../ConnectButton/ConnectButton.tsx';\nimport { styles } from './DisconnectedBody.css.ts';\n\ninterface DisconnectedBodyProps {\n message: ReactNode;\n}\n\nexport function DisconnectedBody({ message }: DisconnectedBodyProps) {\n const { error } = useHubSpotAppConnect();\n\n return (\n <div className={styles.card}>\n <p className={styles.message}>{message}</p>\n <ConnectButton size=\"lg\" />\n {error && (\n <p className={styles.errorText}>\n Failed to connect to HubSpot. {error}\n </p>\n )}\n </div>\n );\n}\n","import { keyframes, style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nconst spin = keyframes({\n from: { transform: 'rotate(0deg)' },\n to: { transform: 'rotate(360deg)' },\n});\n\nexport const styles = {\n wrapper: style({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: 200,\n padding: themeVars.space[600],\n }),\n spinner: style({\n width: 40,\n height: 40,\n borderWidth: 3,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.subtle.default,\n borderTopColor: themeVars.fill.primary.default,\n borderRadius: '50%',\n animation: `${spin} 0.8s linear infinite`,\n }),\n} as const;\n","import { styles } from './LoadingIndicator.css.ts';\n\nexport function LoadingIndicator() {\n return (\n <div className={styles.wrapper} role=\"status\" aria-label=\"Loading\">\n <div className={styles.spinner} />\n </div>\n );\n}\n","import { style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nexport const styles = {\n shell: style({\n width: '100%',\n padding: `${themeVars.space[400]} ${themeVars.space[500]}`,\n }),\n content: style({\n marginTop: themeVars.space[500],\n }),\n connectedErrorBanner: style({\n backgroundColor: themeVars.fill.alert.subtle,\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.alert.default,\n borderRadius: themeVars.borderRadius[300],\n padding: themeVars.space[300],\n marginBottom: themeVars.space[300],\n color: themeVars.text.alert.default,\n }),\n} as const;\n","import { useEffect, type ReactNode } from 'react';\n\nimport { themeClass } from '../../../theme.css.ts';\nimport type { AppConnectController } from '../../../types.ts';\nimport { HubSpotAppConnectControllerContext } from '../../context.ts';\nimport { useHubSpotAppConnect } from '../../hooks.ts';\nimport { AppConnectHeader } from '../AppConnectHeader/AppConnectHeader.tsx';\nimport { DisconnectedBody } from '../DisconnectedBody/DisconnectedBody.tsx';\nimport { LoadingIndicator } from '../LoadingIndicator/LoadingIndicator.tsx';\nimport { styles } from './HubSpotAppConnect.css.ts';\n\n/**\n * Props accepted by {@link HubSpotAppConnect}.\n */\nexport interface HubSpotAppConnectProps {\n /** Title text rendered in the standard SDK header. */\n title: string;\n /** Controller produced by `createAppConnectController`. */\n controller: AppConnectController;\n /** Content rendered when the controller is in the `connected` state. */\n connected: ReactNode;\n /**\n * Description text rendered inside the SDK-owned disconnected card,\n * above the primary \"Connect to HubSpot\" button.\n */\n disconnectedMessage: ReactNode;\n}\n\n/**\n * Layout component that exposes `controller` to {@link useHubSpotAppConnect},\n * starts it once on mount, and renders a standard header plus the content\n * slot that matches the current connection status.\n */\nexport function HubSpotAppConnect({\n title,\n controller,\n connected,\n disconnectedMessage,\n}: HubSpotAppConnectProps) {\n useEffect(() => {\n controller.start();\n }, [controller]);\n useEffect(() => {\n document.documentElement.classList.add(themeClass);\n return () => {\n document.documentElement.classList.remove(themeClass);\n };\n }, []);\n return (\n <HubSpotAppConnectControllerContext.Provider value={controller}>\n <div className={styles.shell}>\n <AppConnectHeader title={title} />\n <div className={styles.content}>\n <HubSpotAppConnectContent\n connected={connected}\n disconnectedMessage={disconnectedMessage}\n />\n </div>\n </div>\n </HubSpotAppConnectControllerContext.Provider>\n );\n}\n\ninterface HubSpotAppConnectContentProps {\n connected: ReactNode;\n disconnectedMessage: ReactNode;\n}\n\nfunction HubSpotAppConnectContent({\n connected,\n disconnectedMessage,\n}: HubSpotAppConnectContentProps) {\n const { status, error } = useHubSpotAppConnect();\n if (status === 'initializing') {\n return <LoadingIndicator />;\n }\n if (status === 'connected') {\n return (\n <>\n {error ? (\n <div className={styles.connectedErrorBanner} role=\"alert\">\n {error}\n </div>\n ) : null}\n {connected}\n </>\n );\n }\n return <DisconnectedBody message={disconnectedMessage} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAUA,MAAa,qCACX,cAA2C,IAAI;;;;;;;;;;;ACIjD,SAAgB,uBAAmD;CACjE,MAAM,aAAa,WAAW,kCAAkC;CAChE,IAAI,cAAc,MAChB,MAAM,IAAI,MACR,4DACF;CAEF,OAAO,qBACL,WAAW,WACX,WAAW,aACX,WAAW,iBACb;AACF;;;;;;;;;;;;;;AEXA,SAAgB,OAAO,EACrB,UACA,WACA,SAAA,YAAU,WACV,MAAA,SAAO,MACP,OAAO,UACP,GAAG,QACW;CACd,MAAM,oBAAoB;EACxBA;EACAC,QAAeC;EACfC,KAAYC;EACZ;CACF,EACG,OAAO,OAAO,EACd,KAAK,GAAG;CACX,OACE,oBAAC,UAAD;EAAQ,GAAI;EAAY;EAAM,WAAW;EACtC;CACK,CAAA;AAEZ;;;;;;;;;;;;;AErBA,SAAgB,cAAc,EAC5B,UAAU,WACV,OAAO,MACP,aACqB;CACrB,MAAM,EAAE,QAAQ,qBAAqB,qBAAqB;CAC1D,MAAM,eAAe,WAAW,gBAAgB,WAAW;CAG3D,OACE,qBAAC,QAAD;EACW;EACH;EACN,WANsB,CAAC,MAAM,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAMlC;EAC3B,aAAW;EACX,eAAe,KAAK,iBAAiB;EACrC,UAAU;YANZ,CAQE,oBAAC,QAAD;GAAM,WAVa,eAAe,GAAG,MAAM,GAAG,eAAe;aAU5B;EAAwB,CAAA,GACxD,eACC,oBAAC,QAAD;GAAM,WAAW,gBAAgB;GAAU,eAAY;aACrD,oBAAC,QAAD,EAAM,WAAW,QAAU,CAAA;EACvB,CAAA,IACJ,IACE;;AAEZ;;;ACtCA,SAAgB,gBAAgB,EAAE,aAAmC;CACnE,OACE,oBAAC,OAAD;EACa;EACX,SAAQ;EACR,MAAK;EACL,OAAM;EACN,eAAY;YAEZ,oBAAC,QAAD;GACE,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;EAChB,CAAA;CACE,CAAA;AAET;;;AClBA,SAAgB,iBAAiB,EAAE,aAAoC;CACrE,OACE,oBAAC,OAAD;EACa;EACX,SAAQ;EACR,MAAK;EACL,OAAM;EACN,eAAY;YAEZ,oBAAC,QAAD;GACE,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;EAChB,CAAA;CACE,CAAA;AAET;;;AClBA,SAAgB,sBAAsB,EACpC,aAC6B;CAC7B,OACE,qBAAC,OAAD;EACa;EACX,SAAQ;EACR,MAAK;EACL,OAAM;EACN,eAAY;YALd,CAOE,oBAAC,QAAD;GACE,GAAE;GACF,GAAE;GACF,OAAM;GACN,QAAO;GACP,IAAG;GACH,QAAO;GACP,aAAY;EACb,CAAA,GACD,oBAAC,QAAD;GACE,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;EACf,CAAA,CACE;;AAET;;;AC5BA,SAAgB,WAAW,EAAE,aAA8B;CACzD,OACE,qBAAC,OAAD;EACa;EACX,SAAQ;EACR,MAAK;EACL,OAAM;EACN,eAAY;YALd;GAOE,oBAAC,QAAD;IACE,GAAE;IACF,QAAO;IACP,aAAY;IACZ,eAAc;IACd,gBAAe;GAChB,CAAA;GACD,oBAAC,QAAD;IACE,GAAE;IACF,QAAO;IACP,aAAY;IACZ,eAAc;IACd,gBAAe;GAChB,CAAA;GACD,oBAAC,QAAD;IACE,GAAE;IACF,QAAO;IACP,aAAY;IACZ,eAAc;IACd,gBAAe;GAChB,CAAA;EACE;;AAET;;;AChCA,SAAgB,UAAU,EAAE,aAA6B;CACvD,OACE,qBAAC,OAAD;EACa;EACX,SAAQ;EACR,MAAK;EACL,OAAM;EACN,eAAY;YALd;GAOE,oBAAC,UAAD;IAAQ,IAAG;IAAK,IAAG;IAAI,GAAE;IAAO,QAAO;IAAe,aAAY;GAAO,CAAA;GACzE,oBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAK,GAAE;IAAO,QAAO;IAAe,aAAY;GAAO,CAAA;GACzE,oBAAC,UAAD;IACE,IAAG;IACH,IAAG;IACH,GAAE;IACF,QAAO;IACP,aAAY;GACb,CAAA;GACD,oBAAC,QAAD;IACE,GAAE;IACF,QAAO;IACP,aAAY;IACZ,eAAc;GACf,CAAA;EACE;;AAET;;;;;;;;;AEvBA,eAAe,oBACb,SACe;CACf,MAAM,EAAE,cAAc;CACtB,MAAM,MAAM,OAAO,SAAS;CAC5B,IAAI,OAAO,UAAU,UAAU,YAC7B,IAAI;EACF,MAAM,UAAU,MAAM;GAAE,OAAO;GAAW,MAAM;GAAW;EAAI,CAAC;EAChE;CACF,SAAS,OAAO;EACd,IAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAClD;CAEJ;CAEF,IAAI,OAAO,UAAU,WAAW,cAAc,YAC5C,MAAM,UAAU,UAAU,UAAU,GAAG;AAE3C;AAMA,SAAgB,YAAY,EAAE,aAA+B;CAC3D,OACE,qBAAC,UAAD;EACE,MAAK;EACL,WAAWC,SAAO;EAClB,eAAe,KAAK,oBAAoB,EAAE,UAAU,CAAC;YAHvD,CAKE,oBAAC,WAAD,EAAW,WAAWA,SAAO,UAAY,CAAA,GACzC,oBAAC,QAAD,EAAA,UAAM,QAAW,CAAA,CACX;;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEzBA,MAAM,YAAsB;CAC1B,WAAW;CACX,UAAU;CACV,OAAO;AACT;AAEA,SAAS,gBAAgB,MAAwB;CAG/C,OAAO,GAFO,KAAK,UAAU,OAAO,CAAC,EAAE,YAEzB,IADD,KAAK,SAAS,OAAO,CAAC,EAAE,YAChB;AACvB;AAEA,SAAS,YAAY,MAAwB;CAC3C,OAAO,GAAG,KAAK,UAAU,GAAG,KAAK;AACnC;AAMA,SAAgB,iBAAiB,EAAE,SAAgC;CACjE,MAAM,EAAE,WAAW,qBAAqB;CACxC,MAAM,gBACJ,WAAW,iBAAiB,OAAO,oBAAC,eAAD,EAAe,SAAQ,YAAa,CAAA;CACzE,OACE,qBAAC,UAAD;EAAQ,WAAWC,SAAO;YAA1B,CACE,oBAAC,OAAD;GAAK,WAAWA,SAAO;aACrB,qBAAC,OAAD;IAAK,WAAWA,SAAO;cAAvB,CACE,qBAAC,OAAD;KAAK,WAAWA,SAAO;eAAvB,CACE,oBAAC,MAAD;MAAI,WAAWA,SAAO;gBAAQ;KAAU,CAAA,GACxC,oBAAC,aAAD,EAAa,WAAW,MAAQ,CAAA,CAC7B;QACJ,WAAW,cAAc,oBAAC,0BAAD,CAA2B,CAAA,IAAI,IACtD;;EACF,CAAA,GACJ,WAAW,cAAc,oBAAC,UAAD,CAAW,CAAA,IAAI,aACnC;;AAEZ;AAEA,SAAS,2BAA2B;CAClC,OACE,qBAAC,OAAD;EAAK,WAAWA,SAAO;YAAvB;GACE,oBAAC,uBAAD,EAAuB,WAAWA,SAAO,YAAc,CAAA;GACvD,oBAAC,QAAD;IAAM,WAAWA,SAAO;cAAe;GAAgC,CAAA;GACvE,qBAAC,KAAD;IACE,WAAWA,SAAO;IAClB,MAAK;IACL,UAAU,UAAU;KAClB,MAAM,eAAe;IACvB;cALF,CAMC,uBAEC,oBAAC,kBAAD,EAAkB,WAAWA,SAAO,oBAAsB,CAAA,CACzD;;EACA;;AAET;AAEA,SAAS,WAAW;CAClB,MAAM,EAAE,0BAA0B,qBAAqB;CACvD,MAAM,WAAW,gBAAgB,SAAS;CAC1C,MAAM,WAAW,YAAY,SAAS;CAEtC,OACE,qBAAC,KAAK,MAAN;EAAW,OAAO;YAAlB,CACE,qBAAC,KAAK,SAAN;GAAc,WAAWA,SAAO;aAAhC;IACE,oBAAC,QAAD;KACE,WAAW,GAAGA,SAAO,OAAO,GAAGA,SAAO;KACtC,eAAY;eAEX;IACG,CAAA;IACN,oBAAC,QAAD;KAAM,WAAWA,SAAO;eAAc;IAAe,CAAA;IACrD,oBAAC,iBAAD,EAAiB,WAAWA,SAAO,QAAU,CAAA;GACjC;MACd,oBAAC,KAAK,QAAN,EAAA,UACE,oBAAC,KAAK,YAAN;GAAiB,YAAY;GAAG,OAAM;aACpC,qBAAC,KAAK,OAAN;IAAY,WAAWA,SAAO;cAA9B;KACE,qBAAC,OAAD;MAAK,WAAWA,SAAO;gBAAvB,CACE,oBAAC,QAAD;OACE,WAAW,GAAGA,SAAO,OAAO,GAAGA,SAAO;OACtC,eAAY;iBAEX;MACG,CAAA,GACN,qBAAC,OAAD;OAAK,WAAWA,SAAO;iBAAvB,CACE,oBAAC,QAAD;QAAM,WAAWA,SAAO;kBAAe;OAAe,CAAA,GACtD,oBAAC,QAAD;QAAM,WAAWA,SAAO;kBAAgB,UAAU;OAAY,CAAA,CAC3D;QACF;;KACL,oBAAC,KAAK,WAAN,EAAgB,WAAWA,SAAO,UAAY,CAAA;KAC9C,qBAAC,KAAK,MAAN;MACE,WAAWA,SAAO;MAClB,eAAe,KAAK,sBAAsB;gBAF5C,CAIE,oBAAC,YAAD,EAAY,WAAWA,SAAO,eAAiB,CAAA,GAAC,YAEvC;;IACD;;EACG,CAAA,EACN,CAAA,CACJ;;AAEf;;;;;;;;;;AE/GA,SAAgB,iBAAiB,EAAE,WAAkC;CACnE,MAAM,EAAE,UAAU,qBAAqB;CAEvC,OACE,qBAAC,OAAD;EAAK,WAAWC,SAAO;YAAvB;GACE,oBAAC,KAAD;IAAG,WAAWA,SAAO;cAAU;GAAW,CAAA;GAC1C,oBAAC,eAAD,EAAe,MAAK,KAAM,CAAA;GACzB,SACC,qBAAC,KAAD;IAAG,WAAWA,SAAO;cAArB,CAAgC,kCACC,KAC9B;;EAEF;;AAET;;;;;;;;;AEtBA,SAAgB,mBAAmB;CACjC,OACE,oBAAC,OAAD;EAAK,WAAWC,SAAO;EAAS,MAAK;EAAS,cAAW;YACvD,oBAAC,OAAD,EAAK,WAAWA,SAAO,QAAU,CAAA;CAC9B,CAAA;AAET;;;;;;;;;;;;;;;AEyBA,SAAgB,kBAAkB,EAChC,OACA,YACA,WACA,uBACyB;CACzB,gBAAgB;EACd,WAAW,MAAM;CACnB,GAAG,CAAC,UAAU,CAAC;CACf,gBAAgB;EACd,SAAS,gBAAgB,UAAU,IAAI,UAAU;EACjD,aAAa;GACX,SAAS,gBAAgB,UAAU,OAAO,UAAU;EACtD;CACF,GAAG,CAAC,CAAC;CACL,OACE,oBAAC,mCAAmC,UAApC;EAA6C,OAAO;YAClD,qBAAC,OAAD;GAAK,WAAW,OAAO;aAAvB,CACE,oBAAC,kBAAD,EAAyB,MAAQ,CAAA,GACjC,oBAAC,OAAD;IAAK,WAAW,OAAO;cACrB,oBAAC,0BAAD;KACa;KACU;IACtB,CAAA;GACE,CAAA,CACF;;CACsC,CAAA;AAEjD;AAOA,SAAS,yBAAyB,EAChC,WACA,uBACgC;CAChC,MAAM,EAAE,QAAQ,UAAU,qBAAqB;CAC/C,IAAI,WAAW,gBACb,OAAO,oBAAC,kBAAD,CAAmB,CAAA;CAE5B,IAAI,WAAW,aACb,OACE,qBAAA,UAAA,EAAA,UAAA,CACG,QACC,oBAAC,OAAD;EAAK,WAAW,OAAO;EAAsB,MAAK;YAC/C;CACE,CAAA,IACH,MACH,SACD,EAAA,CAAA;CAGN,OAAO,oBAAC,kBAAD,EAAkB,SAAS,oBAAsB,CAAA;AAC1D"}
1
+ {"version":3,"file":"HubSpotAppConnect-721kYr9d.js","names":["styles.root","styles.variant","variant","styles.size","size","styles","styles","styles","styles"],"sources":["../../src/browser/react/context.ts","../../src/browser/react/hooks.ts","../../src/browser/react/components/Button/Button.css.ts","../../src/browser/react/components/Button/Button.tsx","../../src/browser/react/components/ConnectButton/ConnectButton.css.ts","../../src/browser/react/components/ConnectButton/ConnectButton.tsx","../../src/browser/react/components/icons/ChevronDownIcon.tsx","../../src/browser/react/components/icons/ExternalLinkIcon.tsx","../../src/browser/react/components/icons/HubSpotDataSourceIcon.tsx","../../src/browser/react/components/icons/LogoutIcon.tsx","../../src/browser/react/components/icons/ShareIcon.tsx","../../src/browser/react/components/ShareButton/ShareButton.css.ts","../../src/browser/react/components/ShareButton/ShareButton.tsx","../../src/browser/react/components/AppConnectHeader/AppConnectHeader.css.ts","../../src/browser/react/components/AppConnectHeader/AppConnectHeader.tsx","../../src/browser/react/components/DisconnectedBody/DisconnectedBody.css.ts","../../src/browser/react/components/DisconnectedBody/DisconnectedBody.tsx","../../src/browser/react/components/LoadingIndicator/LoadingIndicator.css.ts","../../src/browser/react/components/LoadingIndicator/LoadingIndicator.tsx","../../src/browser/react/components/HubSpotAppConnect/HubSpotAppConnect.css.ts","../../src/browser/react/components/HubSpotAppConnect/HubSpotAppConnect.tsx"],"sourcesContent":["import { createContext } from 'react';\n\nimport type { AppConnectController } from '../types.ts';\n\n/**\n * React context that carries the `AppConnectController` from the\n * `HubSpotAppConnect` provider down to consumers of\n * `useHubSpotAppConnect`. `null` indicates the hook is being used\n * outside a provider.\n */\nexport const HubSpotAppConnectControllerContext =\n createContext<AppConnectController | null>(null);\n","import { useContext, useSyncExternalStore } from 'react';\n\nimport type { AppConnectState } from '../types.ts';\nimport { HubSpotAppConnectControllerContext } from './context.ts';\n\nexport type UseHubSpotAppConnectResult = AppConnectState;\n\n/**\n * React hook that returns the current `AppConnectState`. Must be\n * called inside a {@link HubSpotAppConnect} provider — throws when\n * no controller is available.\n *\n * The hook subscribes to the controller via `useSyncExternalStore`\n * so React 18+ batched updates and SSR work correctly.\n */\nexport function useHubSpotAppConnect(): UseHubSpotAppConnectResult {\n const controller = useContext(HubSpotAppConnectControllerContext);\n if (controller == null) {\n throw new Error(\n 'useHubSpotAppConnect must be used within HubSpotAppConnect'\n );\n }\n return useSyncExternalStore(\n controller.subscribe,\n controller.getSnapshot,\n controller.getServerSnapshot\n );\n}\n","import { style, styleVariants } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nexport const root = style({\n appearance: 'none',\n WebkitAppearance: 'none',\n margin: 0,\n font: 'inherit',\n fontWeight: 500,\n lineHeight: 'inherit',\n textAlign: 'center',\n cursor: 'pointer',\n borderWidth: 1,\n borderStyle: 'solid',\n borderRadius: themeVars.borderRadius[100],\n transition:\n 'background-color 120ms ease, border-color 120ms ease, color 120ms ease, opacity 120ms ease',\n selectors: {\n '&:disabled': {\n cursor: 'wait',\n opacity: 0.55,\n },\n },\n});\n\nexport const variant = styleVariants({\n primary: {\n backgroundColor: themeVars.fill.primary.default,\n color: themeVars.text.primary.default,\n borderColor: themeVars.border.primary.default,\n selectors: {\n '&:hover:not(:disabled)': {\n filter: 'brightness(0.95)',\n },\n },\n },\n secondary: {\n backgroundColor: themeVars.fill.surface.default.default,\n color: themeVars.text.core.default,\n borderColor: themeVars.border.core.default,\n selectors: {\n '&:hover:not(:disabled)': {\n backgroundColor: '#f7f7f7',\n },\n },\n },\n});\n\nexport const size = styleVariants({\n md: {\n padding: `${themeVars.space[200]} ${themeVars.space[300]}`,\n fontSize: 14,\n },\n lg: {\n padding: `${themeVars.space[300]} ${themeVars.space[500]}`,\n fontSize: 16,\n },\n});\n","import type { ButtonHTMLAttributes, ReactNode } from 'react';\n\nimport * as styles from './Button.css.ts';\n\nexport type ButtonVariant = 'primary' | 'secondary';\nexport type ButtonSize = 'md' | 'lg';\n\nexport interface ButtonProps extends Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'children'\n> {\n children: ReactNode;\n variant?: ButtonVariant;\n size?: ButtonSize;\n}\n\nexport function Button({\n children,\n className,\n variant = 'primary',\n size = 'md',\n type = 'button',\n ...rest\n}: ButtonProps) {\n const composedClassName = [\n styles.root,\n styles.variant[variant],\n styles.size[size],\n className,\n ]\n .filter(Boolean)\n .join(' ');\n return (\n <button {...rest} type={type} className={composedClassName}>\n {children}\n </button>\n );\n}\n","import { keyframes, style, styleVariants } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nconst spin = keyframes({\n from: { transform: 'rotate(0deg)' },\n to: { transform: 'rotate(360deg)' },\n});\n\nexport const root = style({\n position: 'relative',\n});\n\nexport const label = style({\n position: 'relative',\n zIndex: 0,\n});\n\nexport const labelMuted = style({\n opacity: 0.22,\n});\n\nexport const loadingBackdrop = styleVariants({\n primary: {\n position: 'absolute',\n left: '50%',\n top: '50%',\n zIndex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 36,\n height: 36,\n marginLeft: -18,\n marginTop: -18,\n borderRadius: '50%',\n backgroundColor: 'rgba(255, 255, 255, 0.88)',\n boxShadow: '0 1px 6px rgba(0, 0, 0, 0.14)',\n pointerEvents: 'none',\n },\n secondary: {\n position: 'absolute',\n left: '50%',\n top: '50%',\n zIndex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 36,\n height: 36,\n marginLeft: -18,\n marginTop: -18,\n borderRadius: '50%',\n backgroundColor: 'rgba(255, 255, 255, 0.82)',\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.subtle.default,\n boxShadow: '0 1px 4px rgba(0, 0, 0, 0.08)',\n pointerEvents: 'none',\n },\n});\n\nexport const spinner = style({\n display: 'block',\n width: 14,\n height: 14,\n borderWidth: 2,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.subtle.default,\n borderTopColor: themeVars.fill.primary.default,\n borderRadius: '50%',\n animation: `${spin} 0.8s linear infinite`,\n});\n","import { useHubSpotAppConnect } from '../../hooks.ts';\nimport type { ButtonProps } from '../Button/Button.tsx';\nimport { Button } from '../Button/Button.tsx';\nimport {\n label,\n labelMuted,\n loadingBackdrop,\n root,\n spinner,\n} from './ConnectButton.css.ts';\n\nexport interface ConnectButtonProps extends Pick<\n ButtonProps,\n 'variant' | 'size' | 'className'\n> {}\n\nexport function ConnectButton({\n variant = 'primary',\n size = 'md',\n className,\n}: ConnectButtonProps) {\n const { status, connectToHubSpot } = useHubSpotAppConnect();\n const isConnecting = status === 'connecting' || status === 'initializing';\n const composedClassName = [root, className].filter(Boolean).join(' ');\n const labelClassName = isConnecting ? `${label} ${labelMuted}` : label;\n return (\n <Button\n variant={variant}\n size={size}\n className={composedClassName}\n aria-busy={isConnecting}\n onClick={() => void connectToHubSpot()}\n disabled={isConnecting}\n >\n <span className={labelClassName}>Connect to HubSpot</span>\n {isConnecting ? (\n <span className={loadingBackdrop[variant]} aria-hidden=\"true\">\n <span className={spinner} />\n </span>\n ) : null}\n </Button>\n );\n}\n","export interface ChevronDownIconProps {\n className?: string;\n}\n\nexport function ChevronDownIcon({ className }: ChevronDownIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 6l4 4 4-4\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","export interface ExternalLinkIconProps {\n className?: string;\n}\n\nexport function ExternalLinkIcon({ className }: ExternalLinkIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6.5 3.5H4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2.5M10 2.5h3.5V6M9 7l5-5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.25\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","export interface HubSpotDataSourceIconProps {\n className?: string;\n}\n\nexport function HubSpotDataSourceIcon({\n className,\n}: HubSpotDataSourceIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"2.5\"\n y=\"2.5\"\n width=\"11\"\n height=\"11\"\n rx=\"1.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.25\"\n />\n <path\n d=\"M5 6h6M5 8.25h6M5 10.5h4\"\n stroke=\"currentColor\"\n strokeWidth=\"1.25\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n","export interface LogoutIconProps {\n className?: string;\n}\n\nexport function LogoutIcon({ className }: LogoutIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6.5 2.5h-3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h3\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M10.5 11l3-3-3-3\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M13.5 8h-7\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","export interface ShareIconProps {\n className?: string;\n}\n\nexport function ShareIcon({ className }: ShareIconProps) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <circle cx=\"18\" cy=\"5\" r=\"2.75\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <circle cx=\"6\" cy=\"12\" r=\"2.75\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <circle\n cx=\"18\"\n cy=\"19\"\n r=\"2.75\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <path\n d=\"M8.6 10.5L15.4 6.5M8.6 13.5L15.4 17.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n","import { style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nconst shareBorder = '#cbd6e2';\nconst shareText = '#33475b';\n\nexport const styles = {\n shareButton: style({\n flexShrink: 0,\n appearance: 'none',\n WebkitAppearance: 'none',\n margin: 0,\n fontFamily: 'inherit',\n fontSize: 14,\n fontWeight: 500,\n lineHeight: 1,\n cursor: 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: themeVars.space[200],\n padding: `8px ${themeVars.space[300]}`,\n color: shareText,\n backgroundColor: themeVars.fill.surface.default.default,\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: shareBorder,\n borderRadius: 100,\n transition:\n 'background-color 120ms ease, border-color 120ms ease, color 120ms ease',\n selectors: {\n '&:hover:not(:disabled)': {\n backgroundColor: '#f5f8fa',\n },\n '&:focus-visible': {\n outline: `2px solid ${themeVars.border.tertiary.default}`,\n outlineOffset: 2,\n },\n '&:disabled': {\n cursor: 'not-allowed',\n opacity: 0.55,\n },\n },\n }),\n shareIcon: style({\n width: 16,\n height: 16,\n flexShrink: 0,\n display: 'block',\n }),\n} as const;\n","import { ShareIcon } from '../icons/ShareIcon.tsx';\nimport { styles } from './ShareButton.css.ts';\n\ninterface ShareAppConnectPageOptions {\n pageTitle: string;\n}\n\nasync function shareAppConnectPage(\n options: ShareAppConnectPageOptions\n): Promise<void> {\n const { pageTitle } = options;\n const url = window.location.href;\n if (typeof navigator.share === 'function') {\n try {\n await navigator.share({ title: pageTitle, text: pageTitle, url });\n return;\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n return;\n }\n }\n }\n if (typeof navigator.clipboard?.writeText === 'function') {\n await navigator.clipboard.writeText(url);\n }\n}\n\nexport interface ShareButtonProps {\n pageTitle: string;\n}\n\nexport function ShareButton({ pageTitle }: ShareButtonProps) {\n return (\n <button\n type=\"button\"\n className={styles.shareButton}\n onClick={() => void shareAppConnectPage({ pageTitle })}\n >\n <ShareIcon className={styles.shareIcon} />\n <span>Share</span>\n </button>\n );\n}\n","import { style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nconst avatarOrange = themeVars.fill.brand.default;\nconst avatarTextColor = themeVars.text.primary.default;\n\nexport const styles = {\n header: style({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: themeVars.space[300],\n paddingBottom: themeVars.space[300],\n borderBottomWidth: 1,\n borderBottomStyle: 'solid',\n borderBottomColor: themeVars.border.core.subtle.default,\n }),\n titleRow: style({\n display: 'flex',\n alignItems: 'center',\n flex: 1,\n minWidth: 0,\n }),\n leftStack: style({\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n gap: themeVars.space[200],\n minWidth: 0,\n flex: 1,\n }),\n contextRow: style({\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n fontSize: 14,\n lineHeight: 1.35,\n color: themeVars.text.core.subtle,\n minWidth: 0,\n }),\n contextIcon: style({\n width: 16,\n height: 16,\n flexShrink: 0,\n color: themeVars.text.core.subtle,\n }),\n contextPrefix: style({\n flexShrink: 0,\n }),\n contextLink: style({\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n color: themeVars.fill.brand.default,\n textDecoration: 'none',\n fontWeight: 500,\n selectors: {\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n }),\n contextExternalIcon: style({\n width: 14,\n height: 14,\n flexShrink: 0,\n }),\n titleCluster: style({\n display: 'flex',\n alignItems: 'center',\n gap: themeVars.space[200],\n width: 'max-content',\n maxWidth: '100%',\n minWidth: 0,\n }),\n title: style({\n flex: '0 1 auto',\n fontSize: 20,\n fontWeight: 600,\n margin: 0,\n color: themeVars.text.core.default,\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }),\n userTrigger: style({\n appearance: 'none',\n WebkitAppearance: 'none',\n margin: 0,\n font: 'inherit',\n lineHeight: 1,\n cursor: 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n gap: themeVars.space[200],\n padding: `6px ${themeVars.space[300]} 6px 6px`,\n backgroundColor: themeVars.fill.surface.default.default,\n color: themeVars.text.core.default,\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.default,\n borderRadius: 999,\n transition: 'background-color 120ms ease, border-color 120ms ease',\n selectors: {\n '&:hover': {\n backgroundColor: '#f7f7f7',\n },\n '&[data-popup-open]': {\n backgroundColor: '#f7f7f7',\n },\n },\n }),\n triggerName: style({\n fontWeight: 500,\n fontSize: 14,\n }),\n chevron: style({\n width: 14,\n height: 14,\n color: themeVars.text.core.subtle,\n transition: 'transform 150ms ease',\n selectors: {\n '[data-popup-open] &': {\n transform: 'rotate(180deg)',\n },\n },\n }),\n avatar: style({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: avatarOrange,\n color: avatarTextColor,\n fontWeight: 600,\n flexShrink: 0,\n borderRadius: '50%',\n userSelect: 'none',\n }),\n avatarSm: style({\n width: 28,\n height: 28,\n fontSize: 11,\n }),\n avatarLg: style({\n width: 40,\n height: 40,\n fontSize: 14,\n }),\n popup: style({\n minWidth: 240,\n backgroundColor: themeVars.fill.surface.default.default,\n color: themeVars.text.core.default,\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.subtle.default,\n borderRadius: themeVars.borderRadius[300],\n boxShadow:\n '0 6px 16px rgba(17, 17, 17, 0.08), 0 2px 6px rgba(17, 17, 17, 0.04)',\n padding: `${themeVars.space[200]} 0`,\n outline: 'none',\n transformOrigin: 'top right',\n opacity: 0,\n transform: 'scale(0.96)',\n transition: 'opacity 120ms ease, transform 120ms ease',\n selectors: {\n '&[data-starting-style]': {\n opacity: 0,\n transform: 'scale(0.96)',\n },\n '&[data-open]': {\n opacity: 1,\n transform: 'scale(1)',\n },\n },\n }),\n userInfo: style({\n display: 'flex',\n alignItems: 'center',\n gap: themeVars.space[300],\n padding: `${themeVars.space[200]} ${themeVars.space[300]}`,\n }),\n userInfoText: style({\n display: 'flex',\n flexDirection: 'column',\n minWidth: 0,\n }),\n userInfoName: style({\n fontWeight: 600,\n fontSize: 14,\n color: themeVars.text.core.default,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }),\n userInfoEmail: style({\n fontSize: 13,\n color: themeVars.text.core.subtle,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }),\n separator: style({\n height: 1,\n margin: `${themeVars.space[200]} 0`,\n backgroundColor: themeVars.border.core.subtle.default,\n border: 'none',\n }),\n disconnectItem: style({\n display: 'flex',\n alignItems: 'center',\n gap: themeVars.space[200],\n padding: `${themeVars.space[200]} ${themeVars.space[300]}`,\n color: themeVars.text.alert.default,\n fontSize: 14,\n fontWeight: 500,\n cursor: 'pointer',\n outline: 'none',\n userSelect: 'none',\n selectors: {\n '&[data-highlighted]': {\n backgroundColor: themeVars.fill.accent.red.subtle.default,\n },\n },\n }),\n disconnectIcon: style({\n width: 16,\n height: 16,\n }),\n} as const;\n","import { Menu } from '@base-ui/react/menu';\n\nimport { useHubSpotAppConnect } from '../../hooks.ts';\nimport { ConnectButton } from '../ConnectButton/ConnectButton.tsx';\nimport { ChevronDownIcon } from '../icons/ChevronDownIcon.tsx';\nimport { ExternalLinkIcon } from '../icons/ExternalLinkIcon.tsx';\nimport { HubSpotDataSourceIcon } from '../icons/HubSpotDataSourceIcon.tsx';\nimport { LogoutIcon } from '../icons/LogoutIcon.tsx';\nimport { ShareButton } from '../ShareButton/ShareButton.tsx';\nimport { styles } from './AppConnectHeader.css.ts';\n\ninterface AppConnectHeaderProps {\n title: string;\n}\n\nexport function AppConnectHeader({ title }: AppConnectHeaderProps) {\n const { status } = useHubSpotAppConnect();\n const connectButton =\n status === 'initializing' ? null : <ConnectButton variant=\"secondary\" />;\n return (\n <header className={styles.header}>\n <div className={styles.titleRow}>\n <div className={styles.leftStack}>\n <div className={styles.titleCluster}>\n <h1 className={styles.title}>{title}</h1>\n <ShareButton pageTitle={title} />\n </div>\n {status === 'connected' ? <ViewingHubSpotContextRow /> : null}\n </div>\n </div>\n {status === 'connected' ? <UserMenu /> : connectButton}\n </header>\n );\n}\n\nfunction ViewingHubSpotContextRow() {\n const { whoami } = useHubSpotAppConnect();\n const hubLabel = whoami?.hub?.domain ?? whoami?.hub?.id ?? 'HubSpot';\n return (\n <div className={styles.contextRow}>\n <HubSpotDataSourceIcon className={styles.contextIcon} />\n <span className={styles.contextPrefix}>Viewing HubSpot data from </span>\n <a\n className={styles.contextLink}\n href=\"#\"\n onClick={(event) => {\n event.preventDefault();\n }}\n >\n {hubLabel}\n <ExternalLinkIcon className={styles.contextExternalIcon} />\n </a>\n </div>\n );\n}\n\nfunction UserMenu() {\n const { whoami, disconnectFromHubSpot } = useHubSpotAppConnect();\n const user = whoami?.user;\n const nameParts = [user?.firstName, user?.lastName].filter(\n (p): p is string => typeof p === 'string' && p.length > 0\n );\n const initials =\n nameParts.map((n) => n[0]!.toUpperCase()).join('') ||\n user?.email?.[0]?.toUpperCase() ||\n '?';\n const displayName = nameParts.join(' ') || user?.email || 'HubSpot User';\n\n return (\n <Menu.Root modal={false}>\n <Menu.Trigger className={styles.userTrigger}>\n <span\n className={`${styles.avatar} ${styles.avatarSm}`}\n aria-hidden=\"true\"\n >\n {initials}\n </span>\n <span className={styles.triggerName}>{displayName}</span>\n <ChevronDownIcon className={styles.chevron} />\n </Menu.Trigger>\n <Menu.Portal>\n <Menu.Positioner sideOffset={8} align=\"end\">\n <Menu.Popup className={styles.popup}>\n <div className={styles.userInfo}>\n <span\n className={`${styles.avatar} ${styles.avatarLg}`}\n aria-hidden=\"true\"\n >\n {initials}\n </span>\n <div className={styles.userInfoText}>\n <span className={styles.userInfoName}>{displayName}</span>\n {user?.email ? (\n <span className={styles.userInfoEmail}>{user.email}</span>\n ) : null}\n </div>\n </div>\n <Menu.Separator className={styles.separator} />\n <Menu.Item\n className={styles.disconnectItem}\n onClick={() => void disconnectFromHubSpot()}\n >\n <LogoutIcon className={styles.disconnectIcon} />\n Disconnect\n </Menu.Item>\n </Menu.Popup>\n </Menu.Positioner>\n </Menu.Portal>\n </Menu.Root>\n );\n}\n","import { style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nexport const styles = {\n card: style({\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.default,\n borderRadius: themeVars.borderRadius[400],\n padding: themeVars.space[500],\n textAlign: 'center',\n backgroundColor: themeVars.fill.surface.default.default,\n color: themeVars.text.core.default,\n }),\n message: style({\n marginTop: 0,\n marginBottom: themeVars.space[400],\n }),\n errorText: style({\n marginTop: themeVars.space[300],\n marginBottom: 0,\n color: themeVars.text.alert.default,\n }),\n} as const;\n","import type { ReactNode } from 'react';\n\nimport { useHubSpotAppConnect } from '../../hooks.ts';\nimport { ConnectButton } from '../ConnectButton/ConnectButton.tsx';\nimport { styles } from './DisconnectedBody.css.ts';\n\ninterface DisconnectedBodyProps {\n message: ReactNode;\n}\n\nexport function DisconnectedBody({ message }: DisconnectedBodyProps) {\n const { error } = useHubSpotAppConnect();\n\n return (\n <div className={styles.card}>\n <p className={styles.message}>{message}</p>\n <ConnectButton size=\"lg\" />\n {error && (\n <p className={styles.errorText}>\n Failed to connect to HubSpot. {error}\n </p>\n )}\n </div>\n );\n}\n","import { keyframes, style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nconst spin = keyframes({\n from: { transform: 'rotate(0deg)' },\n to: { transform: 'rotate(360deg)' },\n});\n\nexport const styles = {\n wrapper: style({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: 200,\n padding: themeVars.space[600],\n }),\n spinner: style({\n width: 40,\n height: 40,\n borderWidth: 3,\n borderStyle: 'solid',\n borderColor: themeVars.border.core.subtle.default,\n borderTopColor: themeVars.fill.primary.default,\n borderRadius: '50%',\n animation: `${spin} 0.8s linear infinite`,\n }),\n} as const;\n","import { styles } from './LoadingIndicator.css.ts';\n\nexport function LoadingIndicator() {\n return (\n <div className={styles.wrapper} role=\"status\" aria-label=\"Loading\">\n <div className={styles.spinner} />\n </div>\n );\n}\n","import { style } from '@vanilla-extract/css';\n\nimport { themeVars } from '../../../theme.css.ts';\n\nexport const styles = {\n shell: style({\n width: '100%',\n padding: `${themeVars.space[400]} ${themeVars.space[500]}`,\n }),\n content: style({\n marginTop: themeVars.space[500],\n }),\n connectedErrorBanner: style({\n backgroundColor: themeVars.fill.alert.subtle,\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: themeVars.border.alert.default,\n borderRadius: themeVars.borderRadius[300],\n padding: themeVars.space[300],\n marginBottom: themeVars.space[300],\n color: themeVars.text.alert.default,\n }),\n} as const;\n","import { useEffect, type ReactNode } from 'react';\n\nimport { themeClass } from '../../../theme.css.ts';\nimport type { AppConnectController } from '../../../types.ts';\nimport { HubSpotAppConnectControllerContext } from '../../context.ts';\nimport { useHubSpotAppConnect } from '../../hooks.ts';\nimport { AppConnectHeader } from '../AppConnectHeader/AppConnectHeader.tsx';\nimport { DisconnectedBody } from '../DisconnectedBody/DisconnectedBody.tsx';\nimport { LoadingIndicator } from '../LoadingIndicator/LoadingIndicator.tsx';\nimport { styles } from './HubSpotAppConnect.css.ts';\n\n/**\n * Props accepted by {@link HubSpotAppConnect}.\n */\nexport interface HubSpotAppConnectProps {\n /** Title text rendered in the standard SDK header. */\n title: string;\n /** Controller produced by `createAppConnectController`. */\n controller: AppConnectController;\n /** Content rendered when the controller is in the `connected` state. */\n connected: ReactNode;\n /**\n * Description text rendered inside the SDK-owned disconnected card,\n * above the primary \"Connect to HubSpot\" button.\n */\n disconnectedMessage: ReactNode;\n}\n\n/**\n * Layout component that exposes `controller` to {@link useHubSpotAppConnect},\n * starts it once on mount, and renders a standard header plus the content\n * slot that matches the current connection status.\n */\nexport function HubSpotAppConnect({\n title,\n controller,\n connected,\n disconnectedMessage,\n}: HubSpotAppConnectProps) {\n useEffect(() => {\n controller.start();\n }, [controller]);\n useEffect(() => {\n document.documentElement.classList.add(themeClass);\n return () => {\n document.documentElement.classList.remove(themeClass);\n };\n }, []);\n return (\n <HubSpotAppConnectControllerContext.Provider value={controller}>\n <div className={styles.shell}>\n <AppConnectHeader title={title} />\n <div className={styles.content}>\n <HubSpotAppConnectContent\n connected={connected}\n disconnectedMessage={disconnectedMessage}\n />\n </div>\n </div>\n </HubSpotAppConnectControllerContext.Provider>\n );\n}\n\ninterface HubSpotAppConnectContentProps {\n connected: ReactNode;\n disconnectedMessage: ReactNode;\n}\n\nfunction HubSpotAppConnectContent({\n connected,\n disconnectedMessage,\n}: HubSpotAppConnectContentProps) {\n const { status, error } = useHubSpotAppConnect();\n if (status === 'initializing') {\n return <LoadingIndicator />;\n }\n if (status === 'connected') {\n return (\n <>\n {error ? (\n <div className={styles.connectedErrorBanner} role=\"alert\">\n {error}\n </div>\n ) : null}\n {connected}\n </>\n );\n }\n return <DisconnectedBody message={disconnectedMessage} />;\n}\n"],"mappings":"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"}
@@ -55,11 +55,13 @@ const OAUTH_POPUP_CALLBACK_MESSAGE_TYPE = "hubspot-app-connect:oauth-callback";
55
55
  //#endregion
56
56
  //#region src/browser/app-connect-controller/constants.ts
57
57
  /**
58
- * Key the controller persists the access-token `expiresAt` (Unix
59
- * epoch milliseconds) under in `sessionStorage`. Survives full-page
60
- * navigations within the same tab.
58
+ * Key under which the controller persists the entire session blob
59
+ * (expiresAt + whoami) as a single JSON string in `sessionStorage`.
60
+ * Using one key makes logout trivially correct — one `removeItem` call
61
+ * clears all session state. Survives full-page navigations within the
62
+ * same tab.
61
63
  */
62
- const EXPIRES_AT_KEY = "hubspot_token_expires_at";
64
+ const SESSION_STORAGE_KEY = "hubspot_connect_session";
63
65
  /**
64
66
  * Number of milliseconds before `expiresAt` that the refresh
65
67
  * scheduler attempts to mint a new access token. 60s is comfortably
@@ -68,45 +70,78 @@ const EXPIRES_AT_KEY = "hubspot_token_expires_at";
68
70
  const REFRESH_BUFFER_MS = 6e4;
69
71
  //#endregion
70
72
  //#region src/browser/app-connect-controller/utils/session-utils.ts
71
- /**
72
- * Persists the access-token `expiresAt` (Unix epoch milliseconds) to
73
- * both the in-memory store and `sessionStorage` so it survives
74
- * full-page navigations within the same tab.
75
- */
76
- function storeExpiresAt(options) {
77
- const { context, expiresAtMs } = options;
78
- context.store.setState({ expiresAt: expiresAtMs });
79
- context.sessionStorage.setItem(EXPIRES_AT_KEY, String(expiresAtMs));
80
- }
81
- /**
82
- * Reads the persisted `expiresAt` from `sessionStorage`. Removes the
83
- * value (and returns `null`) if it is malformed or already expired,
84
- * so a stale entry never reactivates a dead session.
85
- */
86
- function getExpiresAtFromSessionStorage(context) {
87
- const raw = context.sessionStorage.getItem(EXPIRES_AT_KEY);
73
+ function readStoredSession(context) {
74
+ const raw = context.sessionStorage.getItem(SESSION_STORAGE_KEY);
88
75
  if (!raw) return null;
89
- const val = parseInt(raw, 10);
90
- if (isNaN(val)) {
91
- context.sessionStorage.removeItem(EXPIRES_AT_KEY);
76
+ let parsed;
77
+ try {
78
+ parsed = JSON.parse(raw);
79
+ } catch {
80
+ context.sessionStorage.removeItem(SESSION_STORAGE_KEY);
81
+ return null;
82
+ }
83
+ if (typeof parsed.expiresAt !== "number" || isNaN(parsed.expiresAt)) {
84
+ context.sessionStorage.removeItem(SESSION_STORAGE_KEY);
92
85
  return null;
93
86
  }
94
- if (Date.now() > val) {
95
- context.sessionStorage.removeItem(EXPIRES_AT_KEY);
87
+ if (Date.now() > parsed.expiresAt) {
88
+ context.sessionStorage.removeItem(SESSION_STORAGE_KEY);
96
89
  return null;
97
90
  }
98
- return val;
91
+ return parsed;
92
+ }
93
+ function writeStoredSession(context, session) {
94
+ context.sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session));
95
+ }
96
+ /**
97
+ * Reads the persisted session blob from `sessionStorage`. Returns
98
+ * `null` (and auto-removes the entry) if the blob is missing,
99
+ * malformed, or already expired.
100
+ */
101
+ function getSessionFromSessionStorage(context) {
102
+ return readStoredSession(context);
99
103
  }
100
104
  /**
101
- * Clears the persisted session-storage state. Called on disconnect.
105
+ * Persists both `expiresAt` and `whoami` to the in-memory store and
106
+ * `sessionStorage` as a single JSON blob. Called from `init.ts` after
107
+ * a successful OAuth token exchange.
108
+ */
109
+ function storeSession(options) {
110
+ const { context, expiresAtMs, whoami } = options;
111
+ context.store.setState({
112
+ expiresAt: expiresAtMs,
113
+ whoami
114
+ });
115
+ writeStoredSession(context, {
116
+ expiresAt: expiresAtMs,
117
+ whoami
118
+ });
119
+ }
120
+ /**
121
+ * Updates only `expiresAt` in both the store and `sessionStorage`,
122
+ * preserving the existing `whoami` from in-memory store state. Called
123
+ * from `refresh.ts` after a successful token refresh.
124
+ */
125
+ function storeExpiresAt(options) {
126
+ const { context, expiresAtMs } = options;
127
+ const currentWhoami = context.store.getSnapshot().whoami;
128
+ context.store.setState({ expiresAt: expiresAtMs });
129
+ writeStoredSession(context, {
130
+ expiresAt: expiresAtMs,
131
+ whoami: currentWhoami
132
+ });
133
+ }
134
+ /**
135
+ * Removes the single session-storage key, clearing all persisted
136
+ * session state. Called on disconnect and on auth-complete errors.
102
137
  */
103
138
  function clearSessionStorage(context) {
104
- context.sessionStorage.removeItem(EXPIRES_AT_KEY);
139
+ context.sessionStorage.removeItem(SESSION_STORAGE_KEY);
105
140
  }
106
141
  /**
107
142
  * Returns `true` when the controller has an `expiresAt` whose value
108
- * is still in the future. Used both to drive the UI status and to
109
- * decide whether the refresh scheduler should run.
143
+ * is still in the future. Used to drive the UI status and the refresh
144
+ * scheduler.
110
145
  */
111
146
  function isClientSessionActive(context) {
112
147
  const expiresAt = context.store.getSnapshot().expiresAt;
@@ -370,6 +405,7 @@ async function disconnectFromHubSpot(context) {
370
405
  await response.body?.cancel();
371
406
  store.setState({
372
407
  expiresAt: null,
408
+ whoami: null,
373
409
  isSessionConnected: false,
374
410
  isDisconnectInFlight: false
375
411
  });
@@ -415,13 +451,15 @@ async function consumeOAuthCallback(context) {
415
451
  code,
416
452
  state
417
453
  })) return;
418
- const { expires_at: expiresAt, return_path: returnPath } = await completeHubSpotOAuthSession(context, {
454
+ const body = await completeHubSpotOAuthSession(context, {
419
455
  code,
420
456
  state
421
457
  });
422
- storeExpiresAt({
458
+ const { expires_at: expiresAt, return_path: returnPath } = body;
459
+ storeSession({
423
460
  context,
424
- expiresAtMs: expiresAt
461
+ expiresAtMs: expiresAt,
462
+ whoami: body.whoami
425
463
  });
426
464
  const targetUrl = new URL(returnPath, window.location.origin);
427
465
  history.replaceState(null, "", `${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`);
@@ -596,6 +634,7 @@ const noop = () => Promise.resolve();
596
634
  const SERVER_VIEW = {
597
635
  status: "initializing",
598
636
  error: null,
637
+ whoami: null,
599
638
  connectToHubSpot: noop,
600
639
  disconnectFromHubSpot: noop
601
640
  };
@@ -634,7 +673,8 @@ function createAppConnectController(options) {
634
673
  isDisconnectInFlight: false,
635
674
  isSessionConnected: false,
636
675
  error: null,
637
- expiresAt: null
676
+ expiresAt: null,
677
+ whoami: null
638
678
  });
639
679
  const context = {
640
680
  config,
@@ -642,7 +682,11 @@ function createAppConnectController(options) {
642
682
  sessionStorage,
643
683
  store
644
684
  };
645
- store.setState({ expiresAt: getExpiresAtFromSessionStorage(context) });
685
+ const storedSession = getSessionFromSessionStorage(context);
686
+ store.setState({
687
+ expiresAt: storedSession?.expiresAt ?? null,
688
+ whoami: storedSession?.whoami ?? null
689
+ });
646
690
  let hasStarted = false;
647
691
  const connectToHubSpot = async () => {
648
692
  logger.info("connectToHubSpot: starting");
@@ -665,6 +709,7 @@ function createAppConnectController(options) {
665
709
  const getViewStateMemoized = memoizeLast((storeState) => ({
666
710
  status: getDerivedStatus(storeState),
667
711
  error: storeState.error,
712
+ whoami: storeState.whoami,
668
713
  connectToHubSpot,
669
714
  disconnectFromHubSpot: disconnectFromHubSpot$1
670
715
  }));
@@ -705,4 +750,4 @@ function createAppConnectController(options) {
705
750
  //#endregion
706
751
  export { createLogger as n, createAppConnectController as t };
707
752
 
708
- //# sourceMappingURL=create-BNQazCF-.js.map
753
+ //# sourceMappingURL=create-DxEyGG-k.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-DxEyGG-k.js","names":["disconnectFromHubSpot","runDisconnectFromHubSpot"],"sources":["../../src/shared/logger.ts","../../src/shared/constants.ts","../../src/browser/app-connect-controller/constants.ts","../../src/browser/app-connect-controller/utils/session-utils.ts","../../src/browser/app-connect-controller/oauth-complete.ts","../../src/browser/app-connect-controller/oauth-popup.ts","../../src/browser/app-connect-controller/utils/iframe-utils.ts","../../src/browser/app-connect-controller/utils/resolve-oauth-connect-mode.ts","../../src/browser/app-connect-controller/utils/timeout-utils.ts","../../src/browser/app-connect-controller/connect-start.ts","../../src/browser/app-connect-controller/default-session-storage.ts","../../src/browser/app-connect-controller/disconnect.ts","../../src/browser/app-connect-controller/init.ts","../../src/browser/app-connect-controller/refresh.ts","../../src/browser/app-connect-controller/utils/memoize-utils.ts","../../src/browser/app-connect-controller/utils/store-utils.ts","../../src/browser/app-connect-controller/view-state.ts","../../src/browser/app-connect-controller/create.ts"],"sourcesContent":["/**\n * Pluggable logger contract used by the SDK on both the browser and\n * server. Consumers can pass `console`-like loggers, structured\n * loggers (pino / winston / etc.) or no-op stubs in tests.\n */\nexport interface Logger {\n debug: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n error: (message: string, ...args: unknown[]) => void;\n}\n\nfunction formatPrefix(name: string): string {\n return `[${name}]`;\n}\n\n/**\n * Creates a console-backed logger that prefixes every line with the\n * supplied `name`. Used as the default when no custom logger is\n * provided.\n */\nexport function createLogger(name: string): Logger {\n const prefix = formatPrefix(name);\n return {\n debug: (message, ...args) => {\n console.debug(prefix, message, ...args);\n },\n info: (message, ...args) => {\n console.info(prefix, message, ...args);\n },\n warn: (message, ...args) => {\n console.warn(prefix, message, ...args);\n },\n error: (message, ...args) => {\n console.error(prefix, message, ...args);\n },\n };\n}\n\n/**\n * Logger that swallows every message. Convenient for tests and for\n * the SDK's server-side handlers when no logger is provided by the\n * host application.\n */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n","/**\n * Constants whose values are part of the contract between the browser\n * controller and the server-side hubspot-connect routes. Both halves\n * import from this module so the wire format stays in sync.\n */\n\n/**\n * Query parameter on the OAuth return URL that carries the new access\n * token's expiry (Unix epoch milliseconds). The browser controller\n * sets this in the URL after a successful `auth/complete` call and\n * then strips it during `initAppConnect` via `history.replaceState`.\n */\nexport const EXPIRES_AT_URL_PARAM = '__hs_expires_at';\n\n/**\n * Path the browser visits after HubSpot's authorize endpoint\n * redirects back to the app. Mounted on the **frontend** origin (not\n * the SDK's edge function host) so all OAuth-related cookies live in\n * the `(frontend, edge)` CHIPS partition.\n *\n * The SDK's `auth/init-session` builds the OAuth `redirect_uri` as\n * `${requestOrigin}${HUBSPOT_FRONTEND_CALLBACK_PATH}`. The browser\n * controller, on `start()`, recognizes this path on `window.location`\n * and forwards `?code` + `?state` to the SDK's `auth/complete`\n * endpoint via a credentialed cross-site fetch. The host app must\n * register `${app_origin}${HUBSPOT_FRONTEND_CALLBACK_PATH}` as a\n * redirect URI in its HubSpot app settings.\n */\nexport const OAUTH_CALLBACK_PATH = '/__hubspot_oauth_callback';\n\n/**\n * Query parameter on the `auth/complete` POST request carrying the\n * authorization `code` HubSpot returned to the frontend callback.\n */\nexport const AUTH_COMPLETE_CODE_PARAM = 'code';\n\n/**\n * Query parameter on the `auth/complete` POST request carrying the\n * OAuth `state` HubSpot echoed back to the frontend callback.\n */\nexport const AUTH_COMPLETE_STATE_PARAM = 'state';\n\n/**\n * `postMessage` `data.type` value the OAuth popup sends to its opener\n * with the authorization `code` and `state` from the callback URL. The\n * opener POSTs them to `auth/complete` so credentialed cookies stay in\n * the same CHIPS partition as `auth/init-session`.\n */\nexport const OAUTH_POPUP_CALLBACK_MESSAGE_TYPE =\n 'hubspot-app-connect:oauth-callback';\n","export { EXPIRES_AT_URL_PARAM } from '../../shared/constants.ts';\n\n/**\n * Key under which the controller persists the entire session blob\n * (expiresAt + whoami) as a single JSON string in `sessionStorage`.\n * Using one key makes logout trivially correct — one `removeItem` call\n * clears all session state. Survives full-page navigations within the\n * same tab.\n */\nexport const SESSION_STORAGE_KEY = 'hubspot_connect_session';\n\n/**\n * Number of milliseconds before `expiresAt` that the refresh\n * scheduler attempts to mint a new access token. 60s is comfortably\n * larger than typical network latency without burning lifetime.\n */\nexport const REFRESH_BUFFER_MS = 60_000;\n","import type { AuthCompleteWhoami } from '../../../shared/wire-types.ts';\nimport { SESSION_STORAGE_KEY } from '../constants.ts';\nimport type { AppConnectContext } from '../types.ts';\n\ninterface StoredSession {\n expiresAt: number;\n whoami: AuthCompleteWhoami | null;\n}\n\nfunction readStoredSession(context: AppConnectContext): StoredSession | null {\n const raw = context.sessionStorage.getItem(SESSION_STORAGE_KEY);\n if (!raw) return null;\n let parsed: StoredSession;\n try {\n parsed = JSON.parse(raw) as StoredSession;\n } catch {\n context.sessionStorage.removeItem(SESSION_STORAGE_KEY);\n return null;\n }\n if (typeof parsed.expiresAt !== 'number' || isNaN(parsed.expiresAt)) {\n context.sessionStorage.removeItem(SESSION_STORAGE_KEY);\n return null;\n }\n if (Date.now() > parsed.expiresAt) {\n context.sessionStorage.removeItem(SESSION_STORAGE_KEY);\n return null;\n }\n return parsed;\n}\n\nfunction writeStoredSession(\n context: AppConnectContext,\n session: StoredSession\n): void {\n context.sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session));\n}\n\n/**\n * Reads the persisted session blob from `sessionStorage`. Returns\n * `null` (and auto-removes the entry) if the blob is missing,\n * malformed, or already expired.\n */\nexport function getSessionFromSessionStorage(\n context: AppConnectContext\n): StoredSession | null {\n return readStoredSession(context);\n}\n\n/**\n * Persists both `expiresAt` and `whoami` to the in-memory store and\n * `sessionStorage` as a single JSON blob. Called from `init.ts` after\n * a successful OAuth token exchange.\n */\nexport function storeSession(options: {\n context: AppConnectContext;\n expiresAtMs: number;\n whoami: AuthCompleteWhoami;\n}): void {\n const { context, expiresAtMs, whoami } = options;\n context.store.setState({ expiresAt: expiresAtMs, whoami });\n writeStoredSession(context, { expiresAt: expiresAtMs, whoami });\n}\n\n/**\n * Updates only `expiresAt` in both the store and `sessionStorage`,\n * preserving the existing `whoami` from in-memory store state. Called\n * from `refresh.ts` after a successful token refresh.\n */\nexport function storeExpiresAt(options: {\n context: AppConnectContext;\n expiresAtMs: number;\n}): void {\n const { context, expiresAtMs } = options;\n const currentWhoami = context.store.getSnapshot().whoami;\n context.store.setState({ expiresAt: expiresAtMs });\n writeStoredSession(context, {\n expiresAt: expiresAtMs,\n whoami: currentWhoami,\n });\n}\n\n/**\n * Removes the single session-storage key, clearing all persisted\n * session state. Called on disconnect and on auth-complete errors.\n */\nexport function clearSessionStorage(context: AppConnectContext): void {\n context.sessionStorage.removeItem(SESSION_STORAGE_KEY);\n}\n\n/**\n * Returns `true` when the controller has an `expiresAt` whose value\n * is still in the future. Used to drive the UI status and the refresh\n * scheduler.\n */\nexport function isClientSessionActive(context: AppConnectContext): boolean {\n const state = context.store.getSnapshot();\n const expiresAt = state.expiresAt;\n return expiresAt !== null && Date.now() < expiresAt;\n}\n","import {\n AUTH_COMPLETE_CODE_PARAM,\n AUTH_COMPLETE_STATE_PARAM,\n} from '../../shared/constants.ts';\nimport type { AuthCompleteResponse } from '../../shared/wire-types.ts';\nimport type { AppConnectContext } from './types.ts';\nimport { clearSessionStorage } from './utils/session-utils.ts';\n\ninterface CompleteHubSpotOAuthSessionOptions {\n code: string;\n state: string;\n}\n\n/**\n * Finishes the OAuth token exchange by POSTing `code` and `state` to the\n * SDK's `auth/complete` route with credentialed cookies from the current\n * browsing context (must match the partition used by `auth/init-session`).\n */\nexport async function completeHubSpotOAuthSession(\n context: AppConnectContext,\n options: CompleteHubSpotOAuthSessionOptions\n): Promise<AuthCompleteResponse> {\n const { code, state } = options;\n\n const completeUrl = new URL(\n `${context.config.hubSpotConnectBaseUrl}/auth/complete`,\n window.location.origin\n );\n completeUrl.searchParams.set(AUTH_COMPLETE_CODE_PARAM, code);\n completeUrl.searchParams.set(AUTH_COMPLETE_STATE_PARAM, state);\n\n let response: Response;\n try {\n response = await fetch(completeUrl.toString(), {\n method: 'POST',\n credentials: 'include',\n });\n } catch (err) {\n clearSessionStorage(context);\n throw new Error(\n `Failed to complete HubSpot OAuth: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n if (!response.ok) {\n clearSessionStorage(context);\n throw new Error(\n `Failed to complete HubSpot OAuth: ${response.status} ${response.statusText}`\n );\n }\n\n return (await response.json()) as AuthCompleteResponse;\n}\n","import { OAUTH_POPUP_CALLBACK_MESSAGE_TYPE } from '../../shared/constants.ts';\nimport { completeHubSpotOAuthSession } from './oauth-complete.ts';\nimport type { AppConnectContext } from './types.ts';\nimport { storeExpiresAt } from './utils/session-utils.ts';\n\nexport const OAUTH_POPUP_WINDOW_NAME = 'hubspot-app-connect-oauth';\nconst OAUTH_POPUP_POLL_INTERVAL_MS = 300;\n\ninterface OAuthPopupCallbackMessage {\n type: typeof OAUTH_POPUP_CALLBACK_MESSAGE_TYPE;\n code: string;\n state: string;\n}\n\ninterface WaitForHubSpotOAuthPopupOptions {\n context: AppConnectContext;\n authorizationUrl: string;\n}\n\ninterface RelayOAuthCallbackToOpenerOptions {\n code: string;\n state: string;\n}\n\nfunction isOAuthPopupCallbackMessage(\n data: unknown\n): data is OAuthPopupCallbackMessage {\n if (typeof data !== 'object' || data === null) return false;\n const record = data as Record<string, unknown>;\n return (\n record.type === OAUTH_POPUP_CALLBACK_MESSAGE_TYPE &&\n typeof record.code === 'string' &&\n record.code.length > 0 &&\n typeof record.state === 'string' &&\n record.state.length > 0\n );\n}\n\n/**\n * Opens HubSpot's authorize URL in a popup and waits for the callback\n * page to relay `code` + `state` back. The opener POSTs to\n * `auth/complete` so credentialed cookies use the same CHIPS partition\n * as `auth/init-session`.\n */\nexport async function waitForHubSpotOAuthPopup(\n options: WaitForHubSpotOAuthPopupOptions\n): Promise<void> {\n const { context, authorizationUrl } = options;\n const targetOrigin = window.location.origin;\n\n return new Promise<void>((resolve, reject) => {\n let popup: Window | null = null;\n let pollTimer: ReturnType<typeof setInterval> | undefined;\n let isSettled = false;\n let isCallbackReceived = false;\n\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n if (pollTimer !== undefined) clearInterval(pollTimer);\n };\n\n const settleSuccess = (expiresAtMs: number) => {\n if (isSettled) return;\n isSettled = true;\n cleanup();\n storeExpiresAt({ context, expiresAtMs });\n context.store.setState({ isSessionConnected: true, error: null });\n resolve();\n };\n\n const settleFailure = (message: string) => {\n if (isSettled) return;\n isSettled = true;\n cleanup();\n try {\n popup?.close();\n } catch {\n // ignore close errors\n }\n reject(new Error(message));\n };\n\n const onMessage = (event: MessageEvent) => {\n if (event.origin !== targetOrigin) return;\n if (!isOAuthPopupCallbackMessage(event.data)) return;\n\n if (isCallbackReceived) return;\n isCallbackReceived = true;\n\n void (async () => {\n try {\n const body = await completeHubSpotOAuthSession(context, {\n code: event.data.code,\n state: event.data.state,\n });\n settleSuccess(body.expires_at);\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'HubSpot OAuth failed';\n settleFailure(message);\n }\n })();\n };\n\n window.addEventListener('message', onMessage);\n\n popup = window.open(\n authorizationUrl,\n OAUTH_POPUP_WINDOW_NAME,\n 'popup=yes,width=600,height=700'\n );\n if (!popup) {\n settleFailure('Popup blocked. Allow popups for this site and try again.');\n return;\n }\n\n pollTimer = setInterval(() => {\n if (popup?.closed && !isCallbackReceived) {\n settleFailure('Connect to HubSpot was cancelled.');\n }\n }, OAUTH_POPUP_POLL_INTERVAL_MS);\n });\n}\n\n/**\n * Called from the OAuth callback page inside the popup. Relays `code`\n * and `state` to the opener (which runs `auth/complete`) and closes.\n */\nexport function relayOAuthCallbackToOpener(\n options: RelayOAuthCallbackToOpenerOptions\n): boolean {\n const { code, state } = options;\n const opener = window.opener;\n if (!opener || opener.closed) return false;\n\n const message: OAuthPopupCallbackMessage = {\n type: OAUTH_POPUP_CALLBACK_MESSAGE_TYPE,\n code,\n state,\n };\n opener.postMessage(message, window.location.origin);\n try {\n window.close();\n } catch {\n // ignore close errors\n }\n return true;\n}\n\nexport function hasOAuthPopupOpener(): boolean {\n try {\n return Boolean(window.opener && !window.opener.closed);\n } catch {\n return false;\n }\n}\n\nexport function isOAuthPopupCallback(): boolean {\n return window.name === OAUTH_POPUP_WINDOW_NAME && hasOAuthPopupOpener();\n}\n","/**\n * Returns `true` when the app runs inside a parent frame (same-origin\n * or cross-origin). Cross-origin parent access throws; treat that as\n * embedded so OAuth uses a popup instead of a top-level redirect.\n */\nexport function isAppEmbeddedInIframe(): boolean {\n try {\n return window.self !== window.top;\n } catch {\n return true;\n }\n}\n","import type { OAuthConnectMode } from '../../types.ts';\nimport { isAppEmbeddedInIframe } from './iframe-utils.ts';\n\nexport type ResolvedOAuthConnectMode = 'redirect' | 'popup';\n\ninterface ResolveOAuthConnectModeOptions {\n oauthConnectMode?: OAuthConnectMode;\n}\n\n/**\n * Maps the configured {@link OAuthConnectMode} to the concrete connect\n * behavior for the current browsing context.\n */\nexport function resolveOAuthConnectMode(\n options: ResolveOAuthConnectModeOptions\n): ResolvedOAuthConnectMode {\n const mode = options.oauthConnectMode ?? 'auto';\n if (mode === 'popup') return 'popup';\n if (mode === 'redirect') return 'redirect';\n return isAppEmbeddedInIframe() ? 'popup' : 'redirect';\n}\n","export function delay(ms: number): Promise<void> {\n if (ms <= 0) {\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import type { InitSessionResponse } from '../../shared/wire-types.ts';\nimport { waitForHubSpotOAuthPopup } from './oauth-popup.ts';\nimport type { AppConnectContext } from './types.ts';\nimport { resolveOAuthConnectMode } from './utils/resolve-oauth-connect-mode.ts';\nimport { delay } from './utils/timeout-utils.ts';\n\n/** Extra wait before redirect so the connect progress UI is visible; set to `0` to disable. */\nconst ARTIFICIAL_CONNECT_REDIRECT_DELAY_MS = 500;\n\n/**\n * Begins the OAuth connect flow:\n *\n * 1. Calls the SDK's `auth/init-session` route to mint a fresh PKCE\n * verifier + state and obtain HubSpot's `authorize` URL.\n * 2. Navigates to that URL via full-page redirect, or opens it in a\n * popup when embedded in an iframe or when `oauthConnectMode` is\n * `'popup'`.\n *\n * The `return_path` is the current path + query so the user lands\n * back where they started after authorizing (redirect mode only).\n *\n * Throws when the init call fails. Does not return after a redirect\n * begins because the page is unloaded.\n */\nexport async function startHubSpotConnection(\n context: AppConnectContext\n): Promise<void> {\n const { config } = context;\n\n const returnPath = `${window.location.pathname}${window.location.search}`;\n\n const initUrl = new URL(\n `${config.hubSpotConnectBaseUrl}/auth/init-session`,\n window.location.origin\n );\n initUrl.searchParams.set('return_path', returnPath);\n\n const initResponse = await fetch(initUrl.toString(), {\n credentials: 'include',\n });\n if (!initResponse.ok)\n throw new Error(`Failed to init session: ${initResponse.status}`);\n const { authorization_url: authorizationUrl } =\n (await initResponse.json()) as InitSessionResponse;\n\n await delay(ARTIFICIAL_CONNECT_REDIRECT_DELAY_MS);\n\n const connectMode = resolveOAuthConnectMode(\n config.oauthConnectMode !== undefined\n ? { oauthConnectMode: config.oauthConnectMode }\n : {}\n );\n\n if (connectMode === 'popup') {\n await waitForHubSpotOAuthPopup({ context, authorizationUrl });\n return;\n }\n\n window.location.href = authorizationUrl;\n}\n","import type { SessionStorage } from './types.ts';\n\n/**\n * Builds a `SessionStorage` adapter that delegates to the global\n * `sessionStorage` object exposed by browsers. The controller uses\n * this when no custom storage is supplied (e.g. for tests or non-DOM\n * environments).\n */\nexport function createDefaultSessionStorage(): SessionStorage {\n return {\n setItem: (key, value) => {\n sessionStorage.setItem(key, value);\n },\n getItem: (key) => {\n return sessionStorage.getItem(key);\n },\n removeItem: (key) => {\n sessionStorage.removeItem(key);\n },\n };\n}\n","import type { AppConnectContext } from './types.ts';\nimport { clearSessionStorage } from './utils/session-utils.ts';\n\n/**\n * Disconnect flow:\n *\n * 1. Calls the SDK's `auth/logout` route to revoke the upstream token\n * and clear the refresh-token cookie.\n * 2. Clears the local session-storage `expiresAt` entry.\n * 3. Updates the controller state to `disconnected` so the UI re-renders\n * without a page reload.\n *\n * Errors are caught, logged, and surfaced via the controller's\n * `error` field so the UI can show a retry state.\n */\nexport async function disconnectFromHubSpot(\n context: AppConnectContext\n): Promise<void> {\n const { config, logger, store } = context;\n logger.info('disconnectFromHubSpot: starting');\n store.setState({ error: null, isDisconnectInFlight: true });\n const { hubSpotConnectBaseUrl: appConnectBaseUrl } = config;\n\n try {\n clearSessionStorage(context);\n\n const response = await fetch(`${appConnectBaseUrl}/auth/logout`, {\n method: 'POST',\n credentials: 'include',\n });\n if (!response.ok) {\n throw new Error(`Logout failed: ${response.status}`);\n }\n await response.body?.cancel();\n\n store.setState({\n expiresAt: null,\n whoami: null,\n isSessionConnected: false,\n isDisconnectInFlight: false,\n });\n\n logger.info('disconnectFromHubSpot: complete');\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Disconnect failed';\n logger.error('disconnectFromHubSpot: failed', err);\n store.setState({\n error: message,\n isDisconnectInFlight: false,\n });\n }\n}\n","import {\n AUTH_COMPLETE_CODE_PARAM,\n AUTH_COMPLETE_STATE_PARAM,\n OAUTH_CALLBACK_PATH,\n} from '../../shared/constants.ts';\nimport { completeHubSpotOAuthSession } from './oauth-complete.ts';\nimport {\n isOAuthPopupCallback,\n relayOAuthCallbackToOpener,\n} from './oauth-popup.ts';\nimport type { AppConnectContext } from './types.ts';\nimport { storeSession } from './utils/session-utils.ts';\n\n/**\n * On `controller.start()`:\n *\n * 1. If the browser has been redirected back to the SDK's frontend\n * OAuth callback path (`/__hubspot_oauth_callback`) with `?code` +\n * `?state`:\n * - **Redirect flow** (no `window.opener`): POST to `auth/complete`\n * from this window, persist `expires_at`, and `history.replaceState`\n * to `return_path`.\n * - **Popup flow** (`window.opener` present): relay `code` + `state`\n * to the opener via `postMessage` and close. The opener POSTs to\n * `auth/complete` so partitioned cookies match `init-session`.\n * 2. Pick up `?__hs_expires_at` from `window.location` (refresh hop),\n * persist it, and strip it from the address bar.\n *\n * A no-op when neither set of parameters is present.\n */\nexport async function initAppConnect(\n context: AppConnectContext\n): Promise<void> {\n await consumeOAuthCallback(context);\n}\n\nasync function consumeOAuthCallback(context: AppConnectContext): Promise<void> {\n if (window.location.pathname !== OAUTH_CALLBACK_PATH) return;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(AUTH_COMPLETE_CODE_PARAM);\n const state = params.get(AUTH_COMPLETE_STATE_PARAM);\n if (!code || !state) return;\n\n if (isOAuthPopupCallback() && relayOAuthCallbackToOpener({ code, state })) {\n return;\n }\n\n const body = await completeHubSpotOAuthSession(context, { code, state });\n const { expires_at: expiresAt, return_path: returnPath } = body;\n\n storeSession({ context, expiresAtMs: expiresAt, whoami: body.whoami });\n\n const targetUrl = new URL(returnPath, window.location.origin);\n history.replaceState(\n null,\n '',\n `${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`\n );\n}\n","import type { RefreshTokenResponse } from '../../shared/wire-types.ts';\nimport { REFRESH_BUFFER_MS } from './constants.ts';\nimport type { AppConnectContext } from './types.ts';\nimport {\n isClientSessionActive,\n storeExpiresAt,\n} from './utils/session-utils.ts';\n\n/**\n * Tear-down handle returned by {@link startRefreshScheduler}. Calling\n * `stop()` clears any pending refresh timer and unsubscribes from the\n * store so the controller can be garbage-collected.\n */\nexport interface RefreshSchedulerHandle {\n stop: () => void;\n}\n\nasync function refreshAccessToken(context: AppConnectContext): Promise<void> {\n const { config } = context;\n\n const refreshResponse = await fetch(\n `${config.hubSpotConnectBaseUrl}/auth/refresh`,\n {\n method: 'POST',\n credentials: 'include',\n }\n );\n if (!refreshResponse.ok) {\n throw new Error(`Refresh failed: ${refreshResponse.status}`);\n }\n const { expires_in: expiresInSeconds } =\n (await refreshResponse.json()) as RefreshTokenResponse;\n if (\n typeof expiresInSeconds !== 'number' ||\n !Number.isFinite(expiresInSeconds) ||\n expiresInSeconds <= 0\n ) {\n throw new Error('Refresh response missing or invalid expires_in');\n }\n const expiresAtMs = Date.now() + expiresInSeconds * 1000;\n\n storeExpiresAt({ context, expiresAtMs });\n}\n\n/**\n * Subscribes to store changes and (re)schedules a token refresh\n * whenever `expiresAt` moves. Returns a handle that the caller can\n * use to stop the scheduler when the controller is destroyed.\n */\nexport function startRefreshScheduler(\n context: AppConnectContext\n): RefreshSchedulerHandle {\n const { logger, store } = context;\n\n let refreshTimer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n\n const scheduleRefresh = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n if (stopped) return;\n\n const state = store.getSnapshot();\n if (!state.isInitComplete || !state.isSessionConnected) {\n return;\n }\n\n const expiresAt = state.expiresAt;\n if (!expiresAt) {\n logger.debug('scheduleRefresh: no expiresAt, skipping');\n return;\n }\n const delayMs = Math.max(0, expiresAt - Date.now() - REFRESH_BUFFER_MS);\n logger.debug(\n 'scheduleRefresh: next refresh in ',\n (delayMs / 1000).toFixed(1),\n 's',\n {\n expiresAt,\n }\n );\n refreshTimer = setTimeout(() => {\n logger.debug('scheduleRefresh: timer fired, refreshing token');\n refreshTimer = null;\n if (stopped) return;\n\n void (async () => {\n try {\n await refreshAccessToken(context);\n if (stopped) return;\n if (isClientSessionActive(context)) {\n logger.info('token refresh: success, session still active');\n } else {\n logger.warn(\n 'token refresh: success but no active session in storage'\n );\n store.setState({ isSessionConnected: false });\n }\n } catch (err) {\n logger.error('token refresh: failed', err);\n if (stopped) return;\n store.setState({ isSessionConnected: false });\n }\n })();\n }, delayMs);\n };\n\n const unsubscribe = store.subscribe(() => {\n scheduleRefresh();\n });\n\n return {\n stop: () => {\n stopped = true;\n unsubscribe();\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n },\n };\n}\n","/**\n * Wraps `fn` so that calls with the same input (compared via\n * `Object.is`) return the previous output without re-invoking `fn`.\n * The cache holds at most one entry, so this is safe to use for\n * derived view-state from a single store snapshot.\n *\n * Used by `getSnapshot` to keep the React state reference stable\n * between unrelated store updates — `useSyncExternalStore` would\n * otherwise re-render every consumer on every change.\n */\nexport function memoizeLast<TInput, TOutput>(\n fn: (input: TInput) => TOutput\n): (input: TInput) => TOutput {\n let lastInput: TInput;\n let lastOutput: TOutput;\n let hasValue = false;\n return (input: TInput): TOutput => {\n if (hasValue && Object.is(lastInput, input)) {\n return lastOutput;\n }\n lastInput = input;\n lastOutput = fn(input);\n hasValue = true;\n return lastOutput;\n };\n}\n","/**\n * Tiny external store used by the controller. Shaped to be compatible\n * with React's `useSyncExternalStore` while remaining usable outside\n * React.\n */\nexport interface Store<TState extends object> {\n /** Returns the current state. The reference changes on every update. */\n getSnapshot: () => Readonly<TState>;\n /**\n * Subscribes to state changes. Returns an unsubscribe function the\n * caller can invoke at teardown.\n */\n subscribe: (onChange: () => void) => () => void;\n /**\n * Merges `update` into the current state. When `update` is a\n * function, it receives the current state and returns a partial.\n * Listeners are only notified when at least one key actually\n * changed (shallow compare).\n */\n setState: (\n update:\n | Partial<TState>\n | ((prev: Readonly<TState>) => Partial<TState> | TState)\n ) => void;\n /** Reads a single key from the current state. */\n get: <K extends keyof TState>(key: K) => TState[K];\n /** Writes a single key. Listeners only fire when the value changes. */\n set: <K extends keyof TState>(key: K, value: TState[K]) => void;\n /**\n * Drops every listener and prevents future `setState`/`set` calls\n * from notifying. Used by `controller.destroy()`.\n */\n destroy: () => void;\n}\n\nfunction shallowEqualState<TState extends object>(\n a: TState,\n b: TState\n): boolean {\n const keys = new Set([\n ...Object.keys(a),\n ...Object.keys(b),\n ] as (keyof TState)[]);\n for (const k of keys) {\n if (!Object.is(a[k], b[k])) {\n return false;\n }\n }\n return true;\n}\n\nfunction mergeState<TState extends object>(\n prev: TState,\n partial: Partial<TState>\n): TState {\n return { ...prev, ...partial } as TState;\n}\n\n/**\n * Creates a new {@link Store}. The store starts with a shallow copy\n * of `initialState`; subsequent mutations never touch the caller's\n * object.\n */\nexport function createStore<TState extends object>(\n initialState: TState\n): Store<TState> {\n let state: TState = { ...initialState };\n const listeners = new Set<() => void>();\n let destroyed = false;\n\n const notify = () => {\n for (const listener of listeners) {\n listener();\n }\n };\n\n return {\n getSnapshot() {\n return state as Readonly<TState>;\n },\n subscribe(onChange) {\n listeners.add(onChange);\n return () => {\n listeners.delete(onChange);\n };\n },\n setState(update) {\n if (destroyed) return;\n const patch =\n typeof update === 'function'\n ? update(state as Readonly<TState>)\n : update;\n if (typeof patch !== 'object' || patch == null) {\n return;\n }\n const next = mergeState(state, patch as Partial<TState>);\n if (shallowEqualState(state, next)) {\n return;\n }\n state = next;\n notify();\n },\n get(key) {\n return state[key];\n },\n set(key, value) {\n if (destroyed) return;\n if (Object.is(state[key], value)) {\n return;\n }\n state = { ...state, [key]: value } as TState;\n notify();\n },\n destroy() {\n destroyed = true;\n listeners.clear();\n },\n };\n}\n","import type { AppConnectState, AppConnectStatus } from '../types.ts';\nimport type { AppConnectInternalState } from './types.ts';\n\nconst noop = (): Promise<void> => Promise.resolve();\n\n/**\n * Snapshot returned by `getServerSnapshot` for SSR. Has stable\n * references and inert connect/disconnect actions because actions are\n * meaningless before hydration.\n */\nexport const SERVER_VIEW: AppConnectState = {\n status: 'initializing',\n error: null,\n whoami: null,\n connectToHubSpot: noop,\n disconnectFromHubSpot: noop,\n};\n\n/**\n * Reduces the boolean lifecycle flags into the user-facing\n * `AppConnectStatus` enum value. The order of checks matters:\n * disconnect-in-flight beats connect-in-flight (a transitional logout\n * shouldn't show a \"connecting\" spinner), and connected beats default.\n */\nexport function getDerivedStatus(\n state: AppConnectInternalState\n): AppConnectStatus {\n const {\n isInitComplete,\n isConnectInFlight,\n isSessionConnected,\n isDisconnectInFlight,\n } = state;\n if (!isInitComplete) {\n return 'initializing';\n }\n if (isDisconnectInFlight) return 'disconnecting';\n if (isConnectInFlight) return 'connecting';\n if (isSessionConnected) return 'connected';\n return 'disconnected';\n}\n","import { noopLogger, type Logger } from '../../shared/logger.ts';\nimport type {\n AppConnectBrowserConfig,\n AppConnectController,\n AppConnectState,\n} from '../types.ts';\nimport { startHubSpotConnection } from './connect-start.ts';\nimport { createDefaultSessionStorage } from './default-session-storage.ts';\nimport { disconnectFromHubSpot as runDisconnectFromHubSpot } from './disconnect.ts';\nimport { initAppConnect } from './init.ts';\nimport { startRefreshScheduler } from './refresh.ts';\nimport type {\n AppConnectContext,\n AppConnectInternalState,\n AppConnectStore,\n} from './types.ts';\nimport { memoizeLast } from './utils/memoize-utils.ts';\nimport {\n getSessionFromSessionStorage,\n isClientSessionActive,\n} from './utils/session-utils.ts';\nimport { createStore } from './utils/store-utils.ts';\nimport { getDerivedStatus, SERVER_VIEW } from './view-state.ts';\n\n/**\n * Options accepted by {@link createAppConnectController}.\n */\nexport interface CreateAppConnectControllerOptions {\n /** Runtime configuration; see {@link AppConnectBrowserConfig}. */\n config: AppConnectBrowserConfig;\n /** Logger the controller uses for status/debug messages. */\n logger?: Logger;\n}\n\n/**\n * Creates an `AppConnectController`. Exactly one controller should be\n * shared by the entire app — the React provider takes the controller\n * as a prop and exposes it via context.\n *\n * The returned controller is inert until `start()` is called: nothing\n * is read from session storage, no refresh timer is scheduled, and no\n * fetches are issued. Tests can construct a controller and inspect\n * its initial snapshot without triggering side effects.\n */\nexport function createAppConnectController(\n options: CreateAppConnectControllerOptions\n): AppConnectController {\n const { config, logger = noopLogger } = options;\n const sessionStorage = createDefaultSessionStorage();\n const store: AppConnectStore = createStore<AppConnectInternalState>({\n isInitComplete: false,\n isConnectInFlight: false,\n isDisconnectInFlight: false,\n isSessionConnected: false,\n error: null,\n expiresAt: null,\n whoami: null,\n });\n const context: AppConnectContext = {\n config,\n logger,\n sessionStorage,\n store,\n };\n\n const storedSession = getSessionFromSessionStorage(context);\n store.setState({\n expiresAt: storedSession?.expiresAt ?? null,\n whoami: storedSession?.whoami ?? null,\n });\n\n let hasStarted = false;\n\n const connectToHubSpot = async () => {\n logger.info('connectToHubSpot: starting');\n store.setState({ error: null, isConnectInFlight: true });\n try {\n await startHubSpotConnection(context);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Connection failed';\n logger.error('connectToHubSpot: failed', err);\n store.setState({ error: message });\n } finally {\n logger.debug(\n 'connectToHubSpot: connect flow step finished (may redirect to HubSpot)'\n );\n store.setState({ isConnectInFlight: false });\n }\n };\n const disconnectFromHubSpot = () => runDisconnectFromHubSpot(context);\n\n const getViewStateMemoized = memoizeLast<\n Readonly<AppConnectInternalState>,\n AppConnectState\n >((storeState) => ({\n status: getDerivedStatus(storeState),\n error: storeState.error,\n whoami: storeState.whoami,\n connectToHubSpot,\n disconnectFromHubSpot,\n }));\n\n function getSnapshot() {\n return getViewStateMemoized(store.getSnapshot());\n }\n\n return {\n start() {\n if (hasStarted) {\n logger.debug('start skipped (already started)');\n return;\n }\n hasStarted = true;\n startRefreshScheduler(context);\n\n logger.info('start: initSdk (OAuth return handling if applicable)');\n void (async () => {\n try {\n await initAppConnect(context);\n logger.info('initSdk: completed without error');\n } catch (err) {\n logger.error('initSdk: failed', err);\n store.setState({\n error:\n err instanceof Error\n ? err.message\n : 'App Connect initialization failed',\n });\n } finally {\n const sessionActive = isClientSessionActive(context);\n logger.info('start: init complete, session active:', sessionActive);\n store.setState({\n isInitComplete: true,\n isSessionConnected: sessionActive,\n });\n }\n })();\n },\n subscribe: (fn) => store.subscribe(fn),\n getSnapshot,\n getServerSnapshot: () => SERVER_VIEW,\n };\n}\n"],"mappings":";AAYA,SAAS,aAAa,MAAsB;CAC1C,OAAO,IAAI,KAAK;AAClB;;;;;;AAOA,SAAgB,aAAa,MAAsB;CACjD,MAAM,SAAS,aAAa,IAAI;CAChC,OAAO;EACL,QAAQ,SAAS,GAAG,SAAS;GAC3B,QAAQ,MAAM,QAAQ,SAAS,GAAG,IAAI;EACxC;EACA,OAAO,SAAS,GAAG,SAAS;GAC1B,QAAQ,KAAK,QAAQ,SAAS,GAAG,IAAI;EACvC;EACA,OAAO,SAAS,GAAG,SAAS;GAC1B,QAAQ,KAAK,QAAQ,SAAS,GAAG,IAAI;EACvC;EACA,QAAQ,SAAS,GAAG,SAAS;GAC3B,QAAQ,MAAM,QAAQ,SAAS,GAAG,IAAI;EACxC;CACF;AACF;;;;;;AAOA,MAAa,aAAqB;CAChC,aAAa,CAAC;CACd,YAAY,CAAC;CACb,YAAY,CAAC;CACb,aAAa,CAAC;AAChB;;;;;ACfA,MAAa,2BAA2B;;;;;AAMxC,MAAa,4BAA4B;;;;;;;AAQzC,MAAa,oCACX;;;;;;;;;;ACxCF,MAAa,sBAAsB;;;;;;AAOnC,MAAa,oBAAoB;;;ACPjC,SAAS,kBAAkB,SAAkD;CAC3E,MAAM,MAAM,QAAQ,eAAe,QAAQ,mBAAmB;CAC9D,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EACN,QAAQ,eAAe,WAAW,mBAAmB;EACrD,OAAO;CACT;CACA,IAAI,OAAO,OAAO,cAAc,YAAY,MAAM,OAAO,SAAS,GAAG;EACnE,QAAQ,eAAe,WAAW,mBAAmB;EACrD,OAAO;CACT;CACA,IAAI,KAAK,IAAI,IAAI,OAAO,WAAW;EACjC,QAAQ,eAAe,WAAW,mBAAmB;EACrD,OAAO;CACT;CACA,OAAO;AACT;AAEA,SAAS,mBACP,SACA,SACM;CACN,QAAQ,eAAe,QAAQ,qBAAqB,KAAK,UAAU,OAAO,CAAC;AAC7E;;;;;;AAOA,SAAgB,6BACd,SACsB;CACtB,OAAO,kBAAkB,OAAO;AAClC;;;;;;AAOA,SAAgB,aAAa,SAIpB;CACP,MAAM,EAAE,SAAS,aAAa,WAAW;CACzC,QAAQ,MAAM,SAAS;EAAE,WAAW;EAAa;CAAO,CAAC;CACzD,mBAAmB,SAAS;EAAE,WAAW;EAAa;CAAO,CAAC;AAChE;;;;;;AAOA,SAAgB,eAAe,SAGtB;CACP,MAAM,EAAE,SAAS,gBAAgB;CACjC,MAAM,gBAAgB,QAAQ,MAAM,YAAY,EAAE;CAClD,QAAQ,MAAM,SAAS,EAAE,WAAW,YAAY,CAAC;CACjD,mBAAmB,SAAS;EAC1B,WAAW;EACX,QAAQ;CACV,CAAC;AACH;;;;;AAMA,SAAgB,oBAAoB,SAAkC;CACpE,QAAQ,eAAe,WAAW,mBAAmB;AACvD;;;;;;AAOA,SAAgB,sBAAsB,SAAqC;CAEzE,MAAM,YADQ,QAAQ,MAAM,YACN,EAAE;CACxB,OAAO,cAAc,QAAQ,KAAK,IAAI,IAAI;AAC5C;;;;;;;;AChFA,eAAsB,4BACpB,SACA,SAC+B;CAC/B,MAAM,EAAE,MAAM,UAAU;CAExB,MAAM,cAAc,IAAI,IACtB,GAAG,QAAQ,OAAO,sBAAsB,iBACxC,OAAO,SAAS,MAClB;CACA,YAAY,aAAa,IAAI,0BAA0B,IAAI;CAC3D,YAAY,aAAa,IAAI,2BAA2B,KAAK;CAE7D,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,MAAM,YAAY,SAAS,GAAG;GAC7C,QAAQ;GACR,aAAa;EACf,CAAC;CACH,SAAS,KAAK;EACZ,oBAAoB,OAAO;EAC3B,MAAM,IAAI,MACR,qCAAqC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GACtF;CACF;CAEA,IAAI,CAAC,SAAS,IAAI;EAChB,oBAAoB,OAAO;EAC3B,MAAM,IAAI,MACR,qCAAqC,SAAS,OAAO,GAAG,SAAS,YACnE;CACF;CAEA,OAAQ,MAAM,SAAS,KAAK;AAC9B;;;AC/CA,MAAa,0BAA0B;AACvC,MAAM,+BAA+B;AAkBrC,SAAS,4BACP,MACmC;CACnC,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM,OAAO;CACtD,MAAM,SAAS;CACf,OACE,OAAO,SAAA,wCACP,OAAO,OAAO,SAAS,YACvB,OAAO,KAAK,SAAS,KACrB,OAAO,OAAO,UAAU,YACxB,OAAO,MAAM,SAAS;AAE1B;;;;;;;AAQA,eAAsB,yBACpB,SACe;CACf,MAAM,EAAE,SAAS,qBAAqB;CACtC,MAAM,eAAe,OAAO,SAAS;CAErC,OAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,IAAI,QAAuB;EAC3B,IAAI;EACJ,IAAI,YAAY;EAChB,IAAI,qBAAqB;EAEzB,MAAM,gBAAgB;GACpB,OAAO,oBAAoB,WAAW,SAAS;GAC/C,IAAI,cAAc,KAAA,GAAW,cAAc,SAAS;EACtD;EAEA,MAAM,iBAAiB,gBAAwB;GAC7C,IAAI,WAAW;GACf,YAAY;GACZ,QAAQ;GACR,eAAe;IAAE;IAAS;GAAY,CAAC;GACvC,QAAQ,MAAM,SAAS;IAAE,oBAAoB;IAAM,OAAO;GAAK,CAAC;GAChE,QAAQ;EACV;EAEA,MAAM,iBAAiB,YAAoB;GACzC,IAAI,WAAW;GACf,YAAY;GACZ,QAAQ;GACR,IAAI;IACF,OAAO,MAAM;GACf,QAAQ,CAER;GACA,OAAO,IAAI,MAAM,OAAO,CAAC;EAC3B;EAEA,MAAM,aAAa,UAAwB;GACzC,IAAI,MAAM,WAAW,cAAc;GACnC,IAAI,CAAC,4BAA4B,MAAM,IAAI,GAAG;GAE9C,IAAI,oBAAoB;GACxB,qBAAqB;GAErB,CAAM,YAAY;IAChB,IAAI;KAKF,eAAc,MAJK,4BAA4B,SAAS;MACtD,MAAM,MAAM,KAAK;MACjB,OAAO,MAAM,KAAK;KACpB,CAAC,GACkB,UAAU;IAC/B,SAAS,KAAK;KAGZ,cADE,eAAe,QAAQ,IAAI,UAAU,sBAClB;IACvB;GACF,GAAG;EACL;EAEA,OAAO,iBAAiB,WAAW,SAAS;EAE5C,QAAQ,OAAO,KACb,kBACA,yBACA,gCACF;EACA,IAAI,CAAC,OAAO;GACV,cAAc,0DAA0D;GACxE;EACF;EAEA,YAAY,kBAAkB;GAC5B,IAAI,OAAO,UAAU,CAAC,oBACpB,cAAc,mCAAmC;EAErD,GAAG,4BAA4B;CACjC,CAAC;AACH;;;;;AAMA,SAAgB,2BACd,SACS;CACT,MAAM,EAAE,MAAM,UAAU;CACxB,MAAM,SAAS,OAAO;CACtB,IAAI,CAAC,UAAU,OAAO,QAAQ,OAAO;CAErC,MAAM,UAAqC;EACzC,MAAM;EACN;EACA;CACF;CACA,OAAO,YAAY,SAAS,OAAO,SAAS,MAAM;CAClD,IAAI;EACF,OAAO,MAAM;CACf,QAAQ,CAER;CACA,OAAO;AACT;AAEA,SAAgB,sBAA+B;CAC7C,IAAI;EACF,OAAO,QAAQ,OAAO,UAAU,CAAC,OAAO,OAAO,MAAM;CACvD,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,uBAAgC;CAC9C,OAAO,OAAO,SAAA,+BAAoC,oBAAoB;AACxE;;;;;;;;AC1JA,SAAgB,wBAAiC;CAC/C,IAAI;EACF,OAAO,OAAO,SAAS,OAAO;CAChC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;ACEA,SAAgB,wBACd,SAC0B;CAC1B,MAAM,OAAO,QAAQ,oBAAoB;CACzC,IAAI,SAAS,SAAS,OAAO;CAC7B,IAAI,SAAS,YAAY,OAAO;CAChC,OAAO,sBAAsB,IAAI,UAAU;AAC7C;;;ACpBA,SAAgB,MAAM,IAA2B;CAC/C,IAAI,MAAM,GACR,OAAO,QAAQ,QAAQ;CAEzB,OAAO,IAAI,SAAS,YAAY;EAC9B,WAAW,SAAS,EAAE;CACxB,CAAC;AACH;;;;ACAA,MAAM,uCAAuC;;;;;;;;;;;;;;;;AAiB7C,eAAsB,uBACpB,SACe;CACf,MAAM,EAAE,WAAW;CAEnB,MAAM,aAAa,GAAG,OAAO,SAAS,WAAW,OAAO,SAAS;CAEjE,MAAM,UAAU,IAAI,IAClB,GAAG,OAAO,sBAAsB,qBAChC,OAAO,SAAS,MAClB;CACA,QAAQ,aAAa,IAAI,eAAe,UAAU;CAElD,MAAM,eAAe,MAAM,MAAM,QAAQ,SAAS,GAAG,EACnD,aAAa,UACf,CAAC;CACD,IAAI,CAAC,aAAa,IAChB,MAAM,IAAI,MAAM,2BAA2B,aAAa,QAAQ;CAClE,MAAM,EAAE,mBAAmB,qBACxB,MAAM,aAAa,KAAK;CAE3B,MAAM,MAAM,oCAAoC;CAQhD,IANoB,wBAClB,OAAO,qBAAqB,KAAA,IACxB,EAAE,kBAAkB,OAAO,iBAAiB,IAC5C,CAAC,CAGO,MAAM,SAAS;EAC3B,MAAM,yBAAyB;GAAE;GAAS;EAAiB,CAAC;EAC5D;CACF;CAEA,OAAO,SAAS,OAAO;AACzB;;;;;;;;;ACnDA,SAAgB,8BAA8C;CAC5D,OAAO;EACL,UAAU,KAAK,UAAU;GACvB,eAAe,QAAQ,KAAK,KAAK;EACnC;EACA,UAAU,QAAQ;GAChB,OAAO,eAAe,QAAQ,GAAG;EACnC;EACA,aAAa,QAAQ;GACnB,eAAe,WAAW,GAAG;EAC/B;CACF;AACF;;;;;;;;;;;;;;;ACLA,eAAsB,sBACpB,SACe;CACf,MAAM,EAAE,QAAQ,QAAQ,UAAU;CAClC,OAAO,KAAK,iCAAiC;CAC7C,MAAM,SAAS;EAAE,OAAO;EAAM,sBAAsB;CAAK,CAAC;CAC1D,MAAM,EAAE,uBAAuB,sBAAsB;CAErD,IAAI;EACF,oBAAoB,OAAO;EAE3B,MAAM,WAAW,MAAM,MAAM,GAAG,kBAAkB,eAAe;GAC/D,QAAQ;GACR,aAAa;EACf,CAAC;EACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,kBAAkB,SAAS,QAAQ;EAErD,MAAM,SAAS,MAAM,OAAO;EAE5B,MAAM,SAAS;GACb,WAAW;GACX,QAAQ;GACR,oBAAoB;GACpB,sBAAsB;EACxB,CAAC;EAED,OAAO,KAAK,iCAAiC;CAC/C,SAAS,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;EACrD,OAAO,MAAM,iCAAiC,GAAG;EACjD,MAAM,SAAS;GACb,OAAO;GACP,sBAAsB;EACxB,CAAC;CACH;AACF;;;;;;;;;;;;;;;;;;;;ACrBA,eAAsB,eACpB,SACe;CACf,MAAM,qBAAqB,OAAO;AACpC;AAEA,eAAe,qBAAqB,SAA2C;CAC7E,IAAI,OAAO,SAAS,aAAA,6BAAkC;CAEtD,MAAM,SAAS,IAAI,gBAAgB,OAAO,SAAS,MAAM;CACzD,MAAM,OAAO,OAAO,IAAI,wBAAwB;CAChD,MAAM,QAAQ,OAAO,IAAI,yBAAyB;CAClD,IAAI,CAAC,QAAQ,CAAC,OAAO;CAErB,IAAI,qBAAqB,KAAK,2BAA2B;EAAE;EAAM;CAAM,CAAC,GACtE;CAGF,MAAM,OAAO,MAAM,4BAA4B,SAAS;EAAE;EAAM;CAAM,CAAC;CACvE,MAAM,EAAE,YAAY,WAAW,aAAa,eAAe;CAE3D,aAAa;EAAE;EAAS,aAAa;EAAW,QAAQ,KAAK;CAAO,CAAC;CAErE,MAAM,YAAY,IAAI,IAAI,YAAY,OAAO,SAAS,MAAM;CAC5D,QAAQ,aACN,MACA,IACA,GAAG,UAAU,WAAW,UAAU,SAAS,UAAU,MACvD;AACF;;;AC1CA,eAAe,mBAAmB,SAA2C;CAC3E,MAAM,EAAE,WAAW;CAEnB,MAAM,kBAAkB,MAAM,MAC5B,GAAG,OAAO,sBAAsB,gBAChC;EACE,QAAQ;EACR,aAAa;CACf,CACF;CACA,IAAI,CAAC,gBAAgB,IACnB,MAAM,IAAI,MAAM,mBAAmB,gBAAgB,QAAQ;CAE7D,MAAM,EAAE,YAAY,qBACjB,MAAM,gBAAgB,KAAK;CAC9B,IACE,OAAO,qBAAqB,YAC5B,CAAC,OAAO,SAAS,gBAAgB,KACjC,oBAAoB,GAEpB,MAAM,IAAI,MAAM,gDAAgD;CAIlE,eAAe;EAAE;EAAS,aAFN,KAAK,IAAI,IAAI,mBAAmB;CAEd,CAAC;AACzC;;;;;;AAOA,SAAgB,sBACd,SACwB;CACxB,MAAM,EAAE,QAAQ,UAAU;CAE1B,IAAI,eAAqD;CACzD,IAAI,UAAU;CAEd,MAAM,wBAAwB;EAC5B,IAAI,cAAc;GAChB,aAAa,YAAY;GACzB,eAAe;EACjB;EACA,IAAI,SAAS;EAEb,MAAM,QAAQ,MAAM,YAAY;EAChC,IAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,oBAClC;EAGF,MAAM,YAAY,MAAM;EACxB,IAAI,CAAC,WAAW;GACd,OAAO,MAAM,yCAAyC;GACtD;EACF;EACA,MAAM,UAAU,KAAK,IAAI,GAAG,YAAY,KAAK,IAAI,IAAI,iBAAiB;EACtE,OAAO,MACL,sCACC,UAAU,KAAM,QAAQ,CAAC,GAC1B,KACA,EACE,UACF,CACF;EACA,eAAe,iBAAiB;GAC9B,OAAO,MAAM,gDAAgD;GAC7D,eAAe;GACf,IAAI,SAAS;GAEb,CAAM,YAAY;IAChB,IAAI;KACF,MAAM,mBAAmB,OAAO;KAChC,IAAI,SAAS;KACb,IAAI,sBAAsB,OAAO,GAC/B,OAAO,KAAK,8CAA8C;UACrD;MACL,OAAO,KACL,yDACF;MACA,MAAM,SAAS,EAAE,oBAAoB,MAAM,CAAC;KAC9C;IACF,SAAS,KAAK;KACZ,OAAO,MAAM,yBAAyB,GAAG;KACzC,IAAI,SAAS;KACb,MAAM,SAAS,EAAE,oBAAoB,MAAM,CAAC;IAC9C;GACF,GAAG;EACL,GAAG,OAAO;CACZ;CAEA,MAAM,cAAc,MAAM,gBAAgB;EACxC,gBAAgB;CAClB,CAAC;CAED,OAAO,EACL,YAAY;EACV,UAAU;EACV,YAAY;EACZ,IAAI,cAAc;GAChB,aAAa,YAAY;GACzB,eAAe;EACjB;CACF,EACF;AACF;;;;;;;;;;;;;ACjHA,SAAgB,YACd,IAC4B;CAC5B,IAAI;CACJ,IAAI;CACJ,IAAI,WAAW;CACf,QAAQ,UAA2B;EACjC,IAAI,YAAY,OAAO,GAAG,WAAW,KAAK,GACxC,OAAO;EAET,YAAY;EACZ,aAAa,GAAG,KAAK;EACrB,WAAW;EACX,OAAO;CACT;AACF;;;ACUA,SAAS,kBACP,GACA,GACS;CACT,MAAM,OAAO,IAAI,IAAI,CACnB,GAAG,OAAO,KAAK,CAAC,GAChB,GAAG,OAAO,KAAK,CAAC,CAClB,CAAqB;CACrB,KAAK,MAAM,KAAK,MACd,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,GACvB,OAAO;CAGX,OAAO;AACT;AAEA,SAAS,WACP,MACA,SACQ;CACR,OAAO;EAAE,GAAG;EAAM,GAAG;CAAQ;AAC/B;;;;;;AAOA,SAAgB,YACd,cACe;CACf,IAAI,QAAgB,EAAE,GAAG,aAAa;CACtC,MAAM,4BAAY,IAAI,IAAgB;CACtC,IAAI,YAAY;CAEhB,MAAM,eAAe;EACnB,KAAK,MAAM,YAAY,WACrB,SAAS;CAEb;CAEA,OAAO;EACL,cAAc;GACZ,OAAO;EACT;EACA,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;EACA,SAAS,QAAQ;GACf,IAAI,WAAW;GACf,MAAM,QACJ,OAAO,WAAW,aACd,OAAO,KAAyB,IAChC;GACN,IAAI,OAAO,UAAU,YAAY,SAAS,MACxC;GAEF,MAAM,OAAO,WAAW,OAAO,KAAwB;GACvD,IAAI,kBAAkB,OAAO,IAAI,GAC/B;GAEF,QAAQ;GACR,OAAO;EACT;EACA,IAAI,KAAK;GACP,OAAO,MAAM;EACf;EACA,IAAI,KAAK,OAAO;GACd,IAAI,WAAW;GACf,IAAI,OAAO,GAAG,MAAM,MAAM,KAAK,GAC7B;GAEF,QAAQ;IAAE,GAAG;KAAQ,MAAM;GAAM;GACjC,OAAO;EACT;EACA,UAAU;GACR,YAAY;GACZ,UAAU,MAAM;EAClB;CACF;AACF;;;ACnHA,MAAM,aAA4B,QAAQ,QAAQ;;;;;;AAOlD,MAAa,cAA+B;CAC1C,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,kBAAkB;CAClB,uBAAuB;AACzB;;;;;;;AAQA,SAAgB,iBACd,OACkB;CAClB,MAAM,EACJ,gBACA,mBACA,oBACA,yBACE;CACJ,IAAI,CAAC,gBACH,OAAO;CAET,IAAI,sBAAsB,OAAO;CACjC,IAAI,mBAAmB,OAAO;CAC9B,IAAI,oBAAoB,OAAO;CAC/B,OAAO;AACT;;;;;;;;;;;;;ACIA,SAAgB,2BACd,SACsB;CACtB,MAAM,EAAE,QAAQ,SAAS,eAAe;CACxC,MAAM,iBAAiB,4BAA4B;CACnD,MAAM,QAAyB,YAAqC;EAClE,gBAAgB;EAChB,mBAAmB;EACnB,sBAAsB;EACtB,oBAAoB;EACpB,OAAO;EACP,WAAW;EACX,QAAQ;CACV,CAAC;CACD,MAAM,UAA6B;EACjC;EACA;EACA;EACA;CACF;CAEA,MAAM,gBAAgB,6BAA6B,OAAO;CAC1D,MAAM,SAAS;EACb,WAAW,eAAe,aAAa;EACvC,QAAQ,eAAe,UAAU;CACnC,CAAC;CAED,IAAI,aAAa;CAEjB,MAAM,mBAAmB,YAAY;EACnC,OAAO,KAAK,4BAA4B;EACxC,MAAM,SAAS;GAAE,OAAO;GAAM,mBAAmB;EAAK,CAAC;EACvD,IAAI;GACF,MAAM,uBAAuB,OAAO;EACtC,SAAS,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;GACrD,OAAO,MAAM,4BAA4B,GAAG;GAC5C,MAAM,SAAS,EAAE,OAAO,QAAQ,CAAC;EACnC,UAAU;GACR,OAAO,MACL,wEACF;GACA,MAAM,SAAS,EAAE,mBAAmB,MAAM,CAAC;EAC7C;CACF;CACA,MAAMA,gCAA8BC,sBAAyB,OAAO;CAEpE,MAAM,uBAAuB,aAG1B,gBAAgB;EACjB,QAAQ,iBAAiB,UAAU;EACnC,OAAO,WAAW;EAClB,QAAQ,WAAW;EACnB;EACA,uBAAA;CACF,EAAE;CAEF,SAAS,cAAc;EACrB,OAAO,qBAAqB,MAAM,YAAY,CAAC;CACjD;CAEA,OAAO;EACL,QAAQ;GACN,IAAI,YAAY;IACd,OAAO,MAAM,iCAAiC;IAC9C;GACF;GACA,aAAa;GACb,sBAAsB,OAAO;GAE7B,OAAO,KAAK,sDAAsD;GAClE,CAAM,YAAY;IAChB,IAAI;KACF,MAAM,eAAe,OAAO;KAC5B,OAAO,KAAK,kCAAkC;IAChD,SAAS,KAAK;KACZ,OAAO,MAAM,mBAAmB,GAAG;KACnC,MAAM,SAAS,EACb,OACE,eAAe,QACX,IAAI,UACJ,oCACR,CAAC;IACH,UAAU;KACR,MAAM,gBAAgB,sBAAsB,OAAO;KACnD,OAAO,KAAK,yCAAyC,aAAa;KAClE,MAAM,SAAS;MACb,gBAAgB;MAChB,oBAAoB;KACtB,CAAC;IACH;GACF,GAAG;EACL;EACA,YAAY,OAAO,MAAM,UAAU,EAAE;EACrC;EACA,yBAAyB;CAC3B;AACF"}
@@ -1,4 +1,4 @@
1
- import { a as OAuthConnectMode, i as AppConnectStatus, n as AppConnectController, r as AppConnectState, t as AppConnectBrowserConfig } from "./types-DkAmHcZt.js";
1
+ import { a as OAuthConnectMode, i as AppConnectStatus, n as AppConnectController, r as AppConnectState, t as AppConnectBrowserConfig } from "./types-C3wed8dU.js";
2
2
 
3
3
  //#region src/shared/logger.d.ts
4
4
  /**
@@ -1,3 +1,3 @@
1
- import { n as createLogger, t as createAppConnectController } from "./create-BNQazCF-.js";
1
+ import { n as createLogger, t as createAppConnectController } from "./create-DxEyGG-k.js";
2
2
  import { n as themeVars, t as themeClass } from "./theme.css-CJbxi5hC.js";
3
3
  export { createAppConnectController, createLogger, themeClass, themeVars };
@@ -1,4 +1,4 @@
1
- import { a as OAuthConnectMode } from "../types-DkAmHcZt.js";
1
+ import { a as OAuthConnectMode } from "../types-C3wed8dU.js";
2
2
  import { ReactNode } from "react";
3
3
 
4
4
  //#region src/browser/react/lovable/LovableHubSpotAppConnect.d.ts
@@ -1,5 +1,5 @@
1
- import { t as createAppConnectController } from "../create-BNQazCF-.js";
2
- import { t as HubSpotAppConnect } from "../HubSpotAppConnect-DFe9b90e.js";
1
+ import { t as createAppConnectController } from "../create-DxEyGG-k.js";
2
+ import { t as HubSpotAppConnect } from "../HubSpotAppConnect-721kYr9d.js";
3
3
  import { useRef } from "react";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  //#region src/browser/react/lovable/LovableHubSpotAppConnect.tsx
@@ -1,4 +1,4 @@
1
- import { n as AppConnectController, r as AppConnectState } from "./types-DkAmHcZt.js";
1
+ import { n as AppConnectController, r as AppConnectState } from "./types-C3wed8dU.js";
2
2
  import { ReactNode } from "react";
3
3
 
4
4
  //#region src/browser/react/components/HubSpotAppConnect/HubSpotAppConnect.d.ts
@@ -1,2 +1,2 @@
1
- import { n as useHubSpotAppConnect, t as HubSpotAppConnect } from "./HubSpotAppConnect-DFe9b90e.js";
1
+ import { n as useHubSpotAppConnect, t as HubSpotAppConnect } from "./HubSpotAppConnect-721kYr9d.js";
2
2
  export { HubSpotAppConnect, useHubSpotAppConnect };