All files / src/custom-element http-request.js

92.42% Statements 61/66
90% Branches 18/20
100% Functions 20/20
90.56% Lines 48/53

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 9712x         3x               14x 62x 14x     14x 62x 60x 14x     4x       5x   30x 30x       34x 22x 12x 12x 1x 1x     11x 4x   7x 7x 7x   7x 7x 20x 7x   7x 7x 7x 15x 42x   7x 7x 7x 6x 6x 6x   7x                   67x 37x   37x 37x 29x   8x 8x               3x    
const     attr = (el, attr)=> el.getAttribute(attr);
 
export class HttpRequestElement extends HTMLElement
{
    static observedAttributes =
    [   'value' // populated from localStorage, if defined initially, sets the value in storage
    ,   'slice'
    ,   'url'
    ,   'method'
    ,   'header-accept'
    ];
 
    get requestHeaders()
    {   const ret = {};
        [...this.attributes].filter(a=>a.name.startsWith('header-')).map( a => ret[a.name.substring(7)] = a.value );
        return ret
    }
    get requestProps()
    {   const ret = {};
        [...this.attributes].filter(a=>!a.name.startsWith('header-'))
                            .filter(a=>!a.name.startsWith('slice')).map( a => ret[a.name] = a.value );
        return ret
    }
 
    disconnectedCallback(){ this.#destroy?.(); }
 
    connectedCallback()
    {
        setTimeout(()=>this.fetch(),0)
    }
    #inProgressUrl = ''
    #destroy = ()=>{}
 
    async fetch()
    {
        if( !this.closest('body') )
            return;
        const url = attr(this, 'url') || '';
        if( !url )
        {   this.#destroy?.();
            return this.value = {};
        }
 
        if( this.#inProgressUrl === url )
            return ;
 
        this.#inProgressUrl = url;
        const controller = new AbortController();
        this.#destroy = ()=> { controller.abort(this.localName+' disconnected'); this.#inProgressUrl = ''; }
 
        const request = { ...this.requestProps, headers: this.requestHeaders }
        ,       slice = { request }
        ,      update = () => this.dispatchEvent( new Event('change') );
        this.value = slice;
 
        update();
        const response = await fetch(url,{ ...this.requestProps, signal: controller.signal, headers: this.requestHeaders })
        ,      r = {headers: {}};
        [...response.headers].map( ([k,v]) => r.headers[k] = v );
        'ok,status,statusText,type,url,redirected'.split(',').map( k=> r[k] = response[k] )
 
        slice.response = r;
        update();
        if( r.headers['content-type']?.includes('json'))
            try
            {   slice.data = await response.json();
                update();
            }catch(_e){}
        Iif( r.headers['content-type']?.includes('xml'))
            try
            {   const s = await response.text();
                const parser = new DOMParser();
                slice.data = parser.parseFromString(s, 'application/xml')?.documentElement;
                update();
            }catch(_e){}
    }
 
    attributeChangedCallback(name, oldValue, newValue)
    {   if( name === 'url' )
        {   Eif( oldValue !== newValue)
            {
                oldValue && this.#destroy?.();
                if( newValue )
                    setTimeout(()=>this.fetch(),10)
                else
                {   this.value = {}
                    setTimeout(()=>this.dispatchEvent( new Event('change') ),10)
                }
            }
        }
    }
 
}
 
window.customElements.define( 'http-request', HttpRequestElement );
export default HttpRequestElement;