@vercel/connect 0.2.2 → 0.2.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/dist/eve/connection-authorization.d.ts +27 -0
- package/dist/eve/connection-authorization.js +37 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/token.d.ts +18 -0
- package/dist/token.js +29 -1
- package/package.json +1 -1
|
@@ -151,6 +151,33 @@ export interface VercelConnectMetadata {
|
|
|
151
151
|
*/
|
|
152
152
|
export type EveConnectAuthorizationDefinition<TAuthorization extends InteractiveAuthorizationDefinition | NonInteractiveAuthorizationDefinition> = TAuthorization & {
|
|
153
153
|
readonly vercelConnect: VercelConnectMetadata;
|
|
154
|
+
/**
|
|
155
|
+
* Drops the in-process Vercel Connect token cache entry for
|
|
156
|
+
* `principal` so the next `getToken` re-fetches instead of re-serving a
|
|
157
|
+
* rejected bearer. Eve's runtime calls this from its shared eviction
|
|
158
|
+
* path when a resolved token is rejected (a downstream `401` mapped to
|
|
159
|
+
* `requireAuth()`, or an MCP server rejecting the bearer), cascading
|
|
160
|
+
* invalidation from Eve's per-step cache down into this adapter's cache.
|
|
161
|
+
*
|
|
162
|
+
* By default this is a local-cache-only operation: it preserves the
|
|
163
|
+
* underlying Connect grant (and its refresh token), so the next
|
|
164
|
+
* `getToken` can refresh a merely-expired access token without forcing
|
|
165
|
+
* a new consent flow. That is the right default for the automatic
|
|
166
|
+
* `401` cascade, where the rejected bearer is usually just stale.
|
|
167
|
+
*
|
|
168
|
+
* Pass `revoke: true` only when the grant itself is known to be dead
|
|
169
|
+
* and you want it torn down at Vercel Connect (refresh token included)
|
|
170
|
+
* so the next `getToken` surfaces `user_authorization_required` and
|
|
171
|
+
* re-runs consent — e.g. a user-initiated "disconnect this
|
|
172
|
+
* integration" action. Revocation is destructive and best-effort: a
|
|
173
|
+
* failed or duplicate revoke is swallowed so it never masks the error
|
|
174
|
+
* that triggered eviction, and the local cache entry is dropped either
|
|
175
|
+
* way.
|
|
176
|
+
*/
|
|
177
|
+
readonly evict: (opts: {
|
|
178
|
+
readonly principal: ConnectionPrincipal;
|
|
179
|
+
readonly revoke?: boolean;
|
|
180
|
+
}) => Promise<void>;
|
|
154
181
|
};
|
|
155
182
|
/**
|
|
156
183
|
* Builds an Eve {@link AuthorizationDefinition} backed by Vercel
|
|
@@ -30,14 +30,48 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError, } from 'eve/connections';
|
|
32
32
|
import { startAuthorization } from '../authorization.js';
|
|
33
|
-
import { ConnectorInstallationRequiredError, getTokenResponse, NoValidTokenError, UserAuthorizationRequiredError, } from '../token.js';
|
|
33
|
+
import { ConnectorInstallationRequiredError, deleteTokenCacheEntry, getTokenResponse, NoValidTokenError, revokeToken, UserAuthorizationRequiredError, } from '../token.js';
|
|
34
34
|
export function connect(input) {
|
|
35
35
|
const options = normalizeAuthorizationOptions(input);
|
|
36
36
|
const vercelConnect = { connector: options.connector };
|
|
37
|
+
const evict = makeEvict(options);
|
|
37
38
|
if (options.principalType === 'app') {
|
|
38
|
-
return { ...buildNonInteractiveDefinition(options), vercelConnect };
|
|
39
|
+
return { ...buildNonInteractiveDefinition(options), vercelConnect, evict };
|
|
39
40
|
}
|
|
40
|
-
return { ...buildInteractiveDefinition(options), vercelConnect };
|
|
41
|
+
return { ...buildInteractiveDefinition(options), vercelConnect, evict };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Builds the {@link EveConnectAuthorizationDefinition.evict} callback for
|
|
45
|
+
* a connector. Resolves the same token params {@link getToken} uses for
|
|
46
|
+
* `principal`, then drops exactly that cache entry — leaving every other
|
|
47
|
+
* principal's cached token intact.
|
|
48
|
+
*
|
|
49
|
+
* When called with `revoke: true` it instead tears the grant down at
|
|
50
|
+
* Vercel Connect via {@link revokeToken} (best-effort, falling back to a
|
|
51
|
+
* local cache drop if the revoke request fails).
|
|
52
|
+
*/
|
|
53
|
+
function makeEvict(options) {
|
|
54
|
+
return async ({ principal, revoke }) => {
|
|
55
|
+
const params = await buildTokenParams(options, principal);
|
|
56
|
+
if (revoke) {
|
|
57
|
+
try {
|
|
58
|
+
// Destructive: tears down the grant at Vercel Connect (refresh
|
|
59
|
+
// token included) and clears the in-process cache. Best-effort —
|
|
60
|
+
// a failed or duplicate revoke must not mask the auth error that
|
|
61
|
+
// triggered eviction.
|
|
62
|
+
await revokeToken(options.connector, {
|
|
63
|
+
subject: params.subject,
|
|
64
|
+
installationId: params.installationId,
|
|
65
|
+
}, options.connectOptions);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Fall through to the local cache drop so the rejected bearer is
|
|
70
|
+
// gone even when the server-side revoke failed.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
deleteTokenCacheEntry(options.connector, params);
|
|
74
|
+
};
|
|
41
75
|
}
|
|
42
76
|
function normalizeAuthorizationOptions(input) {
|
|
43
77
|
if (typeof input === 'string') {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { 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 ConnectTokenParams, type ConnectTokenResponse, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from './token.js';
|
|
2
2
|
export { startAuthorization, type ConnectAuthorizationOptions, type ConnectAuthorizationResponse, } from './authorization.js';
|
|
3
3
|
export type { ConnectAuthorizationDetail } from './authorization-details.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, } from './token.js';
|
|
1
|
+
export { deleteTokenCacheEntry, getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, } from './token.js';
|
|
2
2
|
export { startAuthorization, } from './authorization.js';
|
package/dist/token.d.ts
CHANGED
|
@@ -99,4 +99,22 @@ export declare function revokeToken(connector: string, params: {
|
|
|
99
99
|
subject: ConnectTokenSubject;
|
|
100
100
|
installationId?: string;
|
|
101
101
|
}, options?: ConnectOptions): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Remove a single cached token entry for `(connector, params)` from the
|
|
104
|
+
* in-process cache.
|
|
105
|
+
*
|
|
106
|
+
* Targeted counterpart to {@link revokeToken}'s `cache.clear()`: it drops
|
|
107
|
+
* exactly the entry {@link getTokenResponse} would serve for these
|
|
108
|
+
* arguments, leaving every other connector/principal untouched. Use it
|
|
109
|
+
* when a credential is known to be bad (the resource server rejected the
|
|
110
|
+
* bearer with a `401`) so the next {@link getTokenResponse} re-fetches
|
|
111
|
+
* instead of re-serving the rejected token — without paying for a Connect
|
|
112
|
+
* round trip on every call the way {@link ConnectOptions.forceRefresh}
|
|
113
|
+
* does.
|
|
114
|
+
*
|
|
115
|
+
* The cache key is derived from `connector` plus every field of `params`,
|
|
116
|
+
* so pass the same `params` used for the original {@link getTokenResponse}
|
|
117
|
+
* call. No-op when no matching entry exists.
|
|
118
|
+
*/
|
|
119
|
+
export declare function deleteTokenCacheEntry(connector: string, params: ConnectTokenParams): void;
|
|
102
120
|
export declare function createConnectErrorFromResponse(response: Response, fallbackMessage: string): Promise<ConnectError>;
|
package/dist/token.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function getToken(connector, params, options) {
|
|
|
37
37
|
}
|
|
38
38
|
export async function getTokenResponse(connector, params, options) {
|
|
39
39
|
const bufferMs = params.validityBufferMs ?? DEFAULT_VALIDITY_BUFFER_MS;
|
|
40
|
-
const cacheKey =
|
|
40
|
+
const cacheKey = tokenCacheKey(connector, params);
|
|
41
41
|
if (options?.forceRefresh) {
|
|
42
42
|
cache.delete(cacheKey);
|
|
43
43
|
}
|
|
@@ -90,9 +90,37 @@ export async function revokeToken(connector, params, options) {
|
|
|
90
90
|
}
|
|
91
91
|
cache.clear();
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Remove a single cached token entry for `(connector, params)` from the
|
|
95
|
+
* in-process cache.
|
|
96
|
+
*
|
|
97
|
+
* Targeted counterpart to {@link revokeToken}'s `cache.clear()`: it drops
|
|
98
|
+
* exactly the entry {@link getTokenResponse} would serve for these
|
|
99
|
+
* arguments, leaving every other connector/principal untouched. Use it
|
|
100
|
+
* when a credential is known to be bad (the resource server rejected the
|
|
101
|
+
* bearer with a `401`) so the next {@link getTokenResponse} re-fetches
|
|
102
|
+
* instead of re-serving the rejected token — without paying for a Connect
|
|
103
|
+
* round trip on every call the way {@link ConnectOptions.forceRefresh}
|
|
104
|
+
* does.
|
|
105
|
+
*
|
|
106
|
+
* The cache key is derived from `connector` plus every field of `params`,
|
|
107
|
+
* so pass the same `params` used for the original {@link getTokenResponse}
|
|
108
|
+
* call. No-op when no matching entry exists.
|
|
109
|
+
*/
|
|
110
|
+
export function deleteTokenCacheEntry(connector, params) {
|
|
111
|
+
cache.delete(tokenCacheKey(connector, params));
|
|
112
|
+
}
|
|
93
113
|
const DEFAULT_VALIDITY_BUFFER_MS = 30_000;
|
|
94
114
|
const MAX_CACHE_SIZE = 100;
|
|
95
115
|
const cache = new Map();
|
|
116
|
+
/**
|
|
117
|
+
* Cache key for a `(connector, params)` pair. Stable across calls with
|
|
118
|
+
* equal arguments so {@link getTokenResponse} and
|
|
119
|
+
* {@link deleteTokenCacheEntry} address the same entry.
|
|
120
|
+
*/
|
|
121
|
+
function tokenCacheKey(connector, params) {
|
|
122
|
+
return JSON.stringify({ connector, ...params });
|
|
123
|
+
}
|
|
96
124
|
function evictLru() {
|
|
97
125
|
let oldestKey;
|
|
98
126
|
let oldestTime = Infinity;
|