* web: Move non-workspace package. * web: Update ESBuild package version. * web: Use NPM link to alias local package. * web: Update lock. * web: Fix regression where bundler is expected.
		
			
				
	
	
		
			222 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/// <reference types="./types.js" />
 | 
						|
 | 
						|
/**
 | 
						|
 * @file Client-side observer for ESBuild events.
 | 
						|
 *
 | 
						|
 * @import { Message as ESBuildMessage } from "esbuild";
 | 
						|
 */
 | 
						|
 | 
						|
const logPrefix = "authentik/dev/web: ";
 | 
						|
const log = console.debug.bind(console, logPrefix);
 | 
						|
 | 
						|
/**
 | 
						|
 * @template {unknown} [Data=unknown]
 | 
						|
 * @typedef {(event: MessageEvent) => void} BuildEventListener
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * A client-side watcher for ESBuild.
 | 
						|
 *
 | 
						|
 * Note that this should be conditionally imported in your code, so that
 | 
						|
 * ESBuild may tree-shake it out of production builds.
 | 
						|
 *
 | 
						|
 * ```ts
 | 
						|
 * if (process.env.NODE_ENV === "development") {
 | 
						|
 *   await import("@goauthentik/esbuild-plugin-live-reload/client")
 | 
						|
 *     .catch(() => console.warn("Failed to import watcher"))
 | 
						|
 * }
 | 
						|
 * ```
 | 
						|
 *
 | 
						|
 * @implements {Disposable}
 | 
						|
 * @category Plugin
 | 
						|
 * runtime browser
 | 
						|
 */
 | 
						|
export class ESBuildObserver extends EventSource {
 | 
						|
    /**
 | 
						|
     * Whether the watcher has a recent connection to the server.
 | 
						|
     */
 | 
						|
    alive = true;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The number of errors that have occurred since the watcher started.
 | 
						|
     */
 | 
						|
    errorCount = 0;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Whether a reload has been requested while offline.
 | 
						|
     */
 | 
						|
    deferredReload = false;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The last time a message was received from the server.
 | 
						|
     */
 | 
						|
    lastUpdatedAt = Date.now();
 | 
						|
 | 
						|
    /**
 | 
						|
     * Whether the browser considers itself online.
 | 
						|
     */
 | 
						|
    online = true;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The ID of the animation frame for the reload.
 | 
						|
     */
 | 
						|
    #reloadFrameID = -1;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The interval for the keep-alive check.
 | 
						|
     * @type {ReturnType<typeof setInterval> | undefined}
 | 
						|
     */
 | 
						|
    #keepAliveInterval;
 | 
						|
 | 
						|
    #trackActivity = () => {
 | 
						|
        this.lastUpdatedAt = Date.now();
 | 
						|
        this.alive = true;
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * @type {BuildEventListener}
 | 
						|
     */
 | 
						|
    #startListener = () => {
 | 
						|
        this.#trackActivity();
 | 
						|
        log("⏰ Build started...");
 | 
						|
    };
 | 
						|
 | 
						|
    #internalErrorListener = () => {
 | 
						|
        this.errorCount += 1;
 | 
						|
 | 
						|
        if (this.errorCount > 100) {
 | 
						|
            clearTimeout(this.#keepAliveInterval);
 | 
						|
 | 
						|
            this.close();
 | 
						|
            log("⛔️ Closing connection");
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * @type {BuildEventListener<string>}
 | 
						|
     */
 | 
						|
    #errorListener = (event) => {
 | 
						|
        this.#trackActivity();
 | 
						|
 | 
						|
        console.group(logPrefix, "⛔️⛔️⛔️  Build error...");
 | 
						|
 | 
						|
        /**
 | 
						|
         * @type {ESBuildMessage[]}
 | 
						|
         */
 | 
						|
        const esbuildErrorMessages = JSON.parse(event.data);
 | 
						|
 | 
						|
        for (const error of esbuildErrorMessages) {
 | 
						|
            console.warn(error.text);
 | 
						|
 | 
						|
            if (error.location) {
 | 
						|
                console.debug(
 | 
						|
                    `file://${error.location.file}:${error.location.line}:${error.location.column}`,
 | 
						|
                );
 | 
						|
                console.debug(error.location.lineText);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        console.groupEnd();
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * @type {BuildEventListener}
 | 
						|
     */
 | 
						|
    #endListener = () => {
 | 
						|
        cancelAnimationFrame(this.#reloadFrameID);
 | 
						|
 | 
						|
        this.#trackActivity();
 | 
						|
 | 
						|
        if (!this.online) {
 | 
						|
            log("🚫 Build finished while offline.");
 | 
						|
            this.deferredReload = true;
 | 
						|
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        log("🛎️ Build completed! Reloading...");
 | 
						|
 | 
						|
        // We use an animation frame to keep the reload from happening before the
 | 
						|
        // event loop has a chance to process the message.
 | 
						|
        this.#reloadFrameID = requestAnimationFrame(() => {
 | 
						|
            window.location.reload();
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * @type {BuildEventListener}
 | 
						|
     */
 | 
						|
    #keepAliveListener = () => {
 | 
						|
        this.#trackActivity();
 | 
						|
        log("🏓 Keep-alive");
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * Initialize the ESBuild observer.
 | 
						|
     * This should be called once in your application.
 | 
						|
     *
 | 
						|
     * @param {string | URL} [url]
 | 
						|
     * @returns {ESBuildObserver}
 | 
						|
     */
 | 
						|
    static initialize = (url) => {
 | 
						|
        const esbuildObserver = new ESBuildObserver(url);
 | 
						|
 | 
						|
        return esbuildObserver;
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     *
 | 
						|
     * @param {string | URL} [url]
 | 
						|
     */
 | 
						|
    constructor(url) {
 | 
						|
        if (!url) {
 | 
						|
            throw new TypeError("ESBuildObserver: Cannot construct without a URL");
 | 
						|
        }
 | 
						|
 | 
						|
        super(url);
 | 
						|
 | 
						|
        this.addEventListener("esbuild:start", this.#startListener);
 | 
						|
        this.addEventListener("esbuild:end", this.#endListener);
 | 
						|
        this.addEventListener("esbuild:error", this.#errorListener);
 | 
						|
        this.addEventListener("esbuild:keep-alive", this.#keepAliveListener);
 | 
						|
 | 
						|
        this.addEventListener("error", this.#internalErrorListener);
 | 
						|
 | 
						|
        window.addEventListener("offline", () => {
 | 
						|
            this.online = false;
 | 
						|
        });
 | 
						|
 | 
						|
        window.addEventListener("online", () => {
 | 
						|
            this.online = true;
 | 
						|
 | 
						|
            if (!this.deferredReload) return;
 | 
						|
 | 
						|
            log("🛎️ Reloading after offline build...");
 | 
						|
            this.deferredReload = false;
 | 
						|
 | 
						|
            window.location.reload();
 | 
						|
        });
 | 
						|
 | 
						|
        log("🛎️ Listening for build changes...");
 | 
						|
 | 
						|
        this.#keepAliveInterval = setInterval(() => {
 | 
						|
            const now = Date.now();
 | 
						|
 | 
						|
            if (now - this.lastUpdatedAt < 10_000) return;
 | 
						|
 | 
						|
            this.alive = false;
 | 
						|
            log("👋 Waiting for build to start...");
 | 
						|
        }, 15_000);
 | 
						|
    }
 | 
						|
 | 
						|
    [Symbol.dispose]() {
 | 
						|
        return this.close();
 | 
						|
    }
 | 
						|
 | 
						|
    dispose() {
 | 
						|
        return this[Symbol.dispose]();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
export default ESBuildObserver;
 |