77 *
88 * @copyright Copyright (c) 2023 Distributive Corp.
99 */
10+ 'use strict' ;
1011
1112const { EventTarget, Event } = require ( 'event-target' ) ;
1213const { DOMException } = require ( 'dom-exception' ) ;
1314const { URL , URLSearchParams } = require ( 'url' ) ;
1415const { request, decodeStr } = require ( 'XMLHttpRequest-internal' ) ;
16+ const debug = globalThis . python . eval ( '__import__("pythonmonkey").bootstrap.require' ) ( 'debug' ) ;
17+
18+ debug ( 'xhr:module' ) ( 'loaded XMLHttpRequest.js module' ) ;
19+
20+ /**
21+ * Truncate a string-like thing for display purposes, returning a string.
22+ * @param {any } what The thing to truncate; must have a slice method and index property.
23+ * Works with string, array, typedarray, etc.
24+ * @param {number } maxlen The maximum length for truncation
25+ * @param {boolean } coerce Not false = coerce to printable character codes
26+ * @returns {string }
27+ */
28+ function trunc ( what , maxlen , coerce )
29+ {
30+ if ( coerce !== false && typeof what !== 'string' )
31+ {
32+ what = Array . from ( what ) . map ( x => {
33+ if ( x > 31 && x < 127 )
34+ return String . fromCharCode ( x ) ;
35+ else if ( x < 32 )
36+ return eval ( '"\\u24' + ( ( x ) . toString ( 16 ) ) . padStart ( 2 , 0 ) + '"' )
37+ else if ( x === 127 )
38+ return '\u2421' ;
39+ else
40+ return '\u2423' ;
41+ } ) . join ( '' ) ;
42+ }
43+ return `${ what . slice ( 0 , maxlen ) } ${ what . length > maxlen ? '\u2026' : '' } ` ;
44+ }
1545
1646// exposed
1747/**
@@ -29,6 +59,7 @@ class ProgressEvent extends Event
2959 this . lengthComputable = eventInitDict . lengthComputable ?? false ;
3060 this . loaded = eventInitDict . loaded ?? 0 ;
3161 this . total = eventInitDict . total ?? 0 ;
62+ this . debugTag = 'xhr:' ;
3263 }
3364}
3465
@@ -112,26 +143,22 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
112143 */
113144 open ( method , url , async = true , username = null , password = null )
114145 {
115- console . log ( 'OPEN START' ) ;
146+ debug ( 'xhr:open' ) ( 'open start, method=' + method ) ;
116147 // Normalize the method.
117148 // @ts -expect-error
118149 method = method . toString ( ) . toUpperCase ( ) ;
119150
120- console . log ( 'OPEN METHOD is ' + method ) ;
121-
122151 // Check for valid request method
123152 if ( ! method || FORBIDDEN_REQUEST_METHODS . includes ( method ) )
124153 throw new DOMException ( 'Request method not allowed' , 'SecurityError' ) ;
125154
126155 const parsedURL = new URL ( url ) ;
127- // parsedURL.protocol = 'http:';
128156 if ( username )
129157 parsedURL . username = username ;
130158 if ( password )
131159 parsedURL . password = password ;
160+ debug ( 'xhr:open' ) ( 'url is ' + parsedURL . href ) ;
132161
133- console . log ( 'OPEN URL is ' + parsedURL ) ;
134-
135162 // step 11
136163 this . #sendFlag = false ;
137164 this . #uploadListenerFlag = false ;
@@ -140,7 +167,6 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
140167 if ( async === false )
141168 this . #synchronousFlag = true ;
142169 this . #requestHeaders = { } ; // clear
143- this . setRequestHeader ( 'Accept-Encoding' , 'identity' ) ;
144170 this . #response = null ;
145171 this . #receivedBytes = [ ] ;
146172 this . #responseObject = null ;
@@ -151,7 +177,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
151177 this . #state = XMLHttpRequest . OPENED ;
152178 this . dispatchEvent ( new Event ( 'readystatechange' ) ) ;
153179 }
154- console . log ( 'OPEN END' ) ;
180+ debug ( 'xhr:open' ) ( 'finished open, state is ' + this . #state ) ;
155181 }
156182
157183 /**
@@ -161,8 +187,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
161187 */
162188 setRequestHeader ( name , value )
163189 {
164- console . log ( 'setRequestHeader, name ' + name + ' value ' + value ) ;
165- //console.log(new Error().stack);
190+ debug ( 'xhr:headers' ) ( `set header ${ name } =${ value } ` ) ;
166191 if ( this . #state !== XMLHttpRequest . OPENED )
167192 throw new DOMException ( 'setRequestHeader can only be called when state is OPEN' , 'InvalidStateError' ) ;
168193 if ( this . #sendFlag)
@@ -228,7 +253,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
228253 */
229254 send ( body = null )
230255 {
231- console . log ( 'SEND' ) ;
256+ debug ( 'xhr:send' ) ( `sending; body length= ${ body ?. length } ` ) ;
232257 if ( this . #state !== XMLHttpRequest . OPENED ) // step 1
233258 throw new DOMException ( 'connection must be opened before send() is called' , 'InvalidStateError' ) ;
234259 if ( this . #sendFlag) // step 2
@@ -259,11 +284,9 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
259284
260285 const originalAuthorContentType = this . #requestHeaders[ 'content-type' ] ;
261286 if ( ! originalAuthorContentType && extractedContentType )
262- {
263- console . log ( 'CONTENT_TYPE' ) ;
264287 this . #requestHeaders[ 'content-type' ] = extractedContentType ;
265- }
266288 }
289+ debug ( 'xhr:send' ) ( `content-type=${ this . #requestHeaders[ 'content-type' ] } ` ) ;
267290
268291 // step 5
269292 if ( this . #uploadObject. _hasAnyListeners ( ) )
@@ -288,7 +311,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
288311 */
289312 #sendAsync( )
290313 {
291- console . log ( 'SEND ASYNC ') ;
314+ debug ( 'xhr:send' ) ( 'sending in async mode ') ;
292315 this . dispatchEvent ( new ProgressEvent ( 'loadstart' , { loaded :0 , total :0 } ) ) ; // step 11.1
293316
294317 let requestBodyTransmitted = 0 ; // step 11.2
@@ -321,59 +344,46 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
321344 let responseLength = 0 ;
322345 const processResponse = ( response ) =>
323346 {
324- console . log ( 'processResponse 1, response is ' + response . toString ( ) ) ;
325- console . log ( 'processResponse 1, response header is ' + response . getAllResponseHeaders ( ) ) ;
326- for ( var key in response ) {
327- console . log ( key + ": " + response [ key ] ) ;
328- }
347+ debug ( 'xhr:response' ) ( `response headers ----\n${ response . getAllResponseHeaders ( ) } ` ) ;
329348 this . #response = response ; // step 11.9.1
330349 this . #state = XMLHttpRequest . HEADERS_RECEIVED ; // step 11.9.4
331350 this . dispatchEvent ( new Event ( 'readystatechange' ) ) ; // step 11.9.5
332351 if ( this . #state !== XMLHttpRequest . HEADERS_RECEIVED ) // step 11.9.6
333352 return ;
334353 responseLength = this . #response. contentLength ; // step 11.9.8
335- console . log ( 'processResponse 2, responseLength is ' + responseLength ) ;
336354 } ;
337355
338356 const processBodyChunk = ( /** @type {Uint8Array } */ bytes ) =>
339357 {
340- console . log ( 'processBodyChunk 1, bytes are ' + bytes ) ;
358+ debug ( 'xhr:response' ) ( `recv chunk, ${ bytes . length } bytes ( ${ trunc ( bytes , 100 ) } )` ) ;
341359 this . #receivedBytes. push ( bytes ) ;
342360 if ( this . #state === XMLHttpRequest . HEADERS_RECEIVED )
343361 this . #state = XMLHttpRequest . LOADING ;
344362 this . dispatchEvent ( new Event ( 'readystatechange' ) ) ;
345363 this . dispatchEvent ( new ProgressEvent ( 'progress' , { loaded :this . #receivedLength, total :responseLength } ) ) ;
346- console . log ( 'processBodyChunk 2' ) ;
347364 } ;
348365
349366 /**
350367 * @see https://xhr.spec.whatwg.org/#handle-response-end-of-body
351368 */
352369 const processEndOfBody = ( ) =>
353370 {
354- console . log ( 'processEndOfBody receivedLength is ' + this . #receivedLength) ;
371+ debug ( 'xhr:response' ) ( `end of body, received ${ this . #receivedLength} bytes` ) ;
355372 const transmitted = this . #receivedLength; // step 3
356373 const length = responseLength || 0 ; // step 4
357- console . log ( 'processEndOfBody responseLength is ' + responseLength ) ;
374+
358375 this . dispatchEvent ( new ProgressEvent ( 'progress' , { loaded :transmitted , total :length } ) ) ; // step 6
359- console . log ( 'processEndOfBody AGAIN 1 responseLength is ' + responseLength ) ;
360376 this . #state = XMLHttpRequest . DONE ; // step 7
361- console . log ( 'processEndOfBody AGAIN 2 responseLength is ' + responseLength ) ;
362377 this . #sendFlag = false ; // step 8
363- console . log ( 'processEndOfBody AGAIN 3 responseLength is ' + responseLength ) ;
378+
364379 this . dispatchEvent ( new Event ( 'readystatechange' ) ) ; // step 9
365- console . log ( 'processEndOfBody AGAIN 4 responseLength is ' + responseLength ) ;
366- console . log ( 'processEndOfBody before loop' ) ;
367380 for ( const eventType of [ 'load' , 'loadend' ] ) // step 10, step 11
368381 this . dispatchEvent ( new ProgressEvent ( eventType , { loaded :transmitted , total :length } ) ) ;
369- console . log ( 'processEndOfBody after loop' ) ;
370382 } ;
371383
372- console . log ( 'CALLING REQUEST' ) ;
373- console . log ( 'CALLING REQUEST, METHOD is ' + this . #requestMethod) ;
374- console . log ( 'CALLING REQUEST, URL is ' + this . #requestURL. toString ( ) ) ;
375- console . log ( 'CALLING REQUEST, HEADERS are ' + this . #requestHeaders) ;
376- console . log ( 'CALLING REQUEST, TIMEOUT is ' + this . timeout ) ;
384+ debug ( 'xhr:send' ) ( `${ this . #requestMethod} ${ this . #requestURL. href } ` ) ;
385+ debug ( 'xhr:headers' ) ( 'headers=' + Object . entries ( this . #requestHeaders) ) ;
386+
377387 // send() step 6
378388 request (
379389 this . #requestMethod,
@@ -396,8 +406,8 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
396406 */
397407 #sendSync( )
398408 {
409+ /* Synchronous XHR deprecated. /wg march 2024 */
399410 throw new DOMException ( 'synchronous XHR is not supported' , 'NotSupportedError' ) ;
400- // TODO: handle synchronous request
401411 }
402412
403413 /**
@@ -410,7 +420,6 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
410420 return ;
411421 if ( this . #timedOutFlag) // step 2
412422 return this . #reportRequestError( 'timeout' , new DOMException ( e . toString ( ) , 'TimeoutError' ) ) ;
413- console . error ( e ) ; // similar to browsers, print out network errors even then the error will be handled by `xhr.onerror`
414423 if ( this . #response === null /* network error */ ) // step 4
415424 return this . #reportRequestError( 'error' , new DOMException ( e . toString ( ) , 'NetworkError' ) ) ;
416425 else // unknown errors
@@ -686,6 +695,10 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget
686695 }
687696}
688697
698+ /* A side-effect of loading this module is to add the XMLHttpRequest and related symbols to the global
699+ * object. This makes them accessible in the "normal" way (like in a browser) even in PythonMonkey JS
700+ * host environments which don't include a require() symbol.
701+ */
689702if ( ! globalThis . XMLHttpRequestEventTarget )
690703 globalThis . XMLHttpRequestEventTarget = XMLHttpRequestEventTarget ;
691704if ( ! globalThis . XMLHttpRequestUpload )
0 commit comments