Home Reference Source Test

src/main/platform/browser/Nimiq.js

/**
 * Entry class and dynamic loader for the Nimiq library in Browsers.
 *
 * When using NodeJS, you don't need this class. Just require the `nimiq` library.
 *
 * @example <caption>Browser usage</caption>
 * <script type="text/javascript" src="https://cdn.nimiq.com/core/nimiq.js></script>
 * <script type="text/javascript">
 *     Nimiq.init(function(core) {
 *         console.log(core.wallet.address);
 *     }, function(errorCode) {
 *         console.log("Error initializing core.");
 *     }, options)
 * </script>
 *
 * @example <caption>Browser usage (experimental)</caption>
 * <script type="text/javascript" src="https://cdn.nimiq.com/core/nimiq.js></script>
 * <script type="text/javascript">
 *     async function init() {
 *         await Nimiq.load();
 *         const core = await new Nimiq.Core(options);
 *         console.log(core.wallet.address);
 *     }
 *     init();
 * </script>
 *
 * @example <caption>NodeJS usage</caption>
 * const Nimiq = require('nimiq');
 * const core = await new Nimiq.Core(options);
 * console.log(core.wallet.address);
 *
 * @namespace
 */
class Nimiq {
    /**
     * Load the Nimiq library.
     * @param {?string} [path] Path that contains the required files to load the library.
     * @returns {Promise} Promise that resolves once the library was loaded.
     */
    static load(path) {
        return Nimiq._load(path, 'web');
    }

    /**
     * Load the reduced offline version of the Nimiq library.
     * @param {?string} [path] Path that contains the required files to load the library.
     * @returns {Promise} Promise that resolves once the library was loaded.
     */
    static loadOffline(path) {
        return Nimiq._load(path, 'web-offline');
    }

    static _load(path, script) {
        if (!Nimiq._hasNativePromise()) return Nimiq._unsupportedPromise();
        if (Nimiq._loaded) return Promise.resolve();
        Nimiq._loadPromise = Nimiq._loadPromise ||
            new Promise((resolve, error) => {
                if (!Nimiq._script) {
                    if (!Nimiq._hasNativeClassSupport() || !Nimiq._hasProperScoping()) {
                        console.error('Unsupported browser');
                        error(Nimiq.ERR_UNSUPPORTED);
                        return;
                    } else if (!Nimiq._hasAsyncAwaitSupport()) {
                        Nimiq._script = `${script}-babel.js`;
                        console.warn('Client lacks native support for async');
                    } else {
                        Nimiq._script = `${script}.js`;
                    }
                }

                if (!path) {
                    if (Nimiq._currentScript && Nimiq._currentScript.src.indexOf('/') !== -1) {
                        path = Nimiq._currentScript.src.substring(0, Nimiq._currentScript.src.lastIndexOf('/') + 1);
                    } else {
                        // Fallback
                        path = './';
                    }
                }

                Nimiq._path = path;
                Nimiq._fullScript = Nimiq._path + Nimiq._script;

                Nimiq._onload = () => {
                    if (!Nimiq._loaded) {
                        error(Nimiq.ERR_UNKNOWN);
                    } else {
                        resolve();
                    }
                };
                Nimiq._loadScript(Nimiq._fullScript);
            }).then(() => new Promise((resolve, reject) =>
                Nimiq.WasmHelper.doImportBrowser()
                    .then(resolve)
                    .catch(reject.bind(null, Nimiq.ERR_UNKNOWN))
            ));
        return Nimiq._loadPromise;
    }

    static _loadScript(url) {
        const head = document.getElementsByTagName('head')[0];
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = url;
        head.appendChild(script);
    }

    /**
     * Load classes into scope (so you don't need to prefix them with `Nimiq.`).
     * @param {...string} classes Array of class names to load in global scope
     * @returns {Promise.<void>}
     */
    static loadToScope(...classes) {
        return Nimiq.load()
            .then(function () {
                for (const clazz of classes) {
                    self[clazz] = Nimiq[clazz];
                }
            });
    }

    static _hasNativeClassSupport() {
        try {
            eval('"use strict"; class A{}'); // eslint-disable-line no-eval
            return true;
        } catch (err) {
            return false;
        }
    }

    static _hasAsyncAwaitSupport() {
        try {
            eval('"use strict"; (async function() { await {}; })()'); // eslint-disable-line no-eval
            return true;
        } catch (err) {
            return false;
        }
    }

    static _hasProperScoping() {
        try {
            eval('"use strict"; class a{ a() { const a = 0; } }'); // eslint-disable-line no-eval
            return true;
        } catch (err) {
            return false;
        }
    }

    static _hasNativePromise() {
        return window.Promise;
    }

    static _unsupportedPromise() {
        return {
            'catch': function (handler) {
                handler(Nimiq.ERR_UNSUPPORTED);
                return this;
            },
            'then': function () {
                return this;
            }
        };
    }

    static _hasNativeGoodies() {
        return window.Number && window.Number.isInteger;
    }

    /**
     * Initialize the Nimiq client library.
     * @param {function()} ready Function to be called once the library is available.
     * @param {function(errorCode: number)} error Function to be called when the initialization fails.
     */
    static init(ready, error) {
        if (!Nimiq._hasNativePromise() || !Nimiq._hasNativeGoodies()) {
            if (error) error(Nimiq.ERR_UNSUPPORTED);
            return;
        }

        // Wait until there is only a single browser window open for this origin.
        WindowDetector.get().waitForSingleWindow(function () {
            Nimiq.load()
                .then(function () {
                    console.log('Nimiq engine loaded.');
                    if (ready) ready();
                })
                .catch(function (e) {
                    if (Number.isInteger(e)) {
                        if (error) error(e);
                    } else {
                        console.error('Error while initializing the core', e);
                        if (error) error(Nimiq.ERR_UNKNOWN);
                    }
                });
        }, function () {
            if (error) error(Nimiq.ERR_WAIT);
        });
    }
}

Nimiq._currentScript = document.currentScript;
if (!Nimiq._currentScript) {
    // Heuristic
    const scripts = document.getElementsByTagName('script');
    Nimiq._currentScript = scripts[scripts.length - 1];
}

Nimiq.ERR_WAIT = -1;
Nimiq.ERR_UNSUPPORTED = -2;
Nimiq.ERR_UNKNOWN = -3;
Nimiq._script = null;
Nimiq._path = null;
Nimiq._fullScript = null;
Nimiq._onload = null;
Nimiq._loaded = false;
Nimiq._loadPromise = null;