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

100% Statements 60/60
100% Branches 18/18
100% Functions 20/20
100% Lines 47/47

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 9012x         3x               14x 62x 14x     14x 62x 60x 14x     4x       5x   35x 35x       39x 27x 12x 12x 1x 1x     11x 4x   7x 7x 7x   7x 7x 20x 7x   7x 7x 7x 15x 42x   7x 7x 7x 6x 6x 6x         130x 65x   42x 42x 34x   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){}
    }
 
    attributeChangedCallback(name, oldValue, newValue)
    {   if( name === 'url' )
        {   if( 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;