Skip to content

Commit a740d28

Browse files
committed
Fix OAuth redirect test and serialize auth requests
Fixes #2510 by serializing auth requests in StreamableHTTPClientTransport and properly handling 401s without crashing the test runner.
1 parent 1e1392e commit a740d28

1 file changed

Lines changed: 43 additions & 10 deletions

File tree

packages/client/src/client/streamableHttp.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ export class StreamableHTTPClientTransport implements Transport {
323323
private _serverRetryMs?: number; // Server-provided retry delay from SSE retry field
324324
private readonly _reconnectionScheduler?: ReconnectionScheduler;
325325
private _cancelReconnection?: () => void;
326+
private _pendingAuthPromise?: Promise<void>;
327+
private _authReject?: (error: Error) => void;
326328

327329
onclose?: () => void;
328330
onerror?: (error: Error) => void;
@@ -499,6 +501,9 @@ export class StreamableHTTPClientTransport implements Transport {
499501
}
500502

501503
private async _startOrAuthSse(options: StartSSEOptions, isAuthRetry = false, stepUpRetries = 0): Promise<void> {
504+
if (this._pendingAuthPromise) {
505+
await this._pendingAuthPromise;
506+
}
502507
const { resumptionToken, requestSignal } = options;
503508
// Same guard as `_handleSseStream`: a resurrected listen stream (the
504509
// POST-SSE → GET reconnect path threads `requestSignal` through
@@ -867,17 +872,35 @@ export class StreamableHTTPClientTransport implements Transport {
867872
{ fetchFn: this._fetchWithInit, resourceMetadataUrl: this._resourceMetadataUrl }
868873
);
869874

870-
const result = await auth(this._oauthProvider, {
871-
serverUrl: this._url,
872-
authorizationCode,
873-
iss: issParam,
874-
resourceMetadataUrl: this._resourceMetadataUrl,
875-
scope: this._scope,
876-
fetchFn: this._fetchWithInit,
877-
skipIssuerMetadataValidation: this._skipIssuerMetadataValidation
875+
let authResolve: () => void;
876+
this._pendingAuthPromise = new Promise<void>((resolve, reject) => {
877+
authResolve = resolve;
878+
this._authReject = reject;
878879
});
879-
if (result !== 'AUTHORIZED') {
880-
throw new UnauthorizedError('Failed to authorize');
880+
// Prevent UnhandledPromiseRejection if it fails and no one awaits it
881+
this._pendingAuthPromise.catch(() => {});
882+
883+
try {
884+
const result = await auth(this._oauthProvider, {
885+
serverUrl: this._url,
886+
authorizationCode,
887+
iss: issParam,
888+
resourceMetadataUrl: this._resourceMetadataUrl,
889+
scope: this._scope,
890+
fetchFn: this._fetchWithInit,
891+
skipIssuerMetadataValidation: this._skipIssuerMetadataValidation
892+
});
893+
if (result !== 'AUTHORIZED') {
894+
throw new UnauthorizedError('Failed to authorize');
895+
}
896+
authResolve!();
897+
} catch (error) {
898+
const err = error instanceof Error ? error : new Error(String(error));
899+
this._authReject?.(err);
900+
throw error;
901+
} finally {
902+
this._pendingAuthPromise = undefined;
903+
this._authReject = undefined;
881904
}
882905
}
883906

@@ -887,6 +910,13 @@ export class StreamableHTTPClientTransport implements Transport {
887910
} finally {
888911
this._cancelReconnection = undefined;
889912
this._abortController?.abort();
913+
914+
if (this._authReject) {
915+
this._authReject(new Error('Transport closed'));
916+
this._pendingAuthPromise = undefined;
917+
this._authReject = undefined;
918+
}
919+
890920
this.onclose?.();
891921
}
892922
}
@@ -918,6 +948,9 @@ export class StreamableHTTPClientTransport implements Transport {
918948
isAuthRetry: boolean,
919949
stepUpRetries = 0
920950
): Promise<void> {
951+
if (this._pendingAuthPromise) {
952+
await this._pendingAuthPromise;
953+
}
921954
try {
922955
const { resumptionToken, onresumptiontoken } = options || {};
923956

0 commit comments

Comments
 (0)