@vercel/connect 1.0.0 → 2.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.
- package/README.md +28 -10
- package/dist/authorization.d.ts +1 -1
- package/dist/authorization.js +2 -6
- package/dist/chat/index.d.ts +2 -1
- package/dist/chat/index.js +1 -0
- package/dist/chat/sendblue-adapter.d.ts +25 -0
- package/dist/chat/sendblue-adapter.js +21 -0
- package/dist/chat/types.d.ts +12 -0
- package/dist/eve/connection-authorization.d.ts +6 -5
- package/dist/eve/connection-authorization.js +31 -13
- package/dist/eve/index.d.ts +1 -0
- package/dist/eve/index.js +1 -0
- package/dist/eve/sendblue-credentials.d.ts +23 -0
- package/dist/eve/sendblue-credentials.js +14 -0
- package/dist/internal/sendblue-lines.d.ts +33 -0
- package/dist/internal/sendblue-lines.js +80 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ SDK for obtaining scoped tokens for third-party services on behalf of apps or us
|
|
|
5
5
|
Seven entrypoints, all ESM:
|
|
6
6
|
|
|
7
7
|
- `@vercel/connect` — core token / authorization SDK
|
|
8
|
-
- `@vercel/connect/chat` — adapter helpers for the [Chat SDK](https://chat-sdk.dev) (`chat`): `connectSlackAdapter`, `connectDiscordAdapter`, `connectGitHubAdapter`, `connectLinearAdapter`, `connectNotionAdapter`, `connectTelegramAdapter` (no Chat SDK dependency — returns structural config)
|
|
8
|
+
- `@vercel/connect/chat` — adapter helpers for the [Chat SDK](https://chat-sdk.dev) (`chat`): `connectSlackAdapter`, `connectDiscordAdapter`, `connectGitHubAdapter`, `connectLinearAdapter`, `connectNotionAdapter`, `connectTelegramAdapter`, `connectSendblueAdapter` (no Chat SDK dependency — returns structural config)
|
|
9
9
|
- `@vercel/connect/ai-sdk` — [Vercel AI SDK](https://ai-sdk.dev) glue: re-exports `connectAuthProvider` for MCP transports (optional peers: `ai`, `@ai-sdk/mcp`)
|
|
10
10
|
- `@vercel/connect/mcp` — canonical MCP-spec `OAuthClientProvider` for any MCP client (optional peer: `@ai-sdk/mcp`)
|
|
11
11
|
- `@vercel/connect/eve` — adapter helpers for [Eve](https://github.com/vercel/eve) connections (optional peer: `eve`)
|
|
@@ -64,10 +64,12 @@ createSlackAdapter({
|
|
|
64
64
|
|
|
65
65
|
`connectDiscordAdapter` (`botToken` and `applicationId`),
|
|
66
66
|
`connectGitHubAdapter` (`installationToken`), and `connectLinearAdapter`
|
|
67
|
-
(`accessToken`) follow the same shape. `
|
|
68
|
-
|
|
69
|
-
`
|
|
70
|
-
|
|
67
|
+
(`accessToken`) follow the same shape. `connectSendblueAdapter` supplies a
|
|
68
|
+
lazy Sendblue `accessToken` and Connect OIDC webhook verifier.
|
|
69
|
+
`connectNotionAdapter` supplies only the outbound `token`; native Notion
|
|
70
|
+
webhooks still require `NOTION_VERIFICATION_TOKEN`. `connectTelegramAdapter`
|
|
71
|
+
supplies only `botToken`; Telegram retains native webhook verification or
|
|
72
|
+
polling. See the
|
|
71
73
|
[Chat SDK integration guide](https://github.com/vercel/vercel/blob/main/packages/connect/docs/chat-integration.md)
|
|
72
74
|
for connector setup, trigger forwarding, and per-platform examples.
|
|
73
75
|
|
|
@@ -110,7 +112,21 @@ SDK's `toolApproval` option or `wrapMcpTools` from `@ai-sdk/policy-opa`.
|
|
|
110
112
|
Non-AI-SDK MCP clients (the official MCP TypeScript SDK, Mastra, etc.)
|
|
111
113
|
can import the same `connectAuthProvider` from `@vercel/connect/mcp`.
|
|
112
114
|
|
|
113
|
-
###
|
|
115
|
+
### Eve
|
|
116
|
+
|
|
117
|
+
Use `connectSendblueCredentials` with Eve's native Sendblue channel. It
|
|
118
|
+
resolves the app-scoped bearer token and managed sending line together, and
|
|
119
|
+
includes Vercel OIDC webhook verification. When the connector has multiple
|
|
120
|
+
lines, pass `fromNumber` to choose one.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { connectSendblueCredentials } from '@vercel/connect/eve';
|
|
124
|
+
import { sendblueChannel } from 'eve/channels/sendblue';
|
|
125
|
+
|
|
126
|
+
export default sendblueChannel({
|
|
127
|
+
credentials: connectSendblueCredentials('sendblue/my-agent'),
|
|
128
|
+
});
|
|
129
|
+
```
|
|
114
130
|
|
|
115
131
|
```ts
|
|
116
132
|
import { defineMcpClientConnection } from 'eve/connections';
|
|
@@ -118,13 +134,15 @@ import { connect } from '@vercel/connect/eve';
|
|
|
118
134
|
|
|
119
135
|
export default defineMcpClientConnection({
|
|
120
136
|
url: 'https://mcp.linear.app/sse',
|
|
121
|
-
auth: connect('linear'),
|
|
137
|
+
auth: connect({ connector: 'linear', autoProvision: true }),
|
|
122
138
|
});
|
|
123
139
|
```
|
|
124
140
|
|
|
125
|
-
By default, `connect()`
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
By default, `connect()` only uses connectors already linked to the project and
|
|
142
|
+
does not provision or modify connectors at runtime. Pass `autoProvision: true`
|
|
143
|
+
to opt in. When enabled, `connect()` first tries the token or authorization
|
|
144
|
+
request; if the connector is missing or not linked, it provisions or links the
|
|
145
|
+
connector and retries the request once.
|
|
128
146
|
|
|
129
147
|
### Better Auth
|
|
130
148
|
|
package/dist/authorization.d.ts
CHANGED
package/dist/authorization.js
CHANGED
|
@@ -2,6 +2,7 @@ import { getVercelOidcToken } from '@vercel/oidc';
|
|
|
2
2
|
import { isDetachedInteractiveAuth, validateCallbackUrl, validateWebhookUrl, } from './internal/url-validation.js';
|
|
3
3
|
import { withDefaultScopes } from './internal/default-scopes.js';
|
|
4
4
|
import { resolveBaseUrl } from './internal/base-url.js';
|
|
5
|
+
import { createConnectErrorFromResponse, } from './token.js';
|
|
5
6
|
export async function startAuthorization(connector, params, options) {
|
|
6
7
|
if (!connector) {
|
|
7
8
|
throw new Error('connector is required');
|
|
@@ -42,12 +43,7 @@ export async function startAuthorization(connector, params, options) {
|
|
|
42
43
|
body: JSON.stringify(body),
|
|
43
44
|
});
|
|
44
45
|
if (!response.ok) {
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
errorText = await response.text();
|
|
48
|
-
}
|
|
49
|
-
catch { }
|
|
50
|
-
throw new Error(`Failed to start authorization: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ''}`);
|
|
46
|
+
throw await createConnectErrorFromResponse(response, 'Failed to start authorization');
|
|
51
47
|
}
|
|
52
48
|
const data = await response.json();
|
|
53
49
|
return data;
|
package/dist/chat/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
export { createConnectWebhookVerifier, type ConnectWebhookVerifier, type ConnectWebhookVerifierOptions, } from './webhook-verifier.js';
|
|
24
|
-
export type { ConnectDiscordAdapterConfig, ConnectGitHubAdapterConfig, ConnectLinearAdapterConfig, ConnectLinqAdapterConfig, ConnectLinqCredentials, ConnectNotionAdapterConfig, ConnectTelegramAdapterConfig, ConnectTokenResolver, } from './types.js';
|
|
24
|
+
export type { ConnectDiscordAdapterConfig, ConnectGitHubAdapterConfig, ConnectLinearAdapterConfig, ConnectLinqAdapterConfig, ConnectLinqCredentials, ConnectNotionAdapterConfig, ConnectSendblueAdapterConfig, ConnectTelegramAdapterConfig, ConnectTokenResolver, } from './types.js';
|
|
25
25
|
export { connectDiscordAdapter, type ConnectDiscordAdapterParams, } from './discord-adapter.js';
|
|
26
26
|
export { connectSlackAdapter, type ConnectSlackAdapterConfig, type ConnectSlackAdapterParams, } from './slack-adapter.js';
|
|
27
27
|
export { connectGitHubAdapter, type ConnectGitHubAdapterParams, } from './github-adapter.js';
|
|
@@ -29,3 +29,4 @@ export { connectLinearAdapter, type ConnectLinearAdapterParams, } from './linear
|
|
|
29
29
|
export { connectLinqAdapter, type ConnectLinqAdapterParams, } from './linq-adapter.js';
|
|
30
30
|
export { connectNotionAdapter, type ConnectNotionAdapterParams, } from './notion-adapter.js';
|
|
31
31
|
export { connectTelegramAdapter, type ConnectTelegramAdapterParams, } from './telegram-adapter.js';
|
|
32
|
+
export { connectSendblueAdapter, type ConnectSendblueAdapterParams, } from './sendblue-adapter.js';
|
package/dist/chat/index.js
CHANGED
|
@@ -28,3 +28,4 @@ export { connectLinearAdapter, } from './linear-adapter.js';
|
|
|
28
28
|
export { connectLinqAdapter, } from './linq-adapter.js';
|
|
29
29
|
export { connectNotionAdapter, } from './notion-adapter.js';
|
|
30
30
|
export { connectTelegramAdapter, } from './telegram-adapter.js';
|
|
31
|
+
export { connectSendblueAdapter, } from './sendblue-adapter.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ConnectOptions } from '../index.js';
|
|
2
|
+
import { type ConnectSendblueParams } from '../internal/sendblue-lines.js';
|
|
3
|
+
import type { ConnectSendblueAdapterConfig } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Token parameters accepted by {@link connectSendblueAdapter}.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors {@link ConnectTokenParams} from `@vercel/connect`, minus `subject`.
|
|
8
|
+
* Sendblue account credentials are app-scoped, so this helper pins the subject
|
|
9
|
+
* to `{ type: 'app' }`.
|
|
10
|
+
*/
|
|
11
|
+
export type ConnectSendblueAdapterParams = ConnectSendblueParams;
|
|
12
|
+
/**
|
|
13
|
+
* Build a Sendblue adapter config fragment backed by a Vercel Connect
|
|
14
|
+
* connector.
|
|
15
|
+
*
|
|
16
|
+
* Sendblue connectors issue a short-lived, line-scoped bearer token. The
|
|
17
|
+
* returned `accessToken` resolver fetches that token for each adapter API
|
|
18
|
+
* operation so Connect token rotation takes effect without rebuilding the
|
|
19
|
+
* adapter. When the connector has one line, that line is selected
|
|
20
|
+
* automatically; select a line explicitly when it has more than one. The
|
|
21
|
+
* webhook verifier accepts Connect trigger-forwarded webhooks using Vercel
|
|
22
|
+
* OIDC, replacing Sendblue's provider-native verification at the deployment
|
|
23
|
+
* boundary.
|
|
24
|
+
*/
|
|
25
|
+
export declare function connectSendblueAdapter(connector: string, params?: ConnectSendblueAdapterParams, options?: ConnectOptions): ConnectSendblueAdapterConfig;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createConnectSendblueConfig, } from '../internal/sendblue-lines.js';
|
|
2
|
+
import { createConnectWebhookVerifier } from './webhook-verifier.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build a Sendblue adapter config fragment backed by a Vercel Connect
|
|
5
|
+
* connector.
|
|
6
|
+
*
|
|
7
|
+
* Sendblue connectors issue a short-lived, line-scoped bearer token. The
|
|
8
|
+
* returned `accessToken` resolver fetches that token for each adapter API
|
|
9
|
+
* operation so Connect token rotation takes effect without rebuilding the
|
|
10
|
+
* adapter. When the connector has one line, that line is selected
|
|
11
|
+
* automatically; select a line explicitly when it has more than one. The
|
|
12
|
+
* webhook verifier accepts Connect trigger-forwarded webhooks using Vercel
|
|
13
|
+
* OIDC, replacing Sendblue's provider-native verification at the deployment
|
|
14
|
+
* boundary.
|
|
15
|
+
*/
|
|
16
|
+
export function connectSendblueAdapter(connector, params = {}, options) {
|
|
17
|
+
return {
|
|
18
|
+
...createConnectSendblueConfig(connector, params, options),
|
|
19
|
+
webhookVerifier: createConnectWebhookVerifier(),
|
|
20
|
+
};
|
|
21
|
+
}
|
package/dist/chat/types.d.ts
CHANGED
|
@@ -76,3 +76,15 @@ export interface ConnectNotionAdapterConfig {
|
|
|
76
76
|
export interface ConnectTelegramAdapterConfig {
|
|
77
77
|
botToken: ConnectTokenResolver;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Partial Sendblue adapter config backed by Vercel Connect.
|
|
81
|
+
*
|
|
82
|
+
* Structurally matches the `accessToken` and `webhookVerifier` options of
|
|
83
|
+
* `createSendblueAdapter` from `chat-adapter-sendblue`.
|
|
84
|
+
*/
|
|
85
|
+
export interface ConnectSendblueAdapterConfig {
|
|
86
|
+
accessToken: ConnectTokenResolver;
|
|
87
|
+
defaultFromNumber: string | (() => Promise<string>);
|
|
88
|
+
allowedFromNumbers: readonly string[] | (() => Promise<readonly string[]>);
|
|
89
|
+
webhookVerifier: ConnectWebhookVerifier;
|
|
90
|
+
}
|
|
@@ -117,17 +117,18 @@ export interface EveAuthorizationOptions {
|
|
|
117
117
|
readonly connectOptions?: ConnectOptions;
|
|
118
118
|
/**
|
|
119
119
|
* Create or link the declared connector against the deploying Vercel
|
|
120
|
-
* project
|
|
121
|
-
* `true
|
|
120
|
+
* project when a token / authorization call reports that the connector
|
|
121
|
+
* is missing or not linked. Defaults to `false`; set this to `true` to
|
|
122
|
+
* opt in to runtime provisioning.
|
|
122
123
|
*
|
|
123
124
|
* The provision request is authenticated with the deployment OIDC token
|
|
124
125
|
* and carries the eve connection's `url` plus this connector UID. Connect
|
|
125
126
|
* creates the managed OAuth connector when missing, links an existing
|
|
126
127
|
* OAuth connector when the UID already exists, and scopes the new project
|
|
127
|
-
* link to the OIDC token's environment
|
|
128
|
+
* link to the OIDC token's authenticated environment.
|
|
128
129
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
130
|
+
* Existing linked connectors are always used directly without a
|
|
131
|
+
* provisioning request. Opaque connector ids (`scl_...`) and connections
|
|
131
132
|
* without a URL are skipped automatically.
|
|
132
133
|
*/
|
|
133
134
|
readonly autoProvision?: boolean;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError, } from 'eve/connections';
|
|
32
32
|
import { startAuthorization } from '../authorization.js';
|
|
33
|
-
import { ConnectorInstallationRequiredError, deleteTokenCacheEntry, getTokenResponse, NoValidTokenError, revokeToken, UserAuthorizationRequiredError, } from '../token.js';
|
|
33
|
+
import { ConnectError, ConnectorInstallationRequiredError, deleteTokenCacheEntry, getTokenResponse, NoValidTokenError, revokeToken, UserAuthorizationRequiredError, } from '../token.js';
|
|
34
34
|
import { provisionEveOAuthConnector } from './provision-oauth-connector.js';
|
|
35
35
|
export function connect(input) {
|
|
36
36
|
const options = normalizeAuthorizationOptions(input);
|
|
@@ -85,8 +85,8 @@ function buildInteractiveDefinition(options) {
|
|
|
85
85
|
principalType: 'user',
|
|
86
86
|
async getToken({ principal, connection, }) {
|
|
87
87
|
try {
|
|
88
|
-
await
|
|
89
|
-
const response = await
|
|
88
|
+
const params = await buildTokenParams(options, principal, connection);
|
|
89
|
+
const response = await withConnectorProvisioningFallback(options, connection, () => getTokenResponse(options.connector, params, getTokenConnectOptions(options)));
|
|
90
90
|
return { token: response.token, expiresAt: response.expiresAt };
|
|
91
91
|
}
|
|
92
92
|
catch (error) {
|
|
@@ -95,7 +95,6 @@ function buildInteractiveDefinition(options) {
|
|
|
95
95
|
},
|
|
96
96
|
async startAuthorization({ principal, connection, callbackUrl, webhook, }) {
|
|
97
97
|
try {
|
|
98
|
-
await autoProvisionConnectorIfEnabled(options, connection);
|
|
99
98
|
// eve's `webhook` parameter is also the browser-redirect
|
|
100
99
|
// target when `callbackUrl` is absent — the orchestrator mints
|
|
101
100
|
// it via `createWebhook({ respondWith:
|
|
@@ -115,12 +114,13 @@ function buildInteractiveDefinition(options) {
|
|
|
115
114
|
// OIDC, which is what lets per-workflow dynamic webhook URLs
|
|
116
115
|
// work without an OAuth-style redirect-URI allowlist.
|
|
117
116
|
const completionWebhook = connectCompletionWebhook(webhook);
|
|
118
|
-
const
|
|
117
|
+
const params = await buildTokenParams(options, principal, connection);
|
|
118
|
+
const response = await withConnectorProvisioningFallback(options, connection, () => startAuthorization(options.connector, params, {
|
|
119
119
|
...options.connectOptions,
|
|
120
120
|
callbackUrl: callbackUrl ?? webhook,
|
|
121
121
|
...(completionWebhook ? { webhook: completionWebhook } : null),
|
|
122
122
|
deviceCode: true,
|
|
123
|
-
});
|
|
123
|
+
}));
|
|
124
124
|
// Sign-in buttons name the destination service ("Sign in with
|
|
125
125
|
// Salesforce"), matching the OAuth idiom — the consent screen
|
|
126
126
|
// names the specific requesting app. The connector's own name is
|
|
@@ -149,8 +149,8 @@ function buildInteractiveDefinition(options) {
|
|
|
149
149
|
},
|
|
150
150
|
async completeAuthorization({ principal, connection, }) {
|
|
151
151
|
try {
|
|
152
|
-
await
|
|
153
|
-
const response = await
|
|
152
|
+
const params = await buildTokenParams(options, principal, connection);
|
|
153
|
+
const response = await withConnectorProvisioningFallback(options, connection, () => getTokenResponse(options.connector, params, options.connectOptions));
|
|
154
154
|
return { token: response.token, expiresAt: response.expiresAt };
|
|
155
155
|
}
|
|
156
156
|
catch (error) {
|
|
@@ -175,8 +175,8 @@ function buildNonInteractiveDefinition(options) {
|
|
|
175
175
|
principalType: 'app',
|
|
176
176
|
async getToken({ principal, connection, }) {
|
|
177
177
|
try {
|
|
178
|
-
await
|
|
179
|
-
const response = await
|
|
178
|
+
const params = await buildTokenParams(options, principal, connection);
|
|
179
|
+
const response = await withConnectorProvisioningFallback(options, connection, () => getTokenResponse(options.connector, params, getTokenConnectOptions(options)));
|
|
180
180
|
return { token: response.token, expiresAt: response.expiresAt };
|
|
181
181
|
}
|
|
182
182
|
catch (error) {
|
|
@@ -185,15 +185,33 @@ function buildNonInteractiveDefinition(options) {
|
|
|
185
185
|
},
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
|
-
async function
|
|
189
|
-
|
|
190
|
-
return;
|
|
188
|
+
async function withConnectorProvisioningFallback(options, connection, operation) {
|
|
189
|
+
try {
|
|
190
|
+
return await operation();
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (options.autoProvision !== true ||
|
|
194
|
+
!isMissingConnectorOrProjectLink(error)) {
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
191
197
|
}
|
|
192
198
|
await provisionEveOAuthConnector({
|
|
193
199
|
connector: options.connector,
|
|
194
200
|
connection,
|
|
195
201
|
connectOptions: options.connectOptions,
|
|
196
202
|
});
|
|
203
|
+
return operation();
|
|
204
|
+
}
|
|
205
|
+
function isMissingConnectorOrProjectLink(error) {
|
|
206
|
+
if (!(error instanceof ConnectError)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (error.status === 404 && error.code === 'not_found') {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return (error.status === 403 &&
|
|
213
|
+
error.code === 'forbidden' &&
|
|
214
|
+
/connector is not linked to this project/i.test(error.message));
|
|
197
215
|
}
|
|
198
216
|
/**
|
|
199
217
|
* Connect SDK options for `getToken` calls. When {@link
|
package/dist/eve/index.d.ts
CHANGED
|
@@ -14,3 +14,4 @@ export { connectLinearCredentials, type ConnectLinearCredentialsParams, } from '
|
|
|
14
14
|
export { connectLinqCredentials, type ConnectLinqCredentials, type ConnectLinqCredentialsParams, } from './linq-credentials.js';
|
|
15
15
|
export { connectPhotonCredentials, type ConnectPhotonCredentialProvider, type PhotonCredentials, type ConnectPhotonCredentialsParams, } from './photon-credentials.js';
|
|
16
16
|
export { connectSlackCredentials, type ConnectSlackCredentialsParams, } from './slack-credentials.js';
|
|
17
|
+
export { connectSendblueCredentials, type ConnectSendblueCredentials, type ConnectSendblueCredentialsParams, } from './sendblue-credentials.js';
|
package/dist/eve/index.js
CHANGED
|
@@ -14,3 +14,4 @@ export { connectLinearCredentials, } from './linear-credentials.js';
|
|
|
14
14
|
export { connectLinqCredentials, } from './linq-credentials.js';
|
|
15
15
|
export { connectPhotonCredentials, } from './photon-credentials.js';
|
|
16
16
|
export { connectSlackCredentials, } from './slack-credentials.js';
|
|
17
|
+
export { connectSendblueCredentials, } from './sendblue-credentials.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ConnectOptions } from '../index.js';
|
|
2
|
+
import { type ConnectSendblueParams } from '../internal/sendblue-lines.js';
|
|
3
|
+
import { vercelOidc } from 'eve/channels/auth';
|
|
4
|
+
/** Managed Sendblue channel credentials returned by {@link connectSendblueCredentials}. */
|
|
5
|
+
export interface ConnectSendblueCredentials {
|
|
6
|
+
/** Short-lived bearer token for outbound Sendblue API calls. */
|
|
7
|
+
accessToken: () => Promise<string>;
|
|
8
|
+
/** Default managed Sendblue line. */
|
|
9
|
+
defaultFromNumber: () => Promise<string>;
|
|
10
|
+
/** Every Sendblue line authorized for this connector. */
|
|
11
|
+
allowedFromNumbers: () => Promise<readonly string[]>;
|
|
12
|
+
/** Verifies webhooks forwarded by Vercel Connect. */
|
|
13
|
+
webhookVerifier: ReturnType<typeof vercelOidc>;
|
|
14
|
+
}
|
|
15
|
+
/** Token parameters accepted by {@link connectSendblueCredentials}. */
|
|
16
|
+
export type ConnectSendblueCredentialsParams = ConnectSendblueParams;
|
|
17
|
+
/**
|
|
18
|
+
* Builds managed Sendblue channel credentials backed by a Vercel Connect
|
|
19
|
+
* connector. The access-token resolver and managed line resolvers keep token
|
|
20
|
+
* rotation and line selection delegated to Connect. Vercel OIDC verifies
|
|
21
|
+
* Connect-forwarded webhooks.
|
|
22
|
+
*/
|
|
23
|
+
export declare function connectSendblueCredentials(connector: string, params?: ConnectSendblueCredentialsParams, options?: ConnectOptions): ConnectSendblueCredentials;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createConnectSendblueConfig, } from '../internal/sendblue-lines.js';
|
|
2
|
+
import { vercelOidc } from 'eve/channels/auth';
|
|
3
|
+
/**
|
|
4
|
+
* Builds managed Sendblue channel credentials backed by a Vercel Connect
|
|
5
|
+
* connector. The access-token resolver and managed line resolvers keep token
|
|
6
|
+
* rotation and line selection delegated to Connect. Vercel OIDC verifies
|
|
7
|
+
* Connect-forwarded webhooks.
|
|
8
|
+
*/
|
|
9
|
+
export function connectSendblueCredentials(connector, params = {}, options) {
|
|
10
|
+
return {
|
|
11
|
+
...createConnectSendblueConfig(connector, params, options),
|
|
12
|
+
webhookVerifier: vercelOidc(),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ConnectOptions, type ConnectTokenParams } from '../token.js';
|
|
2
|
+
export type SendblueFromNumber = string | (() => Promise<string>);
|
|
3
|
+
/** Shared token parameters accepted by Sendblue Connect helpers. */
|
|
4
|
+
export type ConnectSendblueParams = Omit<ConnectTokenParams, 'subject'> & {
|
|
5
|
+
/** Select a Sendblue line when the connector owns more than one. */
|
|
6
|
+
fromNumber?: SendblueFromNumber;
|
|
7
|
+
};
|
|
8
|
+
/** Shared Connect-backed Sendblue credentials and line resolvers. */
|
|
9
|
+
export interface ConnectSendblueConfig {
|
|
10
|
+
accessToken: () => Promise<string>;
|
|
11
|
+
defaultFromNumber: () => Promise<string>;
|
|
12
|
+
allowedFromNumbers: () => Promise<readonly string[]>;
|
|
13
|
+
}
|
|
14
|
+
/** Reads non-empty Sendblue line numbers from Connect connector metadata. */
|
|
15
|
+
export declare function readPhoneNumbers(vendor: Record<string, unknown>): string[];
|
|
16
|
+
/** Resolvers for a selected Sendblue line and every line managed by Connect. */
|
|
17
|
+
export interface SendblueLineResolvers {
|
|
18
|
+
fromNumber: () => Promise<string>;
|
|
19
|
+
allowedFromNumbers: () => Promise<readonly string[]>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Builds shared lazy resolvers for the connector's managed Sendblue lines.
|
|
23
|
+
* An explicit selected line must be one of the managed lines; when no line is
|
|
24
|
+
* selected, Connect's sole line is used automatically.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createSendblueLineResolvers(connector: string, fromNumber: SendblueFromNumber | undefined, options?: ConnectOptions): SendblueLineResolvers;
|
|
27
|
+
/** Resolves an explicit Sendblue line, or the connector's sole managed line. */
|
|
28
|
+
export declare function createSendblueFromNumberResolver(connector: string, fromNumber: SendblueFromNumber | undefined, options?: ConnectOptions): () => Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Builds the shared credential and line configuration for Sendblue Connect
|
|
31
|
+
* integrations. Callers add the webhook verifier required by their runtime.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createConnectSendblueConfig(connector: string, params?: ConnectSendblueParams, options?: ConnectOptions): ConnectSendblueConfig;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getConnectorMetadata } from '../connector.js';
|
|
2
|
+
import { getToken, } from '../token.js';
|
|
3
|
+
/** Reads non-empty Sendblue line numbers from Connect connector metadata. */
|
|
4
|
+
export function readPhoneNumbers(vendor) {
|
|
5
|
+
const lines = vendor.lines;
|
|
6
|
+
if (!Array.isArray(lines))
|
|
7
|
+
return [];
|
|
8
|
+
const phoneNumbers = [];
|
|
9
|
+
for (const line of lines) {
|
|
10
|
+
if (typeof line !== 'object' || line === null)
|
|
11
|
+
return [];
|
|
12
|
+
const phoneNumber = line.phone_number;
|
|
13
|
+
if (typeof phoneNumber !== 'string' || phoneNumber.length === 0)
|
|
14
|
+
return [];
|
|
15
|
+
phoneNumbers.push(phoneNumber);
|
|
16
|
+
}
|
|
17
|
+
return phoneNumbers;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Builds shared lazy resolvers for the connector's managed Sendblue lines.
|
|
21
|
+
* An explicit selected line must be one of the managed lines; when no line is
|
|
22
|
+
* selected, Connect's sole line is used automatically.
|
|
23
|
+
*/
|
|
24
|
+
export function createSendblueLineResolvers(connector, fromNumber, options) {
|
|
25
|
+
let pending;
|
|
26
|
+
let resolved;
|
|
27
|
+
function resolveAllowedFromNumbers() {
|
|
28
|
+
if (resolved !== undefined)
|
|
29
|
+
return Promise.resolve(resolved);
|
|
30
|
+
if (!pending) {
|
|
31
|
+
pending = getConnectorMetadata(connector, options)
|
|
32
|
+
.then(metadata => {
|
|
33
|
+
const phoneNumbers = readPhoneNumbers(metadata.vendor);
|
|
34
|
+
if (phoneNumbers.length === 0) {
|
|
35
|
+
throw new Error(`Vercel Connect connector ${connector} has no Sendblue lines.`);
|
|
36
|
+
}
|
|
37
|
+
resolved = phoneNumbers;
|
|
38
|
+
return phoneNumbers;
|
|
39
|
+
})
|
|
40
|
+
.finally(() => {
|
|
41
|
+
pending = undefined;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return pending;
|
|
45
|
+
}
|
|
46
|
+
async function resolveFromNumber() {
|
|
47
|
+
const phoneNumbers = await resolveAllowedFromNumbers();
|
|
48
|
+
const selected = typeof fromNumber === 'function' ? await fromNumber() : fromNumber;
|
|
49
|
+
if (selected === undefined) {
|
|
50
|
+
if (phoneNumbers.length === 1)
|
|
51
|
+
return phoneNumbers[0];
|
|
52
|
+
throw new Error(`Vercel Connect connector ${connector} has multiple Sendblue lines. Pass fromNumber to select one.`);
|
|
53
|
+
}
|
|
54
|
+
if (!phoneNumbers.includes(selected)) {
|
|
55
|
+
throw new Error(`Vercel Connect connector ${connector} did not authorize Sendblue line ${selected}.`);
|
|
56
|
+
}
|
|
57
|
+
return selected;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
fromNumber: resolveFromNumber,
|
|
61
|
+
allowedFromNumbers: resolveAllowedFromNumbers,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Resolves an explicit Sendblue line, or the connector's sole managed line. */
|
|
65
|
+
export function createSendblueFromNumberResolver(connector, fromNumber, options) {
|
|
66
|
+
return createSendblueLineResolvers(connector, fromNumber, options).fromNumber;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Builds the shared credential and line configuration for Sendblue Connect
|
|
70
|
+
* integrations. Callers add the webhook verifier required by their runtime.
|
|
71
|
+
*/
|
|
72
|
+
export function createConnectSendblueConfig(connector, params = {}, options) {
|
|
73
|
+
const { fromNumber, ...tokenParams } = params;
|
|
74
|
+
const { fromNumber: defaultFromNumber, allowedFromNumbers } = createSendblueLineResolvers(connector, fromNumber, options);
|
|
75
|
+
return {
|
|
76
|
+
accessToken: () => getToken(connector, { ...tokenParams, subject: { type: 'app' } }, options),
|
|
77
|
+
defaultFromNumber,
|
|
78
|
+
allowedFromNumbers,
|
|
79
|
+
};
|
|
80
|
+
}
|