1
1
import { EventSource , type ErrorEvent , type EventSourceInit } from "eventsource" ;
2
2
import { Transport } from "../shared/transport.js" ;
3
3
import { JSONRPCMessage , JSONRPCMessageSchema } from "../types.js" ;
4
+ import { auth , AuthResult , OAuthClientProvider } from "./auth.js" ;
4
5
5
6
export class SseError extends Error {
6
7
constructor (
@@ -12,6 +13,34 @@ export class SseError extends Error {
12
13
}
13
14
}
14
15
16
+ /**
17
+ * Configuration options for the `SSEClientTransport`.
18
+ */
19
+ export type SSEClientTransportOptions = {
20
+ /**
21
+ * An OAuth client provider to use for authentication.
22
+ *
23
+ * If given, the transport will automatically attach an `Authorization` header
24
+ * if an access token is available, or begin the authorization flow if not.
25
+ */
26
+ authProvider ?: OAuthClientProvider ;
27
+
28
+ /**
29
+ * Customizes the initial SSE request to the server (the request that begins the stream).
30
+ *
31
+ * NOTE: Setting this property will prevent an `Authorization` header from
32
+ * being automatically attached to the SSE request, if an `authProvider` is
33
+ * also given. This can be worked around by setting the `Authorization` header
34
+ * manually.
35
+ */
36
+ eventSourceInit ?: EventSourceInit ;
37
+
38
+ /**
39
+ * Customizes recurring POST requests to the server.
40
+ */
41
+ requestInit ?: RequestInit ;
42
+ } ;
43
+
15
44
/**
16
45
* Client transport for SSE: this will connect to a server using Server-Sent Events for receiving
17
46
* messages and make separate POST requests for sending messages.
@@ -23,35 +52,70 @@ export class SSEClientTransport implements Transport {
23
52
private _url : URL ;
24
53
private _eventSourceInit ?: EventSourceInit ;
25
54
private _requestInit ?: RequestInit ;
55
+ private _authProvider ?: OAuthClientProvider ;
26
56
27
57
onclose ?: ( ) => void ;
28
58
onerror ?: ( error : Error ) => void ;
29
59
onmessage ?: ( message : JSONRPCMessage ) => void ;
30
60
31
61
constructor (
32
62
url : URL ,
33
- opts ?: { eventSourceInit ?: EventSourceInit ; requestInit ?: RequestInit } ,
63
+ opts ?: SSEClientTransportOptions ,
34
64
) {
35
65
this . _url = url ;
36
66
this . _eventSourceInit = opts ?. eventSourceInit ;
37
67
this . _requestInit = opts ?. requestInit ;
68
+ this . _authProvider = opts ?. authProvider ;
38
69
}
39
70
40
- start ( ) : Promise < void > {
41
- if ( this . _eventSource ) {
42
- throw new Error (
43
- "SSEClientTransport already started! If using Client class, note that connect() calls start() automatically." ,
44
- ) ;
71
+ private async _authThenStart ( ) : Promise < void > {
72
+ if ( ! this . _authProvider ) {
73
+ throw new Error ( "No auth provider" ) ;
45
74
}
46
75
76
+ let result : AuthResult ;
77
+ try {
78
+ result = await auth ( this . _authProvider , { serverUrl : this . _url } ) ;
79
+ } catch ( error ) {
80
+ this . onerror ?.( error as Error ) ;
81
+ throw error ;
82
+ }
83
+
84
+ if ( result !== "AUTHORIZED" ) {
85
+ throw new Error ( "Unauthorized" ) ;
86
+ }
87
+
88
+ return await this . _startOrAuth ( ) ;
89
+ }
90
+
91
+ private async _commonHeaders ( ) : Promise < HeadersInit > {
92
+ const headers : HeadersInit = { } ;
93
+ if ( this . _authProvider ) {
94
+ const tokens = await this . _authProvider . tokens ( ) ;
95
+ if ( tokens ) {
96
+ headers [ "Authorization" ] = `Bearer ${ tokens . access_token } ` ;
97
+ }
98
+ }
99
+
100
+ return headers ;
101
+ }
102
+
103
+ private _startOrAuth ( ) : Promise < void > {
47
104
return new Promise ( ( resolve , reject ) => {
48
105
this . _eventSource = new EventSource (
49
106
this . _url . href ,
50
- this . _eventSourceInit ,
107
+ this . _eventSourceInit ?? {
108
+ fetch : ( url , init ) => this . _commonHeaders ( ) . then ( ( headers ) => fetch ( url , { ...init , headers } ) ) ,
109
+ } ,
51
110
) ;
52
111
this . _abortController = new AbortController ( ) ;
53
112
54
113
this . _eventSource . onerror = ( event ) => {
114
+ if ( event . code === 401 && this . _authProvider ) {
115
+ this . _authThenStart ( ) . then ( resolve , reject ) ;
116
+ return ;
117
+ }
118
+
55
119
const error = new SseError ( event . code , event . message , event ) ;
56
120
reject ( error ) ;
57
121
this . onerror ?.( error ) ;
@@ -97,6 +161,16 @@ export class SSEClientTransport implements Transport {
97
161
} ) ;
98
162
}
99
163
164
+ async start ( ) {
165
+ if ( this . _eventSource ) {
166
+ throw new Error (
167
+ "SSEClientTransport already started! If using Client class, note that connect() calls start() automatically." ,
168
+ ) ;
169
+ }
170
+
171
+ return await this . _startOrAuth ( ) ;
172
+ }
173
+
100
174
async close ( ) : Promise < void > {
101
175
this . _abortController ?. abort ( ) ;
102
176
this . _eventSource ?. close ( ) ;
@@ -109,7 +183,8 @@ export class SSEClientTransport implements Transport {
109
183
}
110
184
111
185
try {
112
- const headers = new Headers ( this . _requestInit ?. headers ) ;
186
+ const commonHeaders = await this . _commonHeaders ( ) ;
187
+ const headers = new Headers ( { ...commonHeaders , ...this . _requestInit ?. headers } ) ;
113
188
headers . set ( "content-type" , "application/json" ) ;
114
189
const init = {
115
190
...this . _requestInit ,
0 commit comments