@chrischall/mcp-utils 0.1.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 +235 -0
- package/dist/auth/index.d.ts +223 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +267 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/config/index.d.ts +86 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +121 -0
- package/dist/config/index.js.map +1 -0
- package/dist/errors/index.d.ts +90 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +157 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/fetchproxy/index.d.ts +156 -0
- package/dist/fetchproxy/index.d.ts.map +1 -0
- package/dist/fetchproxy/index.js +197 -0
- package/dist/fetchproxy/index.js.map +1 -0
- package/dist/html/index.d.ts +142 -0
- package/dist/html/index.d.ts.map +1 -0
- package/dist/html/index.js +321 -0
- package/dist/html/index.js.map +1 -0
- package/dist/http/index.d.ts +202 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +341 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/response/index.d.ts +22 -0
- package/dist/response/index.d.ts.map +1 -0
- package/dist/response/index.js +61 -0
- package/dist/response/index.js.map +1 -0
- package/dist/server/index.d.ts +109 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +95 -0
- package/dist/server/index.js.map +1 -0
- package/dist/session/index.d.ts +233 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +404 -0
- package/dist/session/index.js.map +1 -0
- package/dist/test/index.d.ts +124 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +181 -0
- package/dist/test/index.js.map +1 -0
- package/dist/zod/index.d.ts +130 -0
- package/dist/zod/index.d.ts.map +1 -0
- package/dist/zod/index.js +184 -0
- package/dist/zod/index.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session scaffolding for the MCP fleet — three related-but-distinct surfaces
|
|
3
|
+
* consolidated behind one subpath (`@chrischall/mcp-utils/session`):
|
|
4
|
+
*
|
|
5
|
+
* 1. {@link SessionRegistry} — an *ephemeral, in-memory* registry of signed-in
|
|
6
|
+
* sessions keyed by account identity, plus {@link registerSessionTools} for
|
|
7
|
+
* the structurally-identical `*_register_session` / `*_set_active_session` /
|
|
8
|
+
* `*_get_session_context` MCP tool trio. Used by the realty MCPs
|
|
9
|
+
* (zillow/redfin/compass/homes/onehome).
|
|
10
|
+
*
|
|
11
|
+
* 2. {@link SessionStore} — a *disk-persisted* store with hardened file perms
|
|
12
|
+
* (0600 file / 0700 dir), normalized keys, and a most-recently-used "active"
|
|
13
|
+
* pointer. Used by ofw/creditkarma/honeybook.
|
|
14
|
+
*
|
|
15
|
+
* 3. {@link TokenManager} — a bearer-token lifecycle manager: proactive refresh
|
|
16
|
+
* inside a 5-minute skew window, reactive 401-replay, and a single-flight
|
|
17
|
+
* semaphore so concurrent callers coalesce into ONE refresh. Used by
|
|
18
|
+
* skylight/canvas/creditkarma/honeybook/zola.
|
|
19
|
+
*
|
|
20
|
+
* Security-sensitive by design (file perms + token-refresh races), so this is
|
|
21
|
+
* the one audited implementation the fleet shares.
|
|
22
|
+
*/
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, } from 'node:fs';
|
|
25
|
+
import { dirname } from 'node:path';
|
|
26
|
+
import { randomBytes } from 'node:crypto';
|
|
27
|
+
import { textResult } from '../response/index.js';
|
|
28
|
+
/** Generate a short, collision-resistant label id. */
|
|
29
|
+
function makeSessionId() {
|
|
30
|
+
return Date.now().toString(36) + randomBytes(6).toString('hex');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Per-process, in-memory registry of signed-in sessions. The constructor takes
|
|
34
|
+
* no arguments, so it's safe to instantiate per test. Sessions are keyed by
|
|
35
|
+
* `session_id` but de-duplicated by `account_identity`.
|
|
36
|
+
*/
|
|
37
|
+
export class SessionRegistry {
|
|
38
|
+
sessions = new Map();
|
|
39
|
+
activeId = null;
|
|
40
|
+
/**
|
|
41
|
+
* Register a new session, or refresh the existing one keyed by
|
|
42
|
+
* `account_identity`. The first session registered becomes the active one.
|
|
43
|
+
*/
|
|
44
|
+
register(args) {
|
|
45
|
+
const identity = args.account_identity.trim();
|
|
46
|
+
if (identity.length === 0) {
|
|
47
|
+
throw new Error('register: account_identity must be non-empty.');
|
|
48
|
+
}
|
|
49
|
+
for (const existing of this.sessions.values()) {
|
|
50
|
+
if (existing.account_identity === identity) {
|
|
51
|
+
if (args.auth_mode !== undefined)
|
|
52
|
+
existing.auth_mode = args.auth_mode;
|
|
53
|
+
existing.auth_ready = true;
|
|
54
|
+
existing.registered_at = new Date().toISOString();
|
|
55
|
+
if (args.auth_expires_at !== undefined) {
|
|
56
|
+
existing.auth_expires_at = args.auth_expires_at;
|
|
57
|
+
}
|
|
58
|
+
return { ...existing };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const sess = {
|
|
62
|
+
session_id: makeSessionId(),
|
|
63
|
+
account_identity: identity,
|
|
64
|
+
auth_mode: args.auth_mode ?? 'browser_session',
|
|
65
|
+
auth_ready: true,
|
|
66
|
+
registered_at: new Date().toISOString(),
|
|
67
|
+
auth_expires_at: args.auth_expires_at ?? null,
|
|
68
|
+
};
|
|
69
|
+
this.sessions.set(sess.session_id, sess);
|
|
70
|
+
if (this.activeId === null)
|
|
71
|
+
this.activeId = sess.session_id;
|
|
72
|
+
return { ...sess };
|
|
73
|
+
}
|
|
74
|
+
/** Switch the active session. Returns `false` if the id is unknown. */
|
|
75
|
+
setActive(sessionId) {
|
|
76
|
+
if (!this.sessions.has(sessionId))
|
|
77
|
+
return false;
|
|
78
|
+
this.activeId = sessionId;
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
/** Look up a session by id (returns a copy, or `null`). */
|
|
82
|
+
get(sessionId) {
|
|
83
|
+
const s = this.sessions.get(sessionId);
|
|
84
|
+
return s ? { ...s } : null;
|
|
85
|
+
}
|
|
86
|
+
/** Snapshot of the full registry plus the active id. */
|
|
87
|
+
getContext() {
|
|
88
|
+
return {
|
|
89
|
+
active_session_id: this.activeId,
|
|
90
|
+
sessions: Array.from(this.sessions.values()).map((s) => ({ ...s })),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** The active session id, if any. */
|
|
94
|
+
activeSessionId() {
|
|
95
|
+
return this.activeId;
|
|
96
|
+
}
|
|
97
|
+
/** Number of registered sessions. */
|
|
98
|
+
size() {
|
|
99
|
+
return this.sessions.size;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve which session a tool call should route through:
|
|
103
|
+
* - `requested` set and known → it;
|
|
104
|
+
* - `requested` set but unknown → throws;
|
|
105
|
+
* - `requested` undefined → the active session;
|
|
106
|
+
* - no sessions registered → `null` (caller uses the default transport).
|
|
107
|
+
*/
|
|
108
|
+
resolve(requested) {
|
|
109
|
+
if (requested !== undefined) {
|
|
110
|
+
if (!this.sessions.has(requested)) {
|
|
111
|
+
throw new Error(`Unknown session_id "${requested}". Call the get_session_context tool to see registered sessions.`);
|
|
112
|
+
}
|
|
113
|
+
return requested;
|
|
114
|
+
}
|
|
115
|
+
return this.activeId;
|
|
116
|
+
}
|
|
117
|
+
/** Clear all state. Test helper. */
|
|
118
|
+
reset() {
|
|
119
|
+
this.sessions.clear();
|
|
120
|
+
this.activeId = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Construct a fresh in-memory {@link SessionRegistry}. */
|
|
124
|
+
export function createSessionRegistry() {
|
|
125
|
+
return new SessionRegistry();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Register the structurally-identical session tool trio against `server`,
|
|
129
|
+
* backed by `registry`. Replaces every MCP's hand-rolled `src/tools/sessions.ts`.
|
|
130
|
+
*/
|
|
131
|
+
export function registerSessionTools(server, registry, opts) {
|
|
132
|
+
const { prefix } = opts;
|
|
133
|
+
const label = opts.serviceLabel ?? prefix;
|
|
134
|
+
const ctxTool = `${prefix}_get_session_context`;
|
|
135
|
+
server.registerTool(`${prefix}_register_session`, {
|
|
136
|
+
title: `Register a signed-in ${label} session`,
|
|
137
|
+
description: `Register (or refresh) an authenticated ${label} session keyed by signed-in account identity. ` +
|
|
138
|
+
'Re-registering the same `account_identity` updates the existing session rather than creating a duplicate. ' +
|
|
139
|
+
'Returns the `session_id` to use when routing per-tool calls. ' +
|
|
140
|
+
'The first registered session becomes the default `active_session_id`.',
|
|
141
|
+
annotations: {
|
|
142
|
+
title: `Register a signed-in ${label} session`,
|
|
143
|
+
readOnlyHint: false,
|
|
144
|
+
idempotentHint: true,
|
|
145
|
+
openWorldHint: false,
|
|
146
|
+
},
|
|
147
|
+
inputSchema: {
|
|
148
|
+
account_identity: z
|
|
149
|
+
.string()
|
|
150
|
+
.min(1)
|
|
151
|
+
.describe('Caller-supplied identifier for the signed-in account (typically the saved-account email).'),
|
|
152
|
+
auth_expires_at: z
|
|
153
|
+
.string()
|
|
154
|
+
.optional()
|
|
155
|
+
.describe('Optional ISO timestamp at which the session expires.'),
|
|
156
|
+
},
|
|
157
|
+
}, async ({ account_identity, auth_expires_at }) => {
|
|
158
|
+
// Pass `auth_expires_at` through as-is: `undefined` means "keep existing",
|
|
159
|
+
// never coerce with `?? null` (that would wipe a prior expiry).
|
|
160
|
+
const session = registry.register({ account_identity, auth_expires_at });
|
|
161
|
+
return textResult({ session, active_session_id: registry.activeSessionId() });
|
|
162
|
+
});
|
|
163
|
+
server.registerTool(`${prefix}_set_active_session`, {
|
|
164
|
+
title: `Set the active ${label} session`,
|
|
165
|
+
description: 'Switch which registered session subsequent tool calls route through by default. ' +
|
|
166
|
+
`Pass a \`session_id\` previously returned by \`${prefix}_register_session\`. ` +
|
|
167
|
+
'Tools that accept an explicit `session_id` parameter override this default per-call.',
|
|
168
|
+
annotations: {
|
|
169
|
+
title: `Set the active ${label} session`,
|
|
170
|
+
readOnlyHint: false,
|
|
171
|
+
idempotentHint: true,
|
|
172
|
+
openWorldHint: false,
|
|
173
|
+
},
|
|
174
|
+
inputSchema: {
|
|
175
|
+
session_id: z.string().min(1).describe('Session id to make active.'),
|
|
176
|
+
},
|
|
177
|
+
}, async ({ session_id }) => {
|
|
178
|
+
if (!registry.setActive(session_id)) {
|
|
179
|
+
throw new Error(`Unknown session_id "${session_id}". Call ${ctxTool} to see registered sessions.`);
|
|
180
|
+
}
|
|
181
|
+
return textResult({
|
|
182
|
+
active_session_id: registry.activeSessionId(),
|
|
183
|
+
context: registry.getContext(),
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
server.registerTool(ctxTool, {
|
|
187
|
+
title: `List all registered ${label} sessions`,
|
|
188
|
+
description: 'Return the full set of registered sessions plus the current `active_session_id`. ' +
|
|
189
|
+
'When no sessions are registered, `sessions` is empty and `active_session_id` is null.',
|
|
190
|
+
annotations: {
|
|
191
|
+
title: `List all registered ${label} sessions`,
|
|
192
|
+
readOnlyHint: true,
|
|
193
|
+
idempotentHint: true,
|
|
194
|
+
openWorldHint: false,
|
|
195
|
+
},
|
|
196
|
+
inputSchema: {},
|
|
197
|
+
}, async () => textResult(registry.getContext()));
|
|
198
|
+
}
|
|
199
|
+
// ===========================================================================
|
|
200
|
+
// 2. Disk-persisted SessionStore
|
|
201
|
+
// ===========================================================================
|
|
202
|
+
/**
|
|
203
|
+
* Given a full URL or just an origin, return the origin without a trailing
|
|
204
|
+
* slash. Falls back to trimming a trailing slash for non-URL input.
|
|
205
|
+
*/
|
|
206
|
+
export function normalizeOrigin(input) {
|
|
207
|
+
try {
|
|
208
|
+
return new URL(input).origin.replace(/\/$/, '');
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return input.replace(/\/$/, '');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Disk-persisted session store with hardened file permissions. Records are kept
|
|
216
|
+
* in a `Map` keyed by their normalized key, persisted as a JSON array (insertion
|
|
217
|
+
* order). The file is written with mode `0600` and its directory `0700` so other
|
|
218
|
+
* users on the machine cannot read captured credentials.
|
|
219
|
+
*
|
|
220
|
+
* `add` marks the record most-recently-used; {@link SessionStore.getActiveSession}
|
|
221
|
+
* (and `get()` with no argument) returns it, so a tool call that omits an explicit
|
|
222
|
+
* key picks up the latest session automatically.
|
|
223
|
+
*/
|
|
224
|
+
export class SessionStore {
|
|
225
|
+
sessions = new Map();
|
|
226
|
+
mostRecentKey = null;
|
|
227
|
+
filePath;
|
|
228
|
+
keyOf;
|
|
229
|
+
normalizeKey;
|
|
230
|
+
constructor(opts) {
|
|
231
|
+
this.filePath = opts.filePath;
|
|
232
|
+
this.keyOf = opts.keyOf;
|
|
233
|
+
this.normalizeKey = opts.normalizeKey ?? normalizeOrigin;
|
|
234
|
+
this.loadFromDisk();
|
|
235
|
+
}
|
|
236
|
+
loadFromDisk() {
|
|
237
|
+
if (!existsSync(this.filePath))
|
|
238
|
+
return;
|
|
239
|
+
try {
|
|
240
|
+
this.sessions = this.deserialize(readFileSync(this.filePath, 'utf8'));
|
|
241
|
+
const keys = Array.from(this.sessions.keys());
|
|
242
|
+
this.mostRecentKey = keys[keys.length - 1] ?? null;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
this.sessions = new Map();
|
|
246
|
+
this.mostRecentKey = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Serialize the store to its on-disk JSON form (array, insertion order). */
|
|
250
|
+
serialize() {
|
|
251
|
+
return JSON.stringify(Array.from(this.sessions.values()), null, 2);
|
|
252
|
+
}
|
|
253
|
+
/** Parse on-disk JSON back into a keyed `Map`; empty map on invalid input. */
|
|
254
|
+
deserialize(body) {
|
|
255
|
+
const map = new Map();
|
|
256
|
+
const arr = JSON.parse(body);
|
|
257
|
+
if (!Array.isArray(arr))
|
|
258
|
+
return map;
|
|
259
|
+
for (const raw of arr) {
|
|
260
|
+
if (raw && typeof raw === 'object') {
|
|
261
|
+
const s = raw;
|
|
262
|
+
const key = this.normalizeKey(this.keyOf(s));
|
|
263
|
+
map.set(key, s);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return map;
|
|
267
|
+
}
|
|
268
|
+
saveToDisk() {
|
|
269
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
270
|
+
writeFileSync(this.filePath, this.serialize(), { mode: 0o600 });
|
|
271
|
+
// Best-effort tighten existing file + dir (writeFileSync respects umask on
|
|
272
|
+
// pre-existing files, so re-assert 0600/0700 explicitly).
|
|
273
|
+
try {
|
|
274
|
+
chmodSync(this.filePath, 0o600);
|
|
275
|
+
chmodSync(dirname(this.filePath), 0o700);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
/* best-effort */
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/** Insert or replace a record, normalizing its key and marking it active. */
|
|
282
|
+
add(session) {
|
|
283
|
+
const key = this.normalizeKey(this.keyOf(session));
|
|
284
|
+
this.sessions.set(key, session);
|
|
285
|
+
this.mostRecentKey = key;
|
|
286
|
+
this.saveToDisk();
|
|
287
|
+
}
|
|
288
|
+
/** Look up by key; with no key, returns the active (most-recent) session. */
|
|
289
|
+
get(key) {
|
|
290
|
+
if (key !== undefined)
|
|
291
|
+
return this.sessions.get(this.normalizeKey(key)) ?? null;
|
|
292
|
+
if (this.mostRecentKey !== null)
|
|
293
|
+
return this.sessions.get(this.mostRecentKey) ?? null;
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
/** The most-recently-added session, or `null`. */
|
|
297
|
+
getActiveSession() {
|
|
298
|
+
return this.get();
|
|
299
|
+
}
|
|
300
|
+
/** All sessions in insertion order. */
|
|
301
|
+
list() {
|
|
302
|
+
return Array.from(this.sessions.values());
|
|
303
|
+
}
|
|
304
|
+
/** Remove a session; fixes up the active pointer. Returns whether it existed. */
|
|
305
|
+
remove(key) {
|
|
306
|
+
const normalized = this.normalizeKey(key);
|
|
307
|
+
const had = this.sessions.delete(normalized);
|
|
308
|
+
if (had) {
|
|
309
|
+
if (this.mostRecentKey === normalized) {
|
|
310
|
+
const keys = Array.from(this.sessions.keys());
|
|
311
|
+
this.mostRecentKey = keys[keys.length - 1] ?? null;
|
|
312
|
+
}
|
|
313
|
+
this.saveToDisk();
|
|
314
|
+
}
|
|
315
|
+
return had;
|
|
316
|
+
}
|
|
317
|
+
/** Clear in-memory state without touching disk. Test helper. */
|
|
318
|
+
resetForTest() {
|
|
319
|
+
this.sessions.clear();
|
|
320
|
+
this.mostRecentKey = null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// ===========================================================================
|
|
324
|
+
// 3. TokenManager — bearer lifecycle (skew, proactive + reactive, race-safe)
|
|
325
|
+
// ===========================================================================
|
|
326
|
+
/** Refresh proactively this many ms before the access token expires. */
|
|
327
|
+
export const TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1000;
|
|
328
|
+
/**
|
|
329
|
+
* Manages a bearer access token's lifecycle:
|
|
330
|
+
*
|
|
331
|
+
* - **Proactive:** {@link TokenManager.getAccessToken} refreshes when the token
|
|
332
|
+
* is within `skewMs` (default 5 min) of expiry, returning a still-valid token.
|
|
333
|
+
* - **Reactive:** {@link TokenManager.withAuth} runs a request, and on a `401`
|
|
334
|
+
* refreshes once and replays exactly once (no infinite loop).
|
|
335
|
+
* - **Race-safe:** concurrent refreshes coalesce onto a single in-flight promise
|
|
336
|
+
* (semaphore), so a burst of callers triggers exactly ONE token exchange. The
|
|
337
|
+
* in-flight promise is cleared on settle so a later refresh can run again.
|
|
338
|
+
*/
|
|
339
|
+
export class TokenManager {
|
|
340
|
+
accessToken;
|
|
341
|
+
refreshToken;
|
|
342
|
+
expiresAt;
|
|
343
|
+
refreshFn;
|
|
344
|
+
skewMs;
|
|
345
|
+
inFlight;
|
|
346
|
+
constructor(opts) {
|
|
347
|
+
this.accessToken = opts.initial.accessToken;
|
|
348
|
+
this.refreshToken = opts.initial.refreshToken;
|
|
349
|
+
this.expiresAt = opts.initial.expiresAt;
|
|
350
|
+
this.refreshFn = opts.refresh;
|
|
351
|
+
this.skewMs = opts.skewMs ?? TOKEN_REFRESH_SKEW_MS;
|
|
352
|
+
}
|
|
353
|
+
/** Whether the token is within the skew window of (or past) expiry. */
|
|
354
|
+
needsRefresh() {
|
|
355
|
+
return Date.now() >= this.expiresAt - this.skewMs;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Single-flight refresh. Concurrent callers share one in-flight promise; it is
|
|
359
|
+
* cleared on settle (success or failure) so a subsequent refresh can proceed.
|
|
360
|
+
*/
|
|
361
|
+
refreshNow() {
|
|
362
|
+
if (!this.inFlight) {
|
|
363
|
+
const rt = this.refreshToken;
|
|
364
|
+
if (rt === undefined) {
|
|
365
|
+
return Promise.reject(new Error('TokenManager: cannot refresh — no refresh token is available.'));
|
|
366
|
+
}
|
|
367
|
+
this.inFlight = (async () => {
|
|
368
|
+
const tok = await this.refreshFn(rt);
|
|
369
|
+
this.accessToken = tok.accessToken;
|
|
370
|
+
if (tok.refreshToken !== undefined && tok.refreshToken !== '') {
|
|
371
|
+
this.refreshToken = tok.refreshToken;
|
|
372
|
+
}
|
|
373
|
+
this.expiresAt = tok.expiresAt;
|
|
374
|
+
})().finally(() => {
|
|
375
|
+
this.inFlight = undefined;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return this.inFlight;
|
|
379
|
+
}
|
|
380
|
+
/** Get a valid access token, refreshing proactively inside the skew window. */
|
|
381
|
+
async getAccessToken() {
|
|
382
|
+
if (this.needsRefresh())
|
|
383
|
+
await this.refreshNow();
|
|
384
|
+
return this.accessToken;
|
|
385
|
+
}
|
|
386
|
+
/** Current absolute expiry (epoch ms). */
|
|
387
|
+
getExpiresAt() {
|
|
388
|
+
return this.expiresAt;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Run an authenticated request with reactive 401-replay. `call` receives a
|
|
392
|
+
* valid access token and returns a `Response`. On `401`, the token is
|
|
393
|
+
* refreshed once and `call` is invoked again exactly once.
|
|
394
|
+
*/
|
|
395
|
+
async withAuth(call) {
|
|
396
|
+
let res = await call(await this.getAccessToken());
|
|
397
|
+
if (res.status === 401) {
|
|
398
|
+
await this.refreshNow();
|
|
399
|
+
res = await call(this.accessToken);
|
|
400
|
+
}
|
|
401
|
+
return res;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,EACT,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AA8ClD,sDAAsD;AACtD,SAAS,aAAa;IACpB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,eAAe;IACT,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IACpD,QAAQ,GAAkB,IAAI,CAAC;IAEvC;;;OAGG;IACH,QAAQ,CAAC,IAAkB;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC9C,IAAI,QAAQ,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;gBAC3C,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;oBAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;gBACtE,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC;gBAC3B,QAAQ,CAAC,aAAa,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;gBAClD,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;oBACvC,QAAQ,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;gBAClD,CAAC;gBACD,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;YACzB,CAAC;QACH,CAAC;QACD,MAAM,IAAI,GAAiB;YACzB,UAAU,EAAE,aAAa,EAAE;YAC3B,gBAAgB,EAAE,QAAQ;YAC1B,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,iBAAiB;YAC9C,UAAU,EAAE,IAAI;YAChB,aAAa,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACvC,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,IAAI;SAC9C,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;YAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC;QAC5D,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;IACrB,CAAC;IAED,uEAAuE;IACvE,SAAS,CAAC,SAAiB;QACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAChD,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2DAA2D;IAC3D,GAAG,CAAC,SAAiB;QACnB,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7B,CAAC;IAED,wDAAwD;IACxD,UAAU;QACR,OAAO;YACL,iBAAiB,EAAE,IAAI,CAAC,QAAQ;YAChC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;SACpE,CAAC;IACJ,CAAC;IAED,qCAAqC;IACrC,eAAe;QACb,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,qCAAqC;IACrC,IAAI;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,SAA6B;QACnC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CACb,uBAAuB,SAAS,kEAAkE,CACnG,CAAC;YACJ,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,oCAAoC;IACpC,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;IACvB,CAAC;CACF;AAED,2DAA2D;AAC3D,MAAM,UAAU,qBAAqB;IACnC,OAAO,IAAI,eAAe,EAAE,CAAC;AAC/B,CAAC;AAaD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAiB,EACjB,QAAyB,EACzB,IAAiC;IAEjC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC;IAC1C,MAAM,OAAO,GAAG,GAAG,MAAM,sBAAsB,CAAC;IAEhD,MAAM,CAAC,YAAY,CACjB,GAAG,MAAM,mBAAmB,EAC5B;QACE,KAAK,EAAE,wBAAwB,KAAK,UAAU;QAC9C,WAAW,EACT,0CAA0C,KAAK,gDAAgD;YAC/F,4GAA4G;YAC5G,+DAA+D;YAC/D,uEAAuE;QACzE,WAAW,EAAE;YACX,KAAK,EAAE,wBAAwB,KAAK,UAAU;YAC9C,YAAY,EAAE,KAAK;YACnB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;QACD,WAAW,EAAE;YACX,gBAAgB,EAAE,CAAC;iBAChB,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CAAC,2FAA2F,CAAC;YACxG,eAAe,EAAE,CAAC;iBACf,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,sDAAsD,CAAC;SACpE;KACF,EACD,KAAK,EAAE,EAAE,gBAAgB,EAAE,eAAe,EAAE,EAAE,EAAE;QAC9C,2EAA2E;QAC3E,gEAAgE;QAChE,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC,CAAC;QACzE,OAAO,UAAU,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IAChF,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,GAAG,MAAM,qBAAqB,EAC9B;QACE,KAAK,EAAE,kBAAkB,KAAK,UAAU;QACxC,WAAW,EACT,kFAAkF;YAClF,kDAAkD,MAAM,uBAAuB;YAC/E,sFAAsF;QACxF,WAAW,EAAE;YACX,KAAK,EAAE,kBAAkB,KAAK,UAAU;YACxC,YAAY,EAAE,KAAK;YACnB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;QACD,WAAW,EAAE;YACX,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,4BAA4B,CAAC;SACrE;KACF,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,uBAAuB,UAAU,WAAW,OAAO,8BAA8B,CAAC,CAAC;QACrG,CAAC;QACD,OAAO,UAAU,CAAC;YAChB,iBAAiB,EAAE,QAAQ,CAAC,eAAe,EAAE;YAC7C,OAAO,EAAE,QAAQ,CAAC,UAAU,EAAE;SAC/B,CAAC,CAAC;IACL,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,OAAO,EACP;QACE,KAAK,EAAE,uBAAuB,KAAK,WAAW;QAC9C,WAAW,EACT,mFAAmF;YACnF,uFAAuF;QACzF,WAAW,EAAE;YACX,KAAK,EAAE,uBAAuB,KAAK,WAAW;YAC9C,YAAY,EAAE,IAAI;YAClB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;QACD,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAC9C,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,iCAAiC;AACjC,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAeD;;;;;;;;;GASG;AACH,MAAM,OAAO,YAAY;IACf,QAAQ,GAAG,IAAI,GAAG,EAAa,CAAC;IAChC,aAAa,GAAkB,IAAI,CAAC;IAC3B,QAAQ,CAAS;IACjB,KAAK,CAAyB;IAC9B,YAAY,CAA0B;IAEvD,YAAY,IAA4B;QACtC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,eAAe,CAAC;QACzD,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO;QACvC,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;YACtE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;YAC1B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,8EAA8E;IACtE,WAAW,CAAC,IAAY;QAC9B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAa,CAAC;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;QACpC,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;YACtB,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACnC,MAAM,CAAC,GAAG,GAAQ,CAAC;gBACnB,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,UAAU;QAChB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAChE,2EAA2E;QAC3E,0DAA0D;QAC1D,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAChC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;QACnB,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,GAAG,CAAC,OAAU;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC;QACzB,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED,6EAA6E;IAC7E,GAAG,CAAC,GAAY;QACd,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;QAChF,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kDAAkD;IAClD,gBAAgB;QACd,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,uCAAuC;IACvC,IAAI;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,iFAAiF;IACjF,MAAM,CAAC,GAAW;QAChB,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,IAAI,GAAG,EAAE,CAAC;YACR,IAAI,IAAI,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC9C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;YACrD,CAAC;YACD,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,gEAAgE;IAChE,YAAY;QACV,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAC5B,CAAC;CACF;AAED,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAE9E,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAmCnD;;;;;;;;;;GAUG;AACH,MAAM,OAAO,YAAY;IACf,WAAW,CAAS;IACpB,YAAY,CAAqB;IACjC,SAAS,CAAS;IACT,SAAS,CAAqD;IAC9D,MAAM,CAAS;IACxB,QAAQ,CAA4B;IAE5C,YAAY,IAAyB;QACnC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;QAC5C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;QAC9C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,qBAAqB,CAAC;IACrD,CAAC;IAED,uEAAuE;IAC/D,YAAY;QAClB,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;IACpD,CAAC;IAED;;;OAGG;IACH,UAAU;QACR,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YAC7B,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;gBACrB,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAC3E,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,QAAQ,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC1B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBACrC,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;gBACnC,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS,IAAI,GAAG,CAAC,YAAY,KAAK,EAAE,EAAE,CAAC;oBAC9D,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,YAAY,CAAC;gBACvC,CAAC;gBACD,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;YACjC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;gBAChB,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;YAC5B,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,cAAc;QAClB,IAAI,IAAI,CAAC,YAAY,EAAE;YAAE,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,0CAA0C;IAC1C,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,IAAgD;QAC7D,IAAI,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QAClD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YACxB,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import type { Mock } from 'vitest';
|
|
5
|
+
/** A function that registers one or more tools onto a fresh `McpServer`. */
|
|
6
|
+
export type RegisterFn = (server: McpServer) => void | Promise<void>;
|
|
7
|
+
/** The connected test harness returned by {@link createTestHarness}. */
|
|
8
|
+
export interface TestHarness {
|
|
9
|
+
/** The MCP client side of the in-memory transport pair. */
|
|
10
|
+
client: Client;
|
|
11
|
+
/** The MCP server the `registerFn` was applied to. */
|
|
12
|
+
server: McpServer;
|
|
13
|
+
/** Call a registered tool by name; arguments default to `{}`. */
|
|
14
|
+
callTool: (name: string, args?: Record<string, unknown>) => Promise<CallToolResult>;
|
|
15
|
+
/** List the tools the server advertises (name only). */
|
|
16
|
+
listTools: () => Promise<{
|
|
17
|
+
name: string;
|
|
18
|
+
}[]>;
|
|
19
|
+
/** Tear down both ends of the transport. Safe to call more than once. */
|
|
20
|
+
close: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create a connected `McpServer` + `Client` pair wired over
|
|
24
|
+
* `InMemoryTransport`. The byte-identical helper every MCP's `tests/helpers.ts`
|
|
25
|
+
* defines — register your tools, then drive them through the real client RPC
|
|
26
|
+
* path (schema validation, content envelopes, isError, and all).
|
|
27
|
+
*/
|
|
28
|
+
export declare function createTestHarness(registerFn: RegisterFn): Promise<TestHarness>;
|
|
29
|
+
/**
|
|
30
|
+
* Parse the JSON body out of a tool's `CallToolResult`. Fleet tools return a
|
|
31
|
+
* single text block of `JSON.stringify(data, null, 2)`; this is the inverse.
|
|
32
|
+
* Throws a contextual error (rather than a bare `TypeError`/`SyntaxError`) when
|
|
33
|
+
* the result is empty, non-text, or not valid JSON — those are the test
|
|
34
|
+
* failures you actually want to read.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseToolResult<T = unknown>(result: CallToolResult): T;
|
|
37
|
+
/** Options for {@link versionSyncTest}. */
|
|
38
|
+
export interface VersionSyncOptions {
|
|
39
|
+
/** Directory to walk for `.ts` files (typically `<repo>/src`). */
|
|
40
|
+
srcDir: string;
|
|
41
|
+
/** Path to the `package.json` whose `version` is the source of truth. */
|
|
42
|
+
pkgPath: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The release-please drift guard. Walks `srcDir` for every line carrying an
|
|
46
|
+
* `x-release-please-version` annotation and asserts the version literal on that
|
|
47
|
+
* line matches `package.json#version`.
|
|
48
|
+
*
|
|
49
|
+
* Why this exists: a recurring footgun where a `VERSION` constant (the MCP's
|
|
50
|
+
* self-reported version + fetchproxy bridge identity) silently drifts from
|
|
51
|
+
* `package.json` because release-please's `extra-files` registration lacks the
|
|
52
|
+
* marker. resy-mcp v0.2.0 and opentable-mcp shipped this bug repeatedly.
|
|
53
|
+
*
|
|
54
|
+
* Returns the list of mismatch descriptions (`file:line → found (expected X)`).
|
|
55
|
+
* An empty array means in sync — callers assert `expect(result).toEqual([])`.
|
|
56
|
+
* Marker lines with no version literal (e.g. a docstring describing the
|
|
57
|
+
* convention) are intentionally skipped, so the test never trips on itself.
|
|
58
|
+
*/
|
|
59
|
+
export declare function versionSyncTest({ srcDir, pkgPath }: VersionSyncOptions): string[];
|
|
60
|
+
/**
|
|
61
|
+
* The shape `@fetchproxy/bootstrap`'s `bootstrap()` resolves to: extracted
|
|
62
|
+
* browser state keyed by name. Mirrored here (rather than imported) because the
|
|
63
|
+
* bootstrap package is an optional, browser-side dep that consumers mock at the
|
|
64
|
+
* module boundary — the harness must not pull it.
|
|
65
|
+
*/
|
|
66
|
+
export interface BootstrapResult {
|
|
67
|
+
/** Cookie name → value. */
|
|
68
|
+
cookies: Record<string, string>;
|
|
69
|
+
/** `localStorage` key → value. */
|
|
70
|
+
localStorage: Record<string, string>;
|
|
71
|
+
/** `sessionStorage` key → value. */
|
|
72
|
+
sessionStorage: Record<string, string>;
|
|
73
|
+
/** Request headers captured during the bootstrap navigation. */
|
|
74
|
+
capturedHeaders: Record<string, string>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build a fully-shaped {@link BootstrapResult}, with empty maps as defaults and
|
|
78
|
+
* `overrides` shallow-merged on top. Keeps tests from re-declaring the four
|
|
79
|
+
* empty maps every time they only care about, say, `cookies`.
|
|
80
|
+
*/
|
|
81
|
+
export declare function makeBootstrapResult(overrides?: Partial<BootstrapResult>): BootstrapResult;
|
|
82
|
+
/** Handle returned by {@link mockFetchproxyBootstrap}. */
|
|
83
|
+
export interface FetchproxyBootstrapMock {
|
|
84
|
+
/** The spy standing in for `bootstrap()`. Assert/override per test. */
|
|
85
|
+
bootstrap: Mock<(...args: unknown[]) => Promise<BootstrapResult>>;
|
|
86
|
+
/**
|
|
87
|
+
* A module factory matching `@fetchproxy/bootstrap`'s export surface — pass
|
|
88
|
+
* to `vi.mock('@fetchproxy/bootstrap', mock.module)`.
|
|
89
|
+
*/
|
|
90
|
+
module: () => {
|
|
91
|
+
bootstrap: (...args: unknown[]) => Promise<BootstrapResult>;
|
|
92
|
+
};
|
|
93
|
+
/** Clear recorded calls and restore the default resolved value. */
|
|
94
|
+
reset: () => void;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Mock `@fetchproxy/bootstrap` at the module boundary so tests never open a
|
|
98
|
+
* real WebSocket to a browser bridge. Returns a spy that resolves a default
|
|
99
|
+
* {@link BootstrapResult} (overridable per call via `bootstrap.mockResolvedValue`).
|
|
100
|
+
*
|
|
101
|
+
* Usage:
|
|
102
|
+
* ```ts
|
|
103
|
+
* const fp = mockFetchproxyBootstrap({ cookies: { SID: 'x' } });
|
|
104
|
+
* vi.mock('@fetchproxy/bootstrap', fp.module);
|
|
105
|
+
* // ...import the SUT, then assert on fp.bootstrap
|
|
106
|
+
* ```
|
|
107
|
+
* Because `vi.mock` is hoisted, declare the mock handle and the `vi.mock` call
|
|
108
|
+
* before importing the system under test.
|
|
109
|
+
*/
|
|
110
|
+
export declare function mockFetchproxyBootstrap(defaultResult?: Partial<BootstrapResult>): FetchproxyBootstrapMock;
|
|
111
|
+
/**
|
|
112
|
+
* Spy on an API client's async request methods and (optionally) stub each one's
|
|
113
|
+
* resolved value. Mirrors the `vi.spyOn(client, 'request').mockResolvedValue(...)`
|
|
114
|
+
* boilerplate every tool-level test repeats.
|
|
115
|
+
*
|
|
116
|
+
* For each entry in `returns`: a spy is installed on `client[method]`. If the
|
|
117
|
+
* value is `undefined`, the spy passes through to the real implementation;
|
|
118
|
+
* otherwise it `mockResolvedValue`s the provided value. Remember to
|
|
119
|
+
* `vi.restoreAllMocks()` in `afterEach`.
|
|
120
|
+
*
|
|
121
|
+
* @returns A map of method name → installed spy, for call assertions.
|
|
122
|
+
*/
|
|
123
|
+
export declare function setupClientMocks<C extends object>(client: C, returns: Partial<Record<keyof C & string, unknown>>): Record<string, Mock>;
|
|
124
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAEnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,MAAM,EAAE,SAAS,CAAC;IAClB,iEAAiE;IACjE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACpF,wDAAwD;IACxD,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IAC7C,yEAAyE;IACzE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA0BpF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,CAAC,CAetE;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CACjB;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,GAAG,MAAM,EAAE,CAiBjF;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,kCAAkC;IAClC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,OAAO,CAAC,eAAe,CAAM,GAAG,eAAe,CAQ7F;AAED,0DAA0D;AAC1D,MAAM,WAAW,uBAAuB;IACtC,uEAAuE;IACvE,SAAS,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;IAClE;;;OAGG;IACH,MAAM,EAAE,MAAM;QAAE,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,eAAe,CAAC,CAAA;KAAE,CAAC;IAC9E,mEAAmE;IACnE,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACrC,aAAa,GAAE,OAAO,CAAC,eAAe,CAAM,GAC3C,uBAAuB,CAazB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAC/C,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC,GAClD,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAUtB"}
|