@vercel/connect 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,12 @@ export interface ConnectAuthorizationOptions {
7
7
  expiresInMs?: number;
8
8
  /** OAuth prompt value to pass to the connector's authorization server. */
9
9
  prompt?: string;
10
+ /**
11
+ * Region to send the request to, e.g. `sfo1`. Defaults to the
12
+ * `VERCEL_REGION` environment variable; override it to target a
13
+ * different region.
14
+ */
15
+ region?: string;
10
16
  }
11
17
  export interface ConnectAuthorizationResponse {
12
18
  request: string;
@@ -1,5 +1,7 @@
1
1
  import { getVercelOidcToken } from '@vercel/oidc';
2
2
  import { isDetachedInteractiveAuth, validateCallbackUrl, validateWebhookUrl, } from './internal/url-validation.js';
3
+ import { withDefaultScopes } from './internal/default-scopes.js';
4
+ import { resolveBaseUrl } from './internal/base-url.js';
3
5
  export async function startAuthorization(connector, params, options) {
4
6
  if (!connector) {
5
7
  throw new Error('connector is required');
@@ -12,13 +14,14 @@ export async function startAuthorization(connector, params, options) {
12
14
  validateWebhookUrl(options.webhook);
13
15
  }
14
16
  const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
15
- const endpoint = `https://api.vercel.com/v1/connect/authorize/${encodeURIComponent(connector)}`;
17
+ const baseUrl = resolveBaseUrl(options);
18
+ const endpoint = `${baseUrl}/v1/connect/authorize/${encodeURIComponent(connector)}`;
16
19
  const deviceCode = options?.deviceCode ?? (detachedInteractiveAuth ? true : undefined);
17
20
  const returnUrl = !detachedInteractiveAuth && options?.callbackUrl !== undefined
18
21
  ? { returnUrl: options.callbackUrl }
19
22
  : {};
20
23
  const body = {
21
- ...params,
24
+ ...withDefaultScopes(params),
22
25
  ...returnUrl,
23
26
  ...(options?.webhook !== undefined && { webhook: options.webhook }),
24
27
  ...(deviceCode !== undefined && {
package/dist/connector.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { getVercelOidcToken } from '@vercel/oidc';
2
2
  import { createConnectErrorFromResponse, } from './token.js';
3
+ import { resolveBaseUrl } from './internal/base-url.js';
3
4
  /**
4
5
  * Fetch a connector's metadata from Vercel Connect.
5
6
  *
@@ -11,7 +12,8 @@ import { createConnectErrorFromResponse, } from './token.js';
11
12
  */
12
13
  export async function getConnectorMetadata(connector, options) {
13
14
  const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
14
- const endpoint = `https://api.vercel.com/v1/connect/connectors/${encodeURIComponent(connector)}`;
15
+ const baseUrl = resolveBaseUrl(options);
16
+ const endpoint = `${baseUrl}/v1/connect/connectors/${encodeURIComponent(connector)}`;
15
17
  const response = await fetch(endpoint, {
16
18
  method: 'GET',
17
19
  headers: {
@@ -1,6 +1,7 @@
1
1
  import { getVercelOidcToken } from '@vercel/oidc';
2
2
  import { ConnectError, createConnectErrorFromResponse } from '../token.js';
3
- const MANAGED_OAUTH_CONNECTOR_ENDPOINT = 'https://api.vercel.com/v1/connect/connectors/managed/oauth';
3
+ import { resolveBaseUrl } from '../internal/base-url.js';
4
+ const MANAGED_OAUTH_CONNECTOR_PATH = '/v1/connect/connectors/managed/oauth';
4
5
  const RESERVED_UID_PATTERN = /^(vc\/|[^/]*\.vercel\.com\/)/;
5
6
  const ALLOWED_RESERVED_UID_PREFIXES = ['mcp.vercel.com/'];
6
7
  const RESERVED_ID_PREFIXES = ['scl_', 'sca_', 'store_', 'ir_'];
@@ -12,9 +13,11 @@ export async function provisionEveOAuthConnector({ connector, connection, connec
12
13
  return;
13
14
  }
14
15
  const vercelToken = connectOptions?.vercelToken ?? (await getVercelOidcToken());
16
+ const endpoint = `${resolveBaseUrl(connectOptions)}${MANAGED_OAUTH_CONNECTOR_PATH}`;
15
17
  const cacheKey = JSON.stringify({
16
18
  connector,
17
19
  serverUrl,
20
+ endpoint,
18
21
  token: await tokenCacheKeyPart(vercelToken),
19
22
  });
20
23
  let promise = provisionCache.get(cacheKey);
@@ -23,6 +26,7 @@ export async function provisionEveOAuthConnector({ connector, connection, connec
23
26
  connector,
24
27
  serverUrl,
25
28
  vercelToken,
29
+ endpoint,
26
30
  }).catch(error => {
27
31
  if (isNonOAuthConnectorConflict(error)) {
28
32
  return;
@@ -80,8 +84,8 @@ function isNonOAuthConnectorConflict(error) {
80
84
  error.status === 409 &&
81
85
  /not an OAuth connector/i.test(error.message));
82
86
  }
83
- async function provisionManagedOAuthConnector({ connector, serverUrl, vercelToken, }) {
84
- const response = await fetch(MANAGED_OAUTH_CONNECTOR_ENDPOINT, {
87
+ async function provisionManagedOAuthConnector({ connector, serverUrl, vercelToken, endpoint, }) {
88
+ const response = await fetch(endpoint, {
85
89
  method: 'POST',
86
90
  headers: {
87
91
  Accept: 'application/json',
@@ -7,6 +7,12 @@ export interface ConnectInstallationOptions {
7
7
  tenantId?: string;
8
8
  deviceCode?: boolean;
9
9
  expiresInMs?: number;
10
+ /**
11
+ * Region to send the request to, e.g. `sfo1`. Defaults to the
12
+ * `VERCEL_REGION` environment variable; override it to target a
13
+ * different region.
14
+ */
15
+ region?: string;
10
16
  }
11
17
  export interface ConnectInstallationResponse {
12
18
  request: string;
@@ -1,6 +1,7 @@
1
1
  import { getVercelOidcToken } from '@vercel/oidc';
2
2
  import { isDetachedInteractiveAuth, validateCallbackUrl, validateWebhookUrl, } from './internal/url-validation.js';
3
3
  import { createConnectErrorFromResponse } from './token.js';
4
+ import { resolveBaseUrl } from './internal/base-url.js';
4
5
  /**
5
6
  * Create an operator installation request for an app-scoped connector.
6
7
  *
@@ -19,7 +20,8 @@ export async function experimental_startInstallation(connector, params = {}, opt
19
20
  validateWebhookUrl(options.webhook);
20
21
  }
21
22
  const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
22
- const endpoint = `https://api.vercel.com/v1/connect/install/${encodeURIComponent(connector)}`;
23
+ const baseUrl = resolveBaseUrl(options);
24
+ const endpoint = `${baseUrl}/v1/connect/install/${encodeURIComponent(connector)}`;
23
25
  const deviceCode = options?.deviceCode ?? (detachedInteractiveAuth ? true : undefined);
24
26
  const returnUrl = !detachedInteractiveAuth && options?.returnUrl !== undefined
25
27
  ? { returnUrl: options.returnUrl }
@@ -0,0 +1,10 @@
1
+ /** Base URL for the Vercel API when no region is available. */
2
+ export declare const API_BASE_URL = "https://api.vercel.com";
3
+ /**
4
+ * Resolves the Vercel API base URL for a region. The region comes from the
5
+ * `region` argument, then the `VERCEL_REGION` environment variable; without
6
+ * either, requests go to the global {@link API_BASE_URL}.
7
+ */
8
+ export declare function resolveBaseUrl({ region }?: {
9
+ region?: string;
10
+ }): string;
@@ -0,0 +1,12 @@
1
+ import { readNonEmptyEnv } from './env.js';
2
+ /** Base URL for the Vercel API when no region is available. */
3
+ export const API_BASE_URL = 'https://api.vercel.com';
4
+ /**
5
+ * Resolves the Vercel API base URL for a region. The region comes from the
6
+ * `region` argument, then the `VERCEL_REGION` environment variable; without
7
+ * either, requests go to the global {@link API_BASE_URL}.
8
+ */
9
+ export function resolveBaseUrl({ region } = {}) {
10
+ const resolved = region ?? readNonEmptyEnv('VERCEL_REGION');
11
+ return resolved ? `https://api-${resolved}.vercel.com` : API_BASE_URL;
12
+ }
@@ -0,0 +1,2 @@
1
+ import type { ConnectTokenParams } from '../token.js';
2
+ export declare function withDefaultScopes(params: ConnectTokenParams): ConnectTokenParams;
@@ -0,0 +1,3 @@
1
+ export function withDefaultScopes(params) {
2
+ return params.scopes === undefined ? { ...params, scopes: ['*'] } : params;
3
+ }
package/dist/token.d.ts CHANGED
@@ -27,8 +27,8 @@ export interface ConnectTokenParams {
27
27
  installationId?: string;
28
28
  audience?: string[];
29
29
  /**
30
- * Access scopes to request. Use `['*']` to request the default scopes for
31
- * the specified subject type.
30
+ * Access scopes to request. Defaults to `['*']`, which requests the default
31
+ * scopes for the specified subject type.
32
32
  */
33
33
  scopes?: string[];
34
34
  resources?: string[];
@@ -103,6 +103,12 @@ export interface ConnectOptions {
103
103
  * a stale bearer.
104
104
  */
105
105
  forceRefresh?: boolean;
106
+ /**
107
+ * Region to send the request to, e.g. `sfo1`. Defaults to the
108
+ * `VERCEL_REGION` environment variable; override it to target a
109
+ * different region.
110
+ */
111
+ region?: string;
106
112
  }
107
113
  export declare function getToken(connector: string, params: ConnectTokenParams, options?: ConnectOptions): Promise<string>;
108
114
  export declare function getTokenResponse(connector: string, params: ConnectTokenParams, options?: ConnectOptions): Promise<ConnectTokenResponse>;
package/dist/token.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { getVercelOidcToken } from '@vercel/oidc';
2
+ import { resolveBaseUrl } from './internal/base-url.js';
3
+ import { withDefaultScopes } from './internal/default-scopes.js';
2
4
  export class ConnectError extends Error {
3
5
  code;
4
6
  status;
@@ -36,8 +38,9 @@ export async function getToken(connector, params, options) {
36
38
  return token;
37
39
  }
38
40
  export async function getTokenResponse(connector, params, options) {
39
- const bufferMs = params.validityBufferMs ?? DEFAULT_VALIDITY_BUFFER_MS;
40
- const cacheKey = tokenCacheKey(connector, params);
41
+ const requestParams = withDefaultScopes(params);
42
+ const bufferMs = requestParams.validityBufferMs ?? DEFAULT_VALIDITY_BUFFER_MS;
43
+ const cacheKey = tokenCacheKey(connector, requestParams);
41
44
  if (options?.forceRefresh) {
42
45
  cache.delete(cacheKey);
43
46
  }
@@ -53,7 +56,8 @@ export async function getTokenResponse(connector, params, options) {
53
56
  }
54
57
  }
55
58
  const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
56
- const endpoint = `https://api.vercel.com/v1/connect/token/${encodeURIComponent(connector)}`;
59
+ const baseUrl = resolveBaseUrl(options);
60
+ const endpoint = `${baseUrl}/v1/connect/token/${encodeURIComponent(connector)}`;
57
61
  const response = await fetch(endpoint, {
58
62
  method: 'POST',
59
63
  headers: {
@@ -61,7 +65,7 @@ export async function getTokenResponse(connector, params, options) {
61
65
  'Content-Type': 'application/json',
62
66
  Authorization: `Bearer ${vercelToken}`,
63
67
  },
64
- body: JSON.stringify(params),
68
+ body: JSON.stringify(requestParams),
65
69
  });
66
70
  if (!response.ok) {
67
71
  throw await createConnectErrorFromResponse(response, 'Failed to get token');
@@ -75,7 +79,8 @@ export async function getTokenResponse(connector, params, options) {
75
79
  }
76
80
  export async function revokeToken(connector, params, options) {
77
81
  const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
78
- const endpoint = `https://api.vercel.com/v1/connect/connectors/${encodeURIComponent(connector)}/tokens`;
82
+ const baseUrl = resolveBaseUrl(options);
83
+ const endpoint = `${baseUrl}/v1/connect/connectors/${encodeURIComponent(connector)}/tokens`;
79
84
  const response = await fetch(endpoint, {
80
85
  method: 'DELETE',
81
86
  headers: {
@@ -108,7 +113,7 @@ export async function revokeToken(connector, params, options) {
108
113
  * call. No-op when no matching entry exists.
109
114
  */
110
115
  export function deleteTokenCacheEntry(connector, params) {
111
- cache.delete(tokenCacheKey(connector, params));
116
+ cache.delete(tokenCacheKey(connector, withDefaultScopes(params)));
112
117
  }
113
118
  const DEFAULT_VALIDITY_BUFFER_MS = 30_000;
114
119
  const MAX_CACHE_SIZE = 100;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/connect",
3
- "version": "0.9.0",
3
+ "version": "1.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "repository": {