Repository: pyscript/pyscript
File: pyscriptjs/src/components/pyscript.ts (TypeScript)

Code before:
import { getLogger } from '../logger';
import { pyExec } from '../pyexec';
import { FetchError, _createAlertBanner } from '../exceptions';

const logger = getLogger('py-script');

export function make_PyScript(runtime: Runtime) {
    class PyScript extends HTMLElement {
        async connectedCallback() {
            ensureUniqueId(this);
            const pySrc = await this.getPySrc();
            this.innerHTML = '';
            pyExec(runtime, pySrc, this);
        }

        async getPySrc(): Promise<string> {
            if (this.hasAttribute('src')) {
                const url = this.getAttribute('src');
                try {
                    const response = await fetchIt(url);
                    return await response.text();
                } catch(e) {
                    _createAlertBanner(e.message);
                    this.innerHTML = '';
                    throw e
                }
            } else {
                return htmlDecode(this.innerHTML);
            }
        }
    }

    return PyScript;
}

/** Defines all possible py-on* and their corresponding event types  */
const pyAttributeToEvent: Map<string, string> = new Map<string, string>([
    // Leaving pys-onClick and pys-onKeyDown for backward compatibility
    ['pys-onClick', 'click'],
    ['pys-onKeyDown', 'keydown'],
    ['py-onClick', 'click'],
    ['py-onKeyDown', 'keydown'],
    // Window Events
    ['py-afterprint', 'afterprint'],
    ['py-beforeprint', 'beforeprint'],
    ['py-beforeunload', 'beforeunload'],
    ['py-error', 'error'],
    ['py-hashchange', 'hashchange'],
    ['py-load', 'load'],
    ['py-message', 'message'],
    ['py-offline', 'offline'],

Review comment (bug):
[follow-up]
Another `_createAlertBanner`: this is a code smell ;).
I think that the correct way to handle this case is to raise `UserError` from here, so that it will be caught by the general UserError logic.

We could go even further, by choosing one of these two options:
1. rename it as `FetchUserError` and make it a subclass of `UserError`: this way, *all* fetch errors will be displayed to the user in a proper way. I'm not 100% sure that this is always the case though: maybe there are cases in which a failed fetch is not an user error but an internal error?

2. in case we decide that some are user errors and some are internal errors, we can probably add an option to `fetchIt` to indicate which exception to raise in each case

Code after:
import { getLogger } from '../logger';
import { pyExec } from '../pyexec';
import { _createAlertBanner } from '../exceptions';
import { robustFetch } from '../fetch';

const logger = getLogger('py-script');

export function make_PyScript(runtime: Runtime) {
    class PyScript extends HTMLElement {
        async connectedCallback() {
            ensureUniqueId(this);
            const pySrc = await this.getPySrc();
            this.innerHTML = '';
            pyExec(runtime, pySrc, this);
        }

        async getPySrc(): Promise<string> {
            if (this.hasAttribute('src')) {
                const url = this.getAttribute('src');
                try {
                    const response = await robustFetch(url);
                    return await response.text();
                } catch(e) {
                    _createAlertBanner(e.message);
                    this.innerHTML = '';
                    throw e
                }
            } else {
                return htmlDecode(this.innerHTML);
            }
        }
    }

    return PyScript;
}

/** Defines all possible py-on* and their corresponding event types  */
const pyAttributeToEvent: Map<string, string> = new Map<string, string>([
    // Leaving pys-onClick and pys-onKeyDown for backward compatibility
    ['pys-onClick', 'click'],
    ['pys-onKeyDown', 'keydown'],
    ['py-onClick', 'click'],
    ['py-onKeyDown', 'keydown'],
    // Window Events
    ['py-afterprint', 'afterprint'],
    ['py-beforeprint', 'beforeprint'],
    ['py-beforeunload', 'beforeunload'],
    ['py-error', 'error'],
    ['py-hashchange', 'hashchange'],
    ['py-load', 'load'],
    ['py-message', 'message'],