@vercel/connect 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,6 +30,22 @@ const token = await getToken(process.env.CONNECTOR_LINEAR!, {
30
30
  });
31
31
  ```
32
32
 
33
+ To create an operator installation request for an app-scoped connector, use the
34
+ experimental install helper:
35
+
36
+ This feature is gated while experimental. Contact Vercel to enable access
37
+ before using it.
38
+
39
+ ```ts
40
+ import { experimental_startInstallation } from '@vercel/connect';
41
+
42
+ const { url } = await experimental_startInstallation(
43
+ process.env.CONNECTOR_SLACK!,
44
+ {},
45
+ { returnUrl: 'https://example.com/settings/integrations' }
46
+ );
47
+ ```
48
+
33
49
  ### Chat SDK
34
50
 
35
51
  Spread the helper into the matching `create*Adapter` factory. Each helper
@@ -123,4 +139,4 @@ import { connect } from '@vercel/connect/authjs';
123
139
  const providers = [connect({ connector: 'linear' })];
124
140
  ```
125
141
 
126
- See the source under `src/` for the full API (additional helpers like `revokeToken`, `getTokenResponse`, `startAuthorization`, typed error classes, and per-adapter options).
142
+ See the source under `src/` for the full API (additional helpers like `revokeToken`, `getTokenResponse`, `startAuthorization`, `experimental_startInstallation`, typed error classes, and per-adapter options).
@@ -49,4 +49,4 @@
49
49
  * ```
50
50
  */
51
51
  export { connectAuthProvider, ConsentRequiredError, type ConnectAuthProviderOptions, type ConsentChallenge, } from '../mcp/connect-auth-provider.js';
52
- export { ConnectError, ConnectorInstallationRequiredError, NoValidTokenError, UserAuthorizationRequiredError, type ConnectErrorOptions, type ConnectTokenParams, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from '../token.js';
52
+ export { ConnectError, ConnectorInstallationRequiredError, NoValidTokenError, UserAuthorizationRequiredError, type ConnectErrorOptions, type ConnectTokenExchangeSubject, type ConnectTokenParams, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from '../token.js';
@@ -1,11 +1,10 @@
1
1
  import { getVercelOidcToken } from '@vercel/oidc';
2
- const DETACHED_INTERACTIVE_AUTH_MODE = 'detached';
3
- const INTERACTIVE_AUTH_MODE_ENV = 'VERCEL_CONNECT_INTERACTIVE_AUTH_MODE';
2
+ import { isDetachedInteractiveAuth, validateCallbackUrl, validateWebhookUrl, } from './internal/url-validation.js';
4
3
  export async function startAuthorization(connector, params, options) {
5
4
  if (!connector) {
6
5
  throw new Error('connector is required');
7
6
  }
8
- const detachedInteractiveAuth = process.env[INTERACTIVE_AUTH_MODE_ENV] === DETACHED_INTERACTIVE_AUTH_MODE;
7
+ const detachedInteractiveAuth = isDetachedInteractiveAuth();
9
8
  if (!detachedInteractiveAuth && options?.callbackUrl !== undefined) {
10
9
  validateCallbackUrl(options.callbackUrl);
11
10
  }
@@ -49,35 +48,3 @@ export async function startAuthorization(connector, params, options) {
49
48
  const data = await response.json();
50
49
  return data;
51
50
  }
52
- function validateCallbackUrl(value) {
53
- let url;
54
- try {
55
- url = new URL(value);
56
- }
57
- catch {
58
- throw new Error(`Invalid callbackUrl: ${value}`);
59
- }
60
- if (url.protocol === 'https:')
61
- return;
62
- if (url.protocol === 'http:' && isLocalHttpCallbackHostname(url.hostname)) {
63
- return;
64
- }
65
- throw new Error(`callbackUrl must be https://, http://localhost, or http://*.localhost, got: ${value}`);
66
- }
67
- function isLocalHttpCallbackHostname(hostname) {
68
- return (hostname === 'localhost' ||
69
- hostname.endsWith('.localhost') ||
70
- hostname === '127.0.0.1');
71
- }
72
- function validateWebhookUrl(value) {
73
- let url;
74
- try {
75
- url = new URL(value);
76
- }
77
- catch {
78
- throw new Error(`Invalid webhook URL: ${value}`);
79
- }
80
- if (url.protocol !== 'https:') {
81
- throw new Error(`webhook must be https://, got: ${value}`);
82
- }
83
- }
@@ -1,4 +1,5 @@
1
1
  import { extractBearerToken, verifyOidc, } from 'eve/channels/auth';
2
+ import { readNonEmptyEnv } from '../internal/env.js';
2
3
  export const CONNECT_OAUTH_ISSUER = 'https://connect.vercel.com';
3
4
  /**
4
5
  * Returns an Eve route auth callback for Vercel Connect OAuth gateway
@@ -110,10 +111,6 @@ function buildClaimMatchers(opts) {
110
111
  typ: ['at'],
111
112
  };
112
113
  }
113
- function readNonEmptyEnv(name) {
114
- const value = typeof process === 'undefined' ? undefined : process.env?.[name]?.trim();
115
- return value === undefined || value.length === 0 ? undefined : value;
116
- }
117
114
  function extractAudiences(payload) {
118
115
  const aud = payload.aud;
119
116
  if (typeof aud === 'string') {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- export { deleteTokenCacheEntry, getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, type ConnectErrorOptions, type ConnectOptions, type ConnectTokenParams, type ConnectTokenResponse, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from './token.js';
1
+ export { deleteTokenCacheEntry, getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, type ConnectErrorOptions, type ConnectOptions, type ConnectTokenExchangeSubject, type ConnectTokenParams, type ConnectTokenResponse, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from './token.js';
2
2
  export { startAuthorization, type ConnectAuthorizationOptions, type ConnectAuthorizationResponse, } from './authorization.js';
3
+ export { experimental_startInstallation, type ConnectInstallationOptions, type ConnectInstallationParams, type ConnectInstallationResponse, } from './installation.js';
3
4
  export type { ConnectAuthorizationDetail } from './authorization-details.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { deleteTokenCacheEntry, getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, } from './token.js';
2
2
  export { startAuthorization, } from './authorization.js';
3
+ export { experimental_startInstallation, } from './installation.js';
@@ -0,0 +1,42 @@
1
+ import type { ConnectTokenParams } from './token.js';
2
+ export type ConnectInstallationParams = Omit<ConnectTokenParams, 'subject'>;
3
+ export interface ConnectInstallationOptions {
4
+ vercelToken?: string;
5
+ returnUrl?: string;
6
+ webhook?: string;
7
+ tenantId?: string;
8
+ deviceCode?: boolean;
9
+ expiresInMs?: number;
10
+ }
11
+ export interface ConnectInstallationResponse {
12
+ request: string;
13
+ verifier: string;
14
+ url: string;
15
+ deviceCode?: string;
16
+ expiresAt: number;
17
+ connector: {
18
+ /** Client id. */
19
+ id: string;
20
+ /** Client uid. */
21
+ uid: string;
22
+ /** Client type, eg. `oauth`, `salesforce`. */
23
+ type: string;
24
+ /** Resolved service id when known, eg. `salesforce`. */
25
+ service?: string;
26
+ /**
27
+ * Curated display name of the resolved service, eg. `Salesforce`,
28
+ * present when the service is known to Vercel Connect. Suited for
29
+ * end-user surfaces like "Sign in with {serviceName}".
30
+ */
31
+ serviceName?: string;
32
+ /** The connector's own (operator-given) name. */
33
+ name: string;
34
+ };
35
+ }
36
+ /**
37
+ * Create an operator installation request for an app-scoped connector.
38
+ *
39
+ * @experimental This API is feature-gated while experimental. Contact Vercel
40
+ * to enable access before using it.
41
+ */
42
+ export declare function experimental_startInstallation(connector: string, params?: ConnectInstallationParams, options?: ConnectInstallationOptions): Promise<ConnectInstallationResponse>;
@@ -0,0 +1,50 @@
1
+ import { getVercelOidcToken } from '@vercel/oidc';
2
+ import { isDetachedInteractiveAuth, validateCallbackUrl, validateWebhookUrl, } from './internal/url-validation.js';
3
+ import { createConnectErrorFromResponse } from './token.js';
4
+ /**
5
+ * Create an operator installation request for an app-scoped connector.
6
+ *
7
+ * @experimental This API is feature-gated while experimental. Contact Vercel
8
+ * to enable access before using it.
9
+ */
10
+ export async function experimental_startInstallation(connector, params = {}, options) {
11
+ if (!connector) {
12
+ throw new Error('connector is required');
13
+ }
14
+ const detachedInteractiveAuth = isDetachedInteractiveAuth();
15
+ if (!detachedInteractiveAuth && options?.returnUrl !== undefined) {
16
+ validateCallbackUrl(options.returnUrl, 'returnUrl');
17
+ }
18
+ if (options?.webhook !== undefined) {
19
+ validateWebhookUrl(options.webhook);
20
+ }
21
+ const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
22
+ const endpoint = `https://api.vercel.com/v1/connect/install/${encodeURIComponent(connector)}`;
23
+ const deviceCode = options?.deviceCode ?? (detachedInteractiveAuth ? true : undefined);
24
+ const returnUrl = !detachedInteractiveAuth && options?.returnUrl !== undefined
25
+ ? { returnUrl: options.returnUrl }
26
+ : {};
27
+ const response = await fetch(endpoint, {
28
+ method: 'POST',
29
+ headers: {
30
+ Accept: 'application/json',
31
+ 'Content-Type': 'application/json',
32
+ Authorization: `Bearer ${vercelToken}`,
33
+ },
34
+ body: JSON.stringify({
35
+ ...params,
36
+ ...returnUrl,
37
+ ...(options?.webhook !== undefined && { webhook: options.webhook }),
38
+ ...(options?.tenantId !== undefined && { tenantId: options.tenantId }),
39
+ ...(deviceCode !== undefined && { deviceCode }),
40
+ ...(options?.expiresInMs !== undefined && {
41
+ expiresInMs: options.expiresInMs,
42
+ }),
43
+ }),
44
+ });
45
+ if (!response.ok) {
46
+ throw await createConnectErrorFromResponse(response, 'Failed to start installation');
47
+ }
48
+ const data = await response.json();
49
+ return data;
50
+ }
@@ -0,0 +1 @@
1
+ export declare function readNonEmptyEnv(name: string): string | undefined;
@@ -0,0 +1,4 @@
1
+ export function readNonEmptyEnv(name) {
2
+ const value = typeof process === 'undefined' ? undefined : process.env?.[name]?.trim();
3
+ return value === undefined || value.length === 0 ? undefined : value;
4
+ }
@@ -0,0 +1,3 @@
1
+ export declare function isDetachedInteractiveAuth(): boolean;
2
+ export declare function validateCallbackUrl(value: string, label?: string): void;
3
+ export declare function validateWebhookUrl(value: string): void;
@@ -0,0 +1,39 @@
1
+ import { readNonEmptyEnv } from './env.js';
2
+ const DETACHED_INTERACTIVE_AUTH_MODE = 'detached';
3
+ const INTERACTIVE_AUTH_MODE_ENV = 'VERCEL_CONNECT_INTERACTIVE_AUTH_MODE';
4
+ export function isDetachedInteractiveAuth() {
5
+ return (readNonEmptyEnv(INTERACTIVE_AUTH_MODE_ENV) ===
6
+ DETACHED_INTERACTIVE_AUTH_MODE);
7
+ }
8
+ export function validateCallbackUrl(value, label = 'callbackUrl') {
9
+ let url;
10
+ try {
11
+ url = new URL(value);
12
+ }
13
+ catch {
14
+ throw new Error(`Invalid ${label}: ${value}`);
15
+ }
16
+ if (url.protocol === 'https:')
17
+ return;
18
+ if (url.protocol === 'http:' && isLocalHttpCallbackHostname(url.hostname)) {
19
+ return;
20
+ }
21
+ throw new Error(`${label} must be https://, http://localhost, or http://*.localhost, got: ${value}`);
22
+ }
23
+ export function validateWebhookUrl(value) {
24
+ let url;
25
+ try {
26
+ url = new URL(value);
27
+ }
28
+ catch {
29
+ throw new Error(`Invalid webhook URL: ${value}`);
30
+ }
31
+ if (url.protocol !== 'https:') {
32
+ throw new Error(`webhook must be https://, got: ${value}`);
33
+ }
34
+ }
35
+ function isLocalHttpCallbackHostname(hostname) {
36
+ return (hostname === 'localhost' ||
37
+ hostname.endsWith('.localhost') ||
38
+ hostname === '127.0.0.1');
39
+ }
@@ -91,9 +91,9 @@ export declare class ConsentRequiredError extends Error {
91
91
  * @param connector Vercel Connect connector UID (e.g. `oauth/linear`)
92
92
  * or opaque connector id.
93
93
  * @param params Token request parameters — always specify a
94
- * `subject`; the three subject types
95
- * (`'app'` / `'user'` / `'jwt-bearer'`) have distinct security
96
- * semantics.
94
+ * `subject`; the supported subject types
95
+ * (`'app'` / `'user'` / `'jwt-bearer'` / `'token'`) have distinct
96
+ * security semantics.
97
97
  * @param options Optional adapter behavior overrides.
98
98
  */
99
99
  export declare function connectAuthProvider(connector: string, params: ConnectTokenParams, options?: ConnectAuthProviderOptions): OAuthClientProvider;
@@ -41,9 +41,9 @@ const EMPTY_CLIENT_METADATA = {
41
41
  * @param connector Vercel Connect connector UID (e.g. `oauth/linear`)
42
42
  * or opaque connector id.
43
43
  * @param params Token request parameters — always specify a
44
- * `subject`; the three subject types
45
- * (`'app'` / `'user'` / `'jwt-bearer'`) have distinct security
46
- * semantics.
44
+ * `subject`; the supported subject types
45
+ * (`'app'` / `'user'` / `'jwt-bearer'` / `'token'`) have distinct
46
+ * security semantics.
47
47
  * @param options Optional adapter behavior overrides.
48
48
  */
49
49
  export function connectAuthProvider(connector, params, options) {
@@ -30,4 +30,4 @@
30
30
  * ```
31
31
  */
32
32
  export { connectAuthProvider, ConsentRequiredError, type ConnectAuthProviderOptions, type ConsentChallenge, } from './connect-auth-provider.js';
33
- export { ConnectError, ConnectorInstallationRequiredError, NoValidTokenError, UserAuthorizationRequiredError, type ConnectErrorOptions, type ConnectTokenParams, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from '../token.js';
33
+ export { ConnectError, ConnectorInstallationRequiredError, NoValidTokenError, UserAuthorizationRequiredError, type ConnectErrorOptions, type ConnectTokenExchangeSubject, type ConnectTokenParams, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from '../token.js';
package/dist/token.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ConnectAuthorizationDetail } from './authorization-details.js';
2
- export type ConnectSubjectType = 'app' | 'user' | 'jwt-bearer';
3
- export type ConnectTokenSubject = ConnectAppTokenSubject | ConnectUserTokenSubject | ConnectJwtBearerTokenSubject;
2
+ export type ConnectSubjectType = 'app' | 'user' | 'jwt-bearer' | 'token';
3
+ export type ConnectTokenSubject = ConnectAppTokenSubject | ConnectUserTokenSubject | ConnectJwtBearerTokenSubject | ConnectTokenExchangeSubject;
4
4
  export interface ConnectAppTokenSubject {
5
5
  type: 'app';
6
6
  }
@@ -18,6 +18,10 @@ export interface ConnectJwtBearerTokenSubject {
18
18
  aud?: string;
19
19
  additionalClaims?: Record<string, unknown>;
20
20
  }
21
+ export interface ConnectTokenExchangeSubject {
22
+ type: 'token';
23
+ token: string;
24
+ }
21
25
  export interface ConnectTokenParams {
22
26
  subject: ConnectTokenSubject;
23
27
  installationId?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/connect",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "dist"
43
43
  ],
44
44
  "dependencies": {
45
- "@vercel/oidc": "3.8.0"
45
+ "@vercel/oidc": "3.8.1"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@ai-sdk/mcp": "^1 || ^2",