1- import { NativeEventSource , EventSourcePolyfill } from "event-source-polyfill " ;
1+ import { fetchEventSource } from "@microsoft/fetch- event-source" ;
22import { EventIterator } from "event-iterator" ;
33
4- // Use native browser EventSource if available, and use the polyfill if not available
5- const EventSource = NativeEventSource || EventSourcePolyfill ;
6-
4+ function _getXSRFToken ( ) {
5+ // from @jupyterlab /services
6+ let cookie = "" ;
7+ try {
8+ cookie = document . cookie ;
9+ } catch ( e ) {
10+ // e.g. SecurityError in case of CSP Sandbox
11+ return null ;
12+ }
13+ const xsrfTokenMatch = cookie . match ( "\\b_xsrf=([^;]*)\\b" ) ;
14+ if ( xsrfTokenMatch ) {
15+ return xsrfTokenMatch [ 1 ] ;
16+ }
17+ return null ;
18+ }
719/**
820 * Build (and optionally launch) a repository by talking to a BinderHub API endpoint
921 */
@@ -14,8 +26,15 @@ export class BinderRepository {
1426 * @param {URL } buildEndpointUrl API URL of the build endpoint to talk to
1527 * @param {string } [buildToken] Optional JWT based build token if this binderhub installation requires using build tokens
1628 * @param {boolean } [buildOnly] Opt out of launching built image by default by passing `build_only` param
29+ * @param {string } [apiToken] Optional Bearer token for authenticating requests
1730 */
18- constructor ( providerSpec , buildEndpointUrl , buildToken , buildOnly ) {
31+ constructor (
32+ providerSpec ,
33+ buildEndpointUrl ,
34+ buildToken ,
35+ buildOnly ,
36+ { apiToken } ,
37+ ) {
1938 this . providerSpec = providerSpec ;
2039 // Make sure that buildEndpointUrl is a real URL - this ensures hostname is properly set
2140 if ( ! ( buildEndpointUrl instanceof URL ) ) {
@@ -40,6 +59,7 @@ export class BinderRepository {
4059 if ( buildOnly ) {
4160 this . buildUrl . searchParams . append ( "build_only" , "true" ) ;
4261 }
62+ this . apiToken = apiToken ;
4363
4464 this . eventIteratorQueue = null ;
4565 }
@@ -67,26 +87,37 @@ export class BinderRepository {
6787 * @returns {AsyncIterable<Line> } An async iterator yielding responses from the API as they come in
6888 */
6989 fetch ( ) {
70- this . eventSource = new EventSource ( this . buildUrl ) ;
71- return new EventIterator ( ( queue ) => {
90+ const headers = { } ;
91+ if ( this . apiToken && this . apiToken . length > 0 ) {
92+ headers [ "Authorization" ] = `Bearer ${ this . apiToken } ` ;
93+ } else {
94+ const xsrf = _getXSRFToken ( ) ;
95+ if ( xsrf ) {
96+ headers [ "X-Xsrftoken" ] = xsrf ;
97+ }
98+ }
99+ return new EventIterator ( async ( queue ) => {
72100 this . eventIteratorQueue = queue ;
73- this . eventSource . onerror = ( ) => {
74- queue . push ( {
75- phase : "failed" ,
76- message : "Failed to connect to event stream\n" ,
77- } ) ;
78- queue . stop ( ) ;
79- } ;
80-
81- this . eventSource . addEventListener ( "message" , ( event ) => {
82- // console.log("message received")
83- // console.log(event)
84- const data = JSON . parse ( event . data ) ;
85- // FIXME: fix case of phase/state upstream
86- if ( data . phase ) {
87- data . phase = data . phase . toLowerCase ( ) ;
88- }
89- queue . push ( data ) ;
101+ await fetchEventSource ( this . buildUrl , {
102+ headers,
103+ onerror : ( ) => {
104+ queue . push ( {
105+ phase : "failed" ,
106+ message : "Failed to connect to event stream\n" ,
107+ } ) ;
108+ queue . stop ( ) ;
109+ } ,
110+
111+ onmessage : ( event ) => {
112+ // console.log("message received")
113+ // console.log(event)
114+ const data = JSON . parse ( event . data ) ;
115+ // FIXME: fix case of phase/state upstream
116+ if ( data . phase ) {
117+ data . phase = data . phase . toLowerCase ( ) ;
118+ }
119+ queue . push ( data ) ;
120+ } ,
90121 } ) ;
91122 } ) ;
92123 }
0 commit comments