@hubspot/app-connect-sdk 1.0.0-alpha.11 → 1.0.0-alpha.13

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 (199) hide show
  1. package/.turbo/turbo-format$colon$check.log +1 -1
  2. package/.turbo/turbo-test.log +81 -50
  3. package/.turbo/turbo-tsdown.log +119 -119
  4. package/build/tsconfig.browser.tsbuildinfo +1 -1
  5. package/build/tsconfig.server.tsbuildinfo +1 -1
  6. package/dist/browser/{HubSpotAppConnect-COQgPrFn.js → HubSpotAppConnect-DFe9b90e.js} +1 -2
  7. package/dist/browser/HubSpotAppConnect-DFe9b90e.js.map +1 -0
  8. package/dist/browser/create-crdncXsh.js.map +1 -1
  9. package/dist/browser/react/lovable.d.ts +1 -2
  10. package/dist/browser/react/lovable.js +1 -1
  11. package/dist/browser/react/lovable.js.map +1 -1
  12. package/dist/browser/react.d.ts +1 -2
  13. package/dist/browser/react.js +1 -1
  14. package/dist/server/api-client-core/apis/account/account-info.generated.js.map +1 -1
  15. package/dist/server/api-client-core/apis/account/audit-logs.generated.js.map +1 -1
  16. package/dist/server/api-client-core/apis/auth/oauth.generated.js.map +1 -1
  17. package/dist/server/api-client-core/apis/automation/actions.generated.js.map +1 -1
  18. package/dist/server/api-client-core/apis/automation/sequences.generated.js.map +1 -1
  19. package/dist/server/api-client-core/apis/business-units.generated.js.map +1 -1
  20. package/dist/server/api-client-core/apis/cms/authors.generated.js.map +1 -1
  21. package/dist/server/api-client-core/apis/cms/blog-settings.generated.js.map +1 -1
  22. package/dist/server/api-client-core/apis/cms/cms-content-audit.generated.js.map +1 -1
  23. package/dist/server/api-client-core/apis/cms/domains.generated.js.map +1 -1
  24. package/dist/server/api-client-core/apis/cms/hubdb.generated.js.map +1 -1
  25. package/dist/server/api-client-core/apis/cms/media-bridge.generated.js.map +1 -1
  26. package/dist/server/api-client-core/apis/cms/pages.generated.js.map +1 -1
  27. package/dist/server/api-client-core/apis/cms/posts.generated.js.map +1 -1
  28. package/dist/server/api-client-core/apis/cms/site-search.generated.js.map +1 -1
  29. package/dist/server/api-client-core/apis/cms/source-code.generated.js.map +1 -1
  30. package/dist/server/api-client-core/apis/cms/tags.generated.js.map +1 -1
  31. package/dist/server/api-client-core/apis/cms/url-mappings.generated.js.map +1 -1
  32. package/dist/server/api-client-core/apis/cms/url-redirects.generated.js.map +1 -1
  33. package/dist/server/api-client-core/apis/communication-preferences/subscriptions.generated.js.map +1 -1
  34. package/dist/server/api-client-core/apis/conversations/custom-channels.generated.js.map +1 -1
  35. package/dist/server/api-client-core/apis/conversations/visitor-identification.generated.js.map +1 -1
  36. package/dist/server/api-client-core/apis/conversations.generated.js.map +1 -1
  37. package/dist/server/api-client-core/apis/crm/app-uninstalls.generated.js.map +1 -1
  38. package/dist/server/api-client-core/apis/crm/appointments.generated.js.map +1 -1
  39. package/dist/server/api-client-core/apis/crm/associations-schema.generated.js.map +1 -1
  40. package/dist/server/api-client-core/apis/crm/associations.generated.js.map +1 -1
  41. package/dist/server/api-client-core/apis/crm/calling-extensions.generated.js.map +1 -1
  42. package/dist/server/api-client-core/apis/crm/calls.generated.js.map +1 -1
  43. package/dist/server/api-client-core/apis/crm/carts.generated.js.map +1 -1
  44. package/dist/server/api-client-core/apis/crm/commerce-payments.generated.js.map +1 -1
  45. package/dist/server/api-client-core/apis/crm/commerce-subscriptions.generated.js.map +1 -1
  46. package/dist/server/api-client-core/apis/crm/communications.generated.js.map +1 -1
  47. package/dist/server/api-client-core/apis/crm/companies.generated.js.map +1 -1
  48. package/dist/server/api-client-core/apis/crm/contacts.generated.js.map +1 -1
  49. package/dist/server/api-client-core/apis/crm/contracts.generated.js.map +1 -1
  50. package/dist/server/api-client-core/apis/crm/courses.generated.js.map +1 -1
  51. package/dist/server/api-client-core/apis/crm/crm-owners.generated.js.map +1 -1
  52. package/dist/server/api-client-core/apis/crm/custom-objects.generated.js.map +1 -1
  53. package/dist/server/api-client-core/apis/crm/deal-splits.generated.js.map +1 -1
  54. package/dist/server/api-client-core/apis/crm/deals.generated.js.map +1 -1
  55. package/dist/server/api-client-core/apis/crm/discounts.generated.js.map +1 -1
  56. package/dist/server/api-client-core/apis/crm/emails.generated.js.map +1 -1
  57. package/dist/server/api-client-core/apis/crm/exports.generated.js.map +1 -1
  58. package/dist/server/api-client-core/apis/crm/feedback-submissions.generated.js.map +1 -1
  59. package/dist/server/api-client-core/apis/crm/fees.generated.js.map +1 -1
  60. package/dist/server/api-client-core/apis/crm/goal-targets.generated.js.map +1 -1
  61. package/dist/server/api-client-core/apis/crm/imports.generated.js.map +1 -1
  62. package/dist/server/api-client-core/apis/crm/invoices.generated.js.map +1 -1
  63. package/dist/server/api-client-core/apis/crm/leads.generated.js.map +1 -1
  64. package/dist/server/api-client-core/apis/crm/limits-tracking.generated.js.map +1 -1
  65. package/dist/server/api-client-core/apis/crm/line-items.generated.js.map +1 -1
  66. package/dist/server/api-client-core/apis/crm/listings.generated.js.map +1 -1
  67. package/dist/server/api-client-core/apis/crm/lists.generated.js.map +1 -1
  68. package/dist/server/api-client-core/apis/crm/meetings.generated.js.map +1 -1
  69. package/dist/server/api-client-core/apis/crm/notes.generated.js.map +1 -1
  70. package/dist/server/api-client-core/apis/crm/object-library.generated.js.map +1 -1
  71. package/dist/server/api-client-core/apis/crm/objects.generated.js.map +1 -1
  72. package/dist/server/api-client-core/apis/crm/orders.generated.js.map +1 -1
  73. package/dist/server/api-client-core/apis/crm/partner-clients.generated.js.map +1 -1
  74. package/dist/server/api-client-core/apis/crm/partner-services.generated.js.map +1 -1
  75. package/dist/server/api-client-core/apis/crm/pipelines.generated.js.map +1 -1
  76. package/dist/server/api-client-core/apis/crm/postal-mail.generated.js.map +1 -1
  77. package/dist/server/api-client-core/apis/crm/products.generated.js.map +1 -1
  78. package/dist/server/api-client-core/apis/crm/projects.generated.js.map +1 -1
  79. package/dist/server/api-client-core/apis/crm/properties.generated.js.map +1 -1
  80. package/dist/server/api-client-core/apis/crm/property-validations.generated.js.map +1 -1
  81. package/dist/server/api-client-core/apis/crm/public-app-crm-cards.generated.js.map +1 -1
  82. package/dist/server/api-client-core/apis/crm/public-app-feature-flags.generated.js.map +1 -1
  83. package/dist/server/api-client-core/apis/crm/quotes.generated.js.map +1 -1
  84. package/dist/server/api-client-core/apis/crm/schemas.generated.js.map +1 -1
  85. package/dist/server/api-client-core/apis/crm/services.generated.js.map +1 -1
  86. package/dist/server/api-client-core/apis/crm/tasks.generated.js.map +1 -1
  87. package/dist/server/api-client-core/apis/crm/taxes.generated.js.map +1 -1
  88. package/dist/server/api-client-core/apis/crm/tickets.generated.js.map +1 -1
  89. package/dist/server/api-client-core/apis/crm/timeline.generated.js.map +1 -1
  90. package/dist/server/api-client-core/apis/crm/transcriptions.generated.js.map +1 -1
  91. package/dist/server/api-client-core/apis/crm/users.generated.js.map +1 -1
  92. package/dist/server/api-client-core/apis/crm/video-conferencing-extension.generated.js.map +1 -1
  93. package/dist/server/api-client-core/apis/events/manage-event-definitions.generated.js.map +1 -1
  94. package/dist/server/api-client-core/apis/events/send-event-completions.generated.js.map +1 -1
  95. package/dist/server/api-client-core/apis/events.generated.js.map +1 -1
  96. package/dist/server/api-client-core/apis/files.generated.js.map +1 -1
  97. package/dist/server/api-client-core/apis/marketing/campaigns-public-api.generated.js.map +1 -1
  98. package/dist/server/api-client-core/apis/marketing/marketing-emails.generated.js.map +1 -1
  99. package/dist/server/api-client-core/apis/marketing/marketing-events.generated.js.map +1 -1
  100. package/dist/server/api-client-core/apis/marketing/single-send.generated.js.map +1 -1
  101. package/dist/server/api-client-core/apis/marketing/transactional-single-send.generated.js.map +1 -1
  102. package/dist/server/api-client-core/apis/meta/origins.generated.js.map +1 -1
  103. package/dist/server/api-client-core/apis/scheduler/meetings.generated.js.map +1 -1
  104. package/dist/server/api-client-core/apis/settings/multicurrency.generated.js.map +1 -1
  105. package/dist/server/api-client-core/apis/settings/tax-rates.generated.js.map +1 -1
  106. package/dist/server/api-client-core/apis/settings/user-provisioning.generated.js.map +1 -1
  107. package/dist/server/api-client-core/apis/webhooks-journal.generated.js.map +1 -1
  108. package/dist/server/api-client-core/apis/webhooks.generated.js.map +1 -1
  109. package/dist/server/api-client-core/binary-data.js.map +1 -1
  110. package/dist/server/api-client-core/client.js +5 -1
  111. package/dist/server/api-client-core/client.js.map +1 -1
  112. package/dist/server/api-client-core/codegen-helpers/file-op-wrappers.js.map +1 -1
  113. package/dist/server/api-client-core/errors.js.map +1 -1
  114. package/dist/server/api-client-core/op.js.map +1 -1
  115. package/dist/server/api-client-core/pagination.js.map +1 -1
  116. package/dist/server/api-client-core/plugins/fetch-transport.js +28 -8
  117. package/dist/server/api-client-core/plugins/fetch-transport.js.map +1 -1
  118. package/dist/server/constants.js.map +1 -1
  119. package/dist/server/deno/start.js.map +1 -1
  120. package/dist/server/hono/hono-request-handler.js +21 -17
  121. package/dist/server/hono/hono-request-handler.js.map +1 -1
  122. package/dist/server/hono/hubspot-connect-routes/auth-complete.js +2 -1
  123. package/dist/server/hono/hubspot-connect-routes/auth-complete.js.map +1 -1
  124. package/dist/server/hono/hubspot-connect-routes/auth-init-session.js +3 -1
  125. package/dist/server/hono/hubspot-connect-routes/auth-init-session.js.map +1 -1
  126. package/dist/server/hono/hubspot-connect-routes/auth-logout.js +14 -8
  127. package/dist/server/hono/hubspot-connect-routes/auth-logout.js.map +1 -1
  128. package/dist/server/hono/hubspot-connect-routes/auth-refresh.js +26 -18
  129. package/dist/server/hono/hubspot-connect-routes/auth-refresh.js.map +1 -1
  130. package/dist/server/hono/hubspot-connect-routes/cimd-client-metadata-types.js.map +1 -1
  131. package/dist/server/hono/hubspot-connect-routes/cimd-public-routes.js +4 -1
  132. package/dist/server/hono/hubspot-connect-routes/cimd-public-routes.js.map +1 -1
  133. package/dist/server/hono/hubspot-connect-routes/fetch-hubspot-client-metadata.js.map +1 -1
  134. package/dist/server/hono/hubspot-connect-routes/hubspot-connect-routes.js.map +1 -1
  135. package/dist/server/hono/hubspot-connect-routes/load-hubspot-connect-routes-env.js.map +1 -1
  136. package/dist/server/hono/hubspot-connect-routes/oauth-client.js.map +1 -1
  137. package/dist/server/hono/hubspot-connect-routes/utils.js +5 -5
  138. package/dist/server/hono/hubspot-connect-routes/utils.js.map +1 -1
  139. package/dist/server/hono/types.d.ts +0 -5
  140. package/dist/server/hono/utils/cookie-utils.js.map +1 -1
  141. package/dist/server/hono/utils/cors-middleware.js.map +1 -1
  142. package/dist/server/import-app-keys.js.map +1 -1
  143. package/dist/server/lovable/create-app-function-start.d.ts +1 -1
  144. package/dist/server/lovable/create-app-function-start.js +4 -5
  145. package/dist/server/lovable/create-app-function-start.js.map +1 -1
  146. package/dist/server/lovable/hubspot-connect/index.js.map +1 -1
  147. package/dist/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.js +11 -11
  148. package/dist/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.js.map +1 -1
  149. package/dist/server/sanitize-request.js +0 -11
  150. package/dist/server/sanitize-request.js.map +1 -1
  151. package/dist/server/secure-start-core.js.map +1 -1
  152. package/dist/server/shared/constants.js +2 -2
  153. package/dist/server/shared/constants.js.map +1 -1
  154. package/dist/server/shared/encoding/base64.js.map +1 -1
  155. package/dist/server/shared/encoding/sha256.js.map +1 -1
  156. package/dist/server/shared/logger.js.map +1 -1
  157. package/dist/server/types.d.ts +1 -35
  158. package/dist/server/utils/cookie-utils.js.map +1 -1
  159. package/dist/server/utils/dpop-utils.js.map +1 -1
  160. package/dist/server/utils/env-utils.js +1 -1
  161. package/dist/server/utils/env-utils.js.map +1 -1
  162. package/dist/server/utils/hubspot-dpop-auth-headers.js +38 -0
  163. package/dist/server/utils/hubspot-dpop-auth-headers.js.map +1 -0
  164. package/dist/server/utils/jwk-utils.js.map +1 -1
  165. package/dist/server/utils/jwt-utils.js.map +1 -1
  166. package/package.json +16 -16
  167. package/src/browser/app-connect-controller/init.test.ts +4 -4
  168. package/src/browser/app-connect-controller/init.ts +2 -2
  169. package/src/browser/react/components/ConnectButton/ConnectButton.tsx +0 -1
  170. package/src/server/api-client-core/client.ts +5 -1
  171. package/src/server/api-client-core/plugins/fetch-transport.test.ts +114 -0
  172. package/src/server/api-client-core/plugins/fetch-transport.ts +65 -11
  173. package/src/server/api-client-core/plugins/index.ts +1 -0
  174. package/src/server/hono/hono-request-handler.ts +33 -19
  175. package/src/server/hono/hubspot-connect-routes/auth-complete.test.ts +2 -2
  176. package/src/server/hono/hubspot-connect-routes/auth-complete.ts +1 -0
  177. package/src/server/hono/hubspot-connect-routes/auth-init-session.test.ts +2 -2
  178. package/src/server/hono/hubspot-connect-routes/auth-init-session.ts +2 -0
  179. package/src/server/hono/hubspot-connect-routes/auth-logout.ts +21 -10
  180. package/src/server/hono/hubspot-connect-routes/auth-refresh.ts +18 -9
  181. package/src/server/hono/hubspot-connect-routes/cimd-public-routes.test.ts +7 -6
  182. package/src/server/hono/hubspot-connect-routes/cimd-public-routes.ts +5 -1
  183. package/src/server/hono/hubspot-connect-routes/hubspot-connect-routes.ts +2 -1
  184. package/src/server/hono/hubspot-connect-routes/utils.test.ts +16 -46
  185. package/src/server/hono/hubspot-connect-routes/utils.ts +6 -6
  186. package/src/server/hono/types.ts +0 -5
  187. package/src/server/lovable/create-app-function-start.ts +4 -4
  188. package/src/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.ts +12 -12
  189. package/src/server/sanitize-request.ts +1 -12
  190. package/src/server/types.ts +0 -36
  191. package/src/server/utils/env-utils.ts +1 -1
  192. package/src/server/utils/hubspot-dpop-auth-headers.test.ts +43 -0
  193. package/src/server/utils/hubspot-dpop-auth-headers.ts +48 -0
  194. package/src/shared/constants.ts +1 -1
  195. package/dist/browser/HubSpotAppConnect-COQgPrFn.js.map +0 -1
  196. package/dist/server/proxy.js +0 -68
  197. package/dist/server/proxy.js.map +0 -1
  198. package/src/server/proxy.test.ts +0 -80
  199. package/src/server/proxy.ts +0 -119
@@ -1,6 +1,7 @@
1
1
  import { Hono } from 'hono';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
+ import { OAUTH_CALLBACK_PATH } from '../../../shared/constants.ts';
4
5
  import { createTestAppKeys } from '../../utils/test-fixtures.ts';
5
6
  import {
6
7
  handleCimdAppJwks,
@@ -13,6 +14,7 @@ import type {
13
14
  import type { HubSpotConnectOAuthRouteOptions } from './types.ts';
14
15
 
15
16
  const BASE_PATH = '/functions/v1/hubspot-connect';
17
+ const APP_ORIGIN = 'https://app.example.com';
16
18
 
17
19
  function buildOptions(
18
20
  hubspotConnectEnv: HubSpotConnectOAuthRouteOptions['hubspotConnectEnv'],
@@ -57,10 +59,11 @@ describe('handleCimdClientJson', () => {
57
59
  app.get(`${BASE_PATH}/client.json`, (c) =>
58
60
  handleCimdClientJson(c, buildOptions(clientSecretEnv))
59
61
  );
60
- const res = await app.request(
61
- `http://app.example.test${BASE_PATH}/client.json`,
62
- { method: 'GET' }
62
+ const clientJsonUrl = new URL(
63
+ `http://app.example.test${BASE_PATH}/client.json`
63
64
  );
65
+ clientJsonUrl.searchParams.set('app_origin', APP_ORIGIN);
66
+ const res = await app.request(clientJsonUrl.toString(), { method: 'GET' });
64
67
 
65
68
  expect(res.status).toBe(200);
66
69
  expect(res.headers.get('Content-Type')).toContain('application/json');
@@ -71,9 +74,7 @@ describe('handleCimdClientJson', () => {
71
74
  scope: { required: string[]; optional?: string[] };
72
75
  };
73
76
 
74
- expect(body.redirect_uri).toBe(
75
- `http://app.example.test${BASE_PATH}/auth/callback`
76
- );
77
+ expect(body.redirect_uri).toBe(`${APP_ORIGIN}${OAUTH_CALLBACK_PATH}`);
77
78
  expect(body.jwks_uri).toBe(`http://app.example.test${BASE_PATH}/jwks.json`);
78
79
  expect(body.scope.required).toContain('crm.objects.contacts.read');
79
80
  expect(body.scope.optional).toContain('crm.objects.deals.read');
@@ -22,13 +22,17 @@ export async function handleCimdClientJson(
22
22
  const xForwardedProto = c.req.header('x-forwarded-proto') ?? undefined;
23
23
  const xForwardedHost = c.req.header('x-forwarded-host') ?? undefined;
24
24
  const requestHostHeader = c.req.header('host') ?? undefined;
25
-
25
+ const appOrigin = c.req.query('app_origin');
26
+ if (!appOrigin) {
27
+ return c.text('Missing app origin', 400);
28
+ }
26
29
  const forwarded: BuildOAuthRedirectUriFromRequestOptions = {
27
30
  requestUrl: c.req.url,
28
31
  basePath,
29
32
  xForwardedProto,
30
33
  xForwardedHost,
31
34
  requestHostHeader,
35
+ appOrigin,
32
36
  };
33
37
 
34
38
  const body: HubSpotConnectCimdClientDocument = {
@@ -14,6 +14,7 @@ import {
14
14
  handleCimdClientJson,
15
15
  } from './cimd-public-routes.ts';
16
16
  import type { HubSpotConnectRoutesEnv } from './load-hubspot-connect-routes-env.ts';
17
+ import type { HubSpotConnectOAuthRouteOptions } from './types.ts';
17
18
 
18
19
  /**
19
20
  * Options accepted by {@link registerHubSpotConnectRoutes}.
@@ -73,7 +74,7 @@ export function registerHubSpotConnectRoutes(
73
74
  assertHubSpotConnectCimdClientMetadata(cimdClientMetadata);
74
75
 
75
76
  const refreshCookiePath = `${basePath}/auth`;
76
- const oauthRouteOptions = {
77
+ const oauthRouteOptions: HubSpotConnectOAuthRouteOptions = {
77
78
  appKeys,
78
79
  refreshCookiePath,
79
80
  logger,
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
+ import { OAUTH_CALLBACK_PATH } from '../../../shared/constants.ts';
3
4
  import {
4
5
  buildCimdClientIdUrlFromRequest,
5
6
  buildHubSpotAppJwksUrlFromRequest,
@@ -7,6 +8,9 @@ import {
7
8
  getRequestHostForHubspotConnect,
8
9
  } from './utils.ts';
9
10
 
11
+ const APP_ORIGIN = 'https://third-party-app.test:5173';
12
+ const APP_ORIGIN_QUERY = `app_origin=${encodeURIComponent(APP_ORIGIN)}`;
13
+
10
14
  describe('buildCimdClientIdUrlFromRequest', () => {
11
15
  it('builds client id URL under base path', () => {
12
16
  expect(
@@ -14,9 +18,10 @@ describe('buildCimdClientIdUrlFromRequest', () => {
14
18
  requestUrl:
15
19
  'https://third-party-app.test:5173/functions/v1/hubspot-connect/auth/init-session',
16
20
  basePath: '/functions/v1/hubspot-connect',
21
+ appOrigin: APP_ORIGIN,
17
22
  })
18
23
  ).toBe(
19
- 'https://third-party-app.test:5173/functions/v1/hubspot-connect/client.json'
24
+ `https://third-party-app.test:5173/functions/v1/hubspot-connect/client.json?${APP_ORIGIN_QUERY}`
20
25
  );
21
26
  });
22
27
 
@@ -28,9 +33,10 @@ describe('buildCimdClientIdUrlFromRequest', () => {
28
33
  basePath: '/functions/v1/hubspot-connect',
29
34
  xForwardedProto: 'https',
30
35
  xForwardedHost: 'third-party-app.test:5173',
36
+ appOrigin: APP_ORIGIN,
31
37
  })
32
38
  ).toBe(
33
- 'https://third-party-app.test:5173/functions/v1/hubspot-connect/client.json'
39
+ `https://third-party-app.test:5173/functions/v1/hubspot-connect/client.json?${APP_ORIGIN_QUERY}`
34
40
  );
35
41
  });
36
42
  });
@@ -42,6 +48,7 @@ describe('buildHubSpotAppJwksUrlFromRequest', () => {
42
48
  requestUrl:
43
49
  'https://third-party-app.test:5173/functions/v1/hubspot-connect/auth/init-session',
44
50
  basePath: '/functions/v1/hubspot-connect',
51
+ appOrigin: APP_ORIGIN,
45
52
  })
46
53
  ).toBe(
47
54
  'https://third-party-app.test:5173/functions/v1/hubspot-connect/jwks.json'
@@ -50,64 +57,27 @@ describe('buildHubSpotAppJwksUrlFromRequest', () => {
50
57
  });
51
58
 
52
59
  describe('buildOAuthRedirectUriFromRequest', () => {
53
- it('builds callback URL from origin and base path', () => {
60
+ it('builds callback URL from app origin and frontend callback path', () => {
54
61
  expect(
55
62
  buildOAuthRedirectUriFromRequest({
56
63
  requestUrl:
57
64
  'https://third-party-app.test:5173/functions/v1/hubspot-connect/auth/init-session',
58
65
  basePath: '/functions/v1/hubspot-connect',
66
+ appOrigin: APP_ORIGIN,
59
67
  })
60
- ).toBe(
61
- 'https://third-party-app.test:5173/functions/v1/hubspot-connect/auth/callback'
62
- );
68
+ ).toBe(`${APP_ORIGIN}${OAUTH_CALLBACK_PATH}`);
63
69
  });
64
70
 
65
- it('strips a trailing slash from basePath', () => {
71
+ it('ignores basePath and forwarded headers for redirect URI', () => {
66
72
  expect(
67
73
  buildOAuthRedirectUriFromRequest({
68
74
  requestUrl: 'https://example.com/x',
69
75
  basePath: '/functions/v1/hubspot-connect/',
70
- })
71
- ).toBe('https://example.com/functions/v1/hubspot-connect/auth/callback');
72
- });
73
-
74
- it('uses X-Forwarded-Proto and X-Forwarded-Host when present', () => {
75
- expect(
76
- buildOAuthRedirectUriFromRequest({
77
- requestUrl:
78
- 'http://127.0.0.1:5175/functions/v1/hubspot-connect/auth/init-session',
79
- basePath: '/functions/v1/hubspot-connect',
80
- xForwardedProto: 'https',
81
- xForwardedHost: 'third-party-app.test:5173',
82
- })
83
- ).toBe(
84
- 'https://third-party-app.test:5173/functions/v1/hubspot-connect/auth/callback'
85
- );
86
- });
87
-
88
- it('uses X-Forwarded-Proto with Host header when X-Forwarded-Host is absent', () => {
89
- expect(
90
- buildOAuthRedirectUriFromRequest({
91
- requestUrl:
92
- 'http://127.0.0.1:5175/functions/v1/hubspot-connect/auth/init-session',
93
- basePath: '/functions/v1/hubspot-connect',
94
- xForwardedProto: 'https',
95
- requestHostHeader: 'third-party-app.test:5173',
96
- })
97
- ).toBe(
98
- 'https://third-party-app.test:5173/functions/v1/hubspot-connect/auth/callback'
99
- );
100
- });
101
-
102
- it('uses X-Forwarded-Proto with request URL host when forwarded host and Host are absent', () => {
103
- expect(
104
- buildOAuthRedirectUriFromRequest({
105
- requestUrl:
106
- 'http://127.0.0.1:5175/functions/v1/hubspot-connect/auth/init-session',
107
- basePath: '/functions/v1/hubspot-connect',
108
76
  xForwardedProto: 'https',
77
+ xForwardedHost: 'other.example.com',
78
+ appOrigin: APP_ORIGIN,
109
79
  })
110
- ).toBe('https://127.0.0.1:5175/functions/v1/hubspot-connect/auth/callback');
80
+ ).toBe(`${APP_ORIGIN}${OAUTH_CALLBACK_PATH}`);
111
81
  });
112
82
  });
113
83
 
@@ -1,4 +1,4 @@
1
- import { HUBSPOT_FRONTEND_CALLBACK_PATH } from '../../../shared/constants.ts';
1
+ import { OAUTH_CALLBACK_PATH } from '../../../shared/constants.ts';
2
2
  import { serializeCookie } from '../utils/cookie-utils.ts';
3
3
 
4
4
  export function clearTempCookie(name: string): string {
@@ -63,7 +63,7 @@ export function parseAppOriginHeader(
63
63
  * the OAuth token endpoint's `redirect_uri` check).
64
64
  */
65
65
  export function buildFrontendOAuthRedirectUri(appOrigin: string): string {
66
- return `${appOrigin}${HUBSPOT_FRONTEND_CALLBACK_PATH}`;
66
+ return `${appOrigin}${OAUTH_CALLBACK_PATH}`;
67
67
  }
68
68
 
69
69
  export function isSafeReturnPath(rawPath: string): boolean {
@@ -126,6 +126,7 @@ export interface BuildOAuthRedirectUriFromRequestOptions {
126
126
  xForwardedHost?: string | undefined;
127
127
  /** `Host` when `X-Forwarded-Host` is absent but `X-Forwarded-Proto` is set. */
128
128
  requestHostHeader?: string | undefined;
129
+ appOrigin: string;
129
130
  }
130
131
 
131
132
  function normalizeHubSpotConnectBasePath(basePath: string): string {
@@ -162,9 +163,8 @@ export function buildHubSpotConnectRequestOrigin(
162
163
  export function buildOAuthRedirectUriFromRequest(
163
164
  options: BuildOAuthRedirectUriFromRequestOptions
164
165
  ): string {
165
- const trimmed = normalizeHubSpotConnectBasePath(options.basePath);
166
- const origin = buildHubSpotConnectRequestOrigin(options);
167
- return `${origin}${trimmed}/auth/callback`;
166
+ const { appOrigin } = options;
167
+ return `${appOrigin}${OAUTH_CALLBACK_PATH}`;
168
168
  }
169
169
 
170
170
  /**
@@ -175,7 +175,7 @@ export function buildCimdClientIdUrlFromRequest(
175
175
  ): string {
176
176
  const trimmed = normalizeHubSpotConnectBasePath(options.basePath);
177
177
  const origin = buildHubSpotConnectRequestOrigin(options);
178
- return `${origin}${trimmed}/client.json`;
178
+ return `${origin}${trimmed}/client.json?app_origin=${encodeURIComponent(options.appOrigin)}`;
179
179
  }
180
180
 
181
181
  /**
@@ -1,11 +1,6 @@
1
1
  import type { HubSpotClient } from '../api-client-core/types.ts';
2
- import type { HubSpotProxy } from '../types.ts';
3
2
 
4
3
  export interface AppConnectRequestContext {
5
- /**
6
- * HubSpot API proxy.
7
- */
8
- proxy: HubSpotProxy;
9
4
  /**
10
5
  * HubSpot API client.
11
6
  */
@@ -27,7 +27,7 @@ export type AppFunctionStart = (context: SecureStartContext) => Promise<void>;
27
27
  /**
28
28
  * Builds a Deno-style `start({ appKeys })` entry point that boots a
29
29
  * Hono app under `basePath`, wires the SDK's per-request HubSpot
30
- * proxy via `createAppConnectRequestHandler`, and serves it with
30
+ * client via `createAppConnectRequestHandler`, and serves it with
31
31
  * `Deno.serve` on `PORT`.
32
32
  */
33
33
  export function createAppFunctionStart(
@@ -35,8 +35,8 @@ export function createAppFunctionStart(
35
35
  ): AppFunctionStart {
36
36
  const { basePath, registerRoutes, logger } = options;
37
37
 
38
- return ({ appKeys }) => {
39
- Deno.serve(
38
+ return async ({ appKeys }) => {
39
+ const server = Deno.serve(
40
40
  serveOptions,
41
41
  createAppConnectRequestHandler({
42
42
  appKeys,
@@ -47,6 +47,6 @@ export function createAppFunctionStart(
47
47
  })
48
48
  );
49
49
 
50
- return Promise.resolve();
50
+ await server.finished;
51
51
  };
52
52
  }
@@ -10,29 +10,29 @@ import {
10
10
  const PORT = Deno.env.get('PORT');
11
11
  const port = typeof PORT === 'string' ? parseInt(PORT!, 10) : undefined;
12
12
 
13
- const HUBSPOT_CONNECT_BASE_PATH = '/hubspot-connect';
13
+ const PUBLIC_HUBSPOT_CONNECT_BASE_PATH = '/functions/v1/hubspot-connect';
14
14
 
15
15
  export interface RunHubSpotConnectLovableServerOptions extends SecureStartContext {
16
16
  cimdClientMetadata: HubSpotConnectCimdClientMetadata;
17
17
  }
18
18
 
19
- export function runHubSpotConnectLovableServer(
19
+ export async function runHubSpotConnectLovableServer(
20
20
  options: RunHubSpotConnectLovableServerOptions
21
21
  ): Promise<void> {
22
22
  const { appKeys, cimdClientMetadata } = options;
23
23
  const hubspotConnectEnv = loadHubSpotConnectRoutesEnv();
24
24
 
25
- const app = new Hono().basePath(HUBSPOT_CONNECT_BASE_PATH);
25
+ const app = new Hono().basePath('/hubspot-connect');
26
26
 
27
- const serveHandler = (request: Request): Response | Promise<Response> => {
28
- registerHubSpotConnectRoutes({
29
- app,
30
- appKeys,
31
- basePath: HUBSPOT_CONNECT_BASE_PATH,
32
- hubspotConnectEnv,
33
- cimdClientMetadata,
34
- });
27
+ registerHubSpotConnectRoutes({
28
+ app,
29
+ appKeys,
30
+ basePath: PUBLIC_HUBSPOT_CONNECT_BASE_PATH,
31
+ hubspotConnectEnv,
32
+ cimdClientMetadata,
33
+ });
35
34
 
35
+ const serveHandler = (request: Request): Response | Promise<Response> => {
36
36
  return app.fetch(request);
37
37
  };
38
38
 
@@ -41,5 +41,5 @@ export function runHubSpotConnectLovableServer(
41
41
  ? Deno.serve({ port }, serveHandler)
42
42
  : Deno.serve(serveHandler);
43
43
 
44
- return server.finished;
44
+ await server.finished;
45
45
  }
@@ -13,13 +13,8 @@ function serializeCookies(cookies: Map<string, string>): string {
13
13
  * Mutates `headers` in place: parses the `Cookie` header, drops every
14
14
  * protected cookie (see {@link isProtectedCookieName}), and rewrites
15
15
  * the header. Deletes the header entirely when nothing survives.
16
- *
17
- * Used by the SDK's auth middleware after it has read the access
18
- * token / session ID, so user route handlers never see — and
19
- * therefore cannot leak — those cookies, while CORS / auth
20
- * middleware that ran first still got the raw values.
21
16
  */
22
- export function stripProtectedCookies(headers: Headers): void {
17
+ function stripProtectedCookies(headers: Headers): void {
23
18
  const cookies = parseCookies(headers.get('Cookie'));
24
19
  const surviving = new Map<string, string>();
25
20
  for (const [name, value] of Object.entries(cookies)) {
@@ -39,12 +34,6 @@ export function stripProtectedCookies(headers: Headers): void {
39
34
  * Returns a clone of `original` whose `Cookie` header has every
40
35
  * protected cookie removed (see {@link isProtectedCookieName}). When
41
36
  * no other cookies remain, the header is dropped entirely.
42
- *
43
- * Standalone helper retained for callers that want a new Request
44
- * (e.g. tests). Inside the SDK's per-request handler, the auth
45
- * middleware uses {@link stripProtectedCookies} directly on
46
- * `c.req.raw.headers` so the auth check itself can still read the
47
- * protected cookies before they are stripped.
48
37
  */
49
38
  export function sanitizeRequest(original: Request): Request {
50
39
  const headers = new Headers(original.headers);
@@ -10,42 +10,6 @@ export interface AppKeys {
10
10
  appPublicKeyJwk: JsonWebKey;
11
11
  }
12
12
 
13
- /**
14
- * Request shape accepted by `HubSpotProxy.fetch`. Only the `path`
15
- * is required; everything else mirrors the equivalent fetch fields.
16
- */
17
- export interface HubSpotProxyRequest {
18
- /** Path component of the upstream URL, including leading slash. */
19
- path: string;
20
- /** HTTP method. Defaults to `GET`. */
21
- method?: string;
22
- /**
23
- * Extra request headers. The proxy adds `Authorization` itself
24
- * (`DPoP` access token plus `DPoP` proof when DPoP is enabled and
25
- * `appKeys` is non-null; otherwise `Bearer` only).
26
- */
27
- headers?: Record<string, string>;
28
- /** Optional request body. Pass `null`/`undefined` for empty bodies. */
29
- body?: string | null | undefined;
30
- }
31
-
32
- /**
33
- * Authenticated proxy returned by `createHubSpotProxy`. Use it
34
- * inside Hono handlers (via `c.env.hubSpotProxy`) to call
35
- * HubSpot's API on behalf of the browser session that issued the
36
- * incoming request.
37
- */
38
- export interface HubSpotProxy {
39
- /**
40
- * `true` when the session cookies present on the inbound request
41
- * yielded a usable access token. When `false`, every `fetch()` call
42
- * returns a 401 without contacting the upstream.
43
- */
44
- authenticated: boolean;
45
- /** Performs an authenticated upstream request. */
46
- fetch: (request: HubSpotProxyRequest) => Promise<Response>;
47
- }
48
-
49
13
  /**
50
14
  * RFC 7517 JWK Set. Returned by HubSpot's `/oauth/v1/jwks` endpoint
51
15
  * and used to verify access tokens on the resource server.
@@ -60,7 +60,7 @@ export function requireEnv(key: string): string {
60
60
  }
61
61
 
62
62
  /**
63
- * HubSpot API origin used by {@link createHubSpotProxy}. Defaults to
63
+ * HubSpot API origin used by the HubSpot API client transport. Defaults to
64
64
  * `https://api.hubapi.com` when `HUBSPOT_API_ORIGIN` is unset.
65
65
  */
66
66
  export function getHubSpotApiOrigin(): string {
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { sha256base64url } from './crypto-utils.ts';
4
+ import { verifyDpopProof } from './dpop-utils.ts';
5
+ import { buildHubSpotDpopAuthHeaders } from './hubspot-dpop-auth-headers.ts';
6
+ import { createTestAppKeys } from './test-fixtures.ts';
7
+
8
+ describe('buildHubSpotDpopAuthHeaders', () => {
9
+ it('returns DPoP authorization headers with a signed proof', async () => {
10
+ const appKeys = await createTestAppKeys();
11
+ const headers = await buildHubSpotDpopAuthHeaders({
12
+ accessToken: 'tok',
13
+ sessionId: 'sid',
14
+ appKeys,
15
+ method: 'GET',
16
+ targetUrl: 'https://api.example.test/x',
17
+ });
18
+
19
+ expect(headers.Authorization).toBe('DPoP tok');
20
+ expect(headers.DPoP).toBeDefined();
21
+ });
22
+
23
+ it('strips query string parameters from the htu claim', async () => {
24
+ const appKeys = await createTestAppKeys();
25
+ const headers = await buildHubSpotDpopAuthHeaders({
26
+ accessToken: 'tok',
27
+ sessionId: 'sid',
28
+ appKeys,
29
+ method: 'GET',
30
+ targetUrl: 'https://api.example.test/x?limit=10&properties=email',
31
+ });
32
+
33
+ await expect(
34
+ verifyDpopProof({
35
+ proof: headers.DPoP!,
36
+ htm: 'GET',
37
+ htu: 'https://api.example.test/x',
38
+ ath: await sha256base64url('tok'),
39
+ sid: await sha256base64url('sid'),
40
+ })
41
+ ).resolves.toBeDefined();
42
+ });
43
+ });
@@ -0,0 +1,48 @@
1
+ import { type AppKeys } from '../types.ts';
2
+ import { sha256base64url } from './crypto-utils.ts';
3
+ import { signDpopProof } from './dpop-utils.ts';
4
+
5
+ export interface BuildHubSpotDpopAuthHeadersOptions {
6
+ accessToken: string;
7
+ sessionId: string;
8
+ appKeys: AppKeys;
9
+ method: string;
10
+ targetUrl: string;
11
+ }
12
+
13
+ function getDpopHtuFromTargetUrl(targetUrl: string): string {
14
+ const url = new URL(targetUrl);
15
+ url.search = '';
16
+ url.hash = '';
17
+ return url.toString();
18
+ }
19
+
20
+ /**
21
+ * Builds `Authorization` and `DPoP` headers for an authenticated
22
+ * HubSpot API request when DPoP is enabled.
23
+ */
24
+ export async function buildHubSpotDpopAuthHeaders(
25
+ options: BuildHubSpotDpopAuthHeadersOptions
26
+ ): Promise<Record<string, string>> {
27
+ const { accessToken, sessionId, appKeys, method, targetUrl } = options;
28
+ const htu = getDpopHtuFromTargetUrl(targetUrl);
29
+
30
+ const ath = await sha256base64url(accessToken);
31
+ const sid = await sha256base64url(sessionId);
32
+ const dpopProof = await signDpopProof({
33
+ appKeys,
34
+ claims: {
35
+ htm: method,
36
+ htu,
37
+ jti: crypto.randomUUID(),
38
+ iat: Math.floor(Date.now() / 1000),
39
+ ath,
40
+ sid,
41
+ },
42
+ });
43
+
44
+ return {
45
+ Authorization: `DPoP ${accessToken}`,
46
+ DPoP: dpopProof,
47
+ };
48
+ }
@@ -26,7 +26,7 @@ export const EXPIRES_AT_URL_PARAM = '__hs_expires_at';
26
26
  * register `${app_origin}${HUBSPOT_FRONTEND_CALLBACK_PATH}` as a
27
27
  * redirect URI in its HubSpot app settings.
28
28
  */
29
- export const HUBSPOT_FRONTEND_CALLBACK_PATH = '/__hubspot_oauth_callback';
29
+ export const OAUTH_CALLBACK_PATH = '/__hubspot_oauth_callback';
30
30
 
31
31
  /**
32
32
  * Query parameter on the `auth/complete` POST request carrying the
@@ -1 +0,0 @@
1
- {"version":3,"file":"HubSpotAppConnect-COQgPrFn.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 console.log('status', status);\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":"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"}