"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const generator_1 = require("./Hash/generator");
const Video_1 = __importDefault(require("./Video/Video"));
const Stream_1 = __importDefault(require("./Stream/Stream"));
const Offline_1 = __importDefault(require("./Offline/Offline"));
const JavascriptLoadFileController_1 = __importDefault(require("./Offline/LoadFile/JavascriptLoadFileController"));
const Command_1 = __importDefault(require("./Command/Command"));
const Hardware_1 = __importDefault(require("./Hardware/Hardware"));
const Browser_1 = __importDefault(require("./Browser/Browser"));
const Iframe_1 = __importDefault(require("./Iframe/Iframe"));
const Touch_1 = __importDefault(require("./Touch/Touch"));
const Input_1 = __importDefault(require("./Input/Input"));
const Debug_1 = __importDefault(require("./Debug/Debug"));
const DeviceInfo_1 = __importDefault(require("./DeviceInfo/DeviceInfo"));
const Display_1 = __importDefault(require("./Display/Display"));
const Timing_1 = __importDefault(require("./Timing/Timing"));
const Sync_1 = __importDefault(require("./Sync/Sync"));
const FileSystem_1 = __importDefault(require("./FileSystem/FileSystem"));
const errorHelper_1 = require("./Error/errorHelper");
const Management_1 = __importDefault(require("./Management/Management"));
const Monitors_1 = __importDefault(require("./Monitors/Monitors"));
const Sensors_1 = __importDefault(require("./Sensors/Sensors"));
const Exchange_1 = require("./Exchange/Exchange");
const OSD_1 = __importDefault(require("./OSD/OSD"));
const ProofOfPlay_1 = __importDefault(require("./ProofOfPlay/ProofOfPlay"));
const NativeCommands_1 = __importDefault(require("./NativeCommands/NativeCommands"));
const Connect_1 = __importDefault(require("./Connect/Connect"));
/**
 * The `sos` API groups together all functionality the \@signageos/front-applet offers. This is a basic object that provides you with access to
 * all the applet functionality, such as device information, display control, video playback, and more.
 *
 * :::warning
 * All calls to the sos object need to happen after `sos.onReady()` is resolved. After the sOS object is ready, there are global properties available, which can be used for applet customization and device identification.
 * :::
 *
 * @example
 * // Simple example of using the sos API to play a video.
 * import sos from '@signageos/front-applet';
 *
 * sos.onReady().then(async function () {
 *   startYourApplet(); // Example method to run
 *   // Any other code
 * }
 */
class FrontApplet {
    window;
    messagePrefix;
    /**
     * The `config` property is a Key-value dictionary of the applet configuration. It is assigned on the device with timing via Box or SDK/Rest API.
     *
     * @since 1.5.0
     * @example // {@link https://developers.signageos.io/docs/sos-guides/configuration/ | How to use configuration}
     */
    config = undefined;
    /**
     * The `authHash` property is an alternative device identifier, which is designed for secure pairing and locating devices through
     * the REST API.
     *
     * 1. The `authHash` is commonly used as a link between the Applet deployed on the device and the device in device management.
     * 2. The `authHash` is unique and can be revoked upon request.
     * 3. The `authHash` can be used as a universal identifier for your Applet application.
     *
     * @since 1.0.0
     * @tutorial https://developers.signageos.io/docs/sos-guides/device-identification
     */
    authHash = undefined;
    /**
     * The `appletVersion` is the version of the current applet.
     *
     * @since 4.5.0
     * @example
     * sos.onReady().then(async function () {
     *  console.log(sos.appletVersion); // e.g. "4.5.0"
     * });
     */
    appletVersion = undefined;
    // volatile, set if the FrontApplet is running inside an iframe
    apiJsUri;
    debug;
    deviceInfo;
    display;
    stream;
    video;
    command;
    hardware;
    offline;
    browser;
    iframe;
    touch;
    input;
    timing;
    sync;
    fileSystem;
    management;
    monitors;
    osd;
    proofOfPlay;
    sensors;
    native;
    connect;
    readyEmitter;
    invocationResponseEmitter;
    javascriptLoadFileController;
    /** @internal */
    constructor(window, messagePrefix) {
        this.window = window;
        this.messagePrefix = messagePrefix;
        const rootExchange = (0, Exchange_1.createExchange)(messagePrefix, this.postMessage.bind(this));
        this.readyEmitter = new events_1.EventEmitter();
        this.invocationResponseEmitter = new events_1.EventEmitter();
        this.debug = new Debug_1.default(this.window, messagePrefix, this.postMessage.bind(this));
        this.deviceInfo = new DeviceInfo_1.default(messagePrefix, this.postMessage.bind(this));
        this.display = new Display_1.default(messagePrefix, this.postMessage.bind(this));
        this.stream = new Stream_1.default(messagePrefix, this.postMessage.bind(this));
        this.video = new Video_1.default(messagePrefix, this.postMessage.bind(this));
        this.command = new Command_1.default(this.messagePrefix, this.postMessage.bind(this));
        this.hardware = new Hardware_1.default(this.messagePrefix, this.postMessage.bind(this));
        this.offline = new Offline_1.default(messagePrefix, this.window, this.postMessage.bind(this));
        this.browser = new Browser_1.default(rootExchange.createNamespaced('browser'));
        this.touch = new Touch_1.default(this.window, this.messagePrefix, this.postMessage.bind(this));
        this.iframe = new Iframe_1.default(this.window, this.messagePrefix, this, [this.touch.transformIframeData.bind(this.touch)]);
        this.input = new Input_1.default(this.window, this.messagePrefix);
        this.timing = new Timing_1.default(this.messagePrefix, this.postMessage.bind(this));
        this.sync = new Sync_1.default(this.messagePrefix, this.postMessage.bind(this));
        this.fileSystem = new FileSystem_1.default(this.messagePrefix, this.postMessage.bind(this));
        this.management = new Management_1.default(messagePrefix, this.postMessage.bind(this));
        this.monitors = new Monitors_1.default(messagePrefix, this.postMessage.bind(this));
        this.osd = new OSD_1.default(messagePrefix, this.postMessage.bind(this));
        this.proofOfPlay = new ProofOfPlay_1.default(this.command);
        this.sensors = new Sensors_1.default(messagePrefix);
        this.native = new NativeCommands_1.default(messagePrefix, this.postMessage.bind(this));
        this.connect = new Connect_1.default(this.window, this.messagePrefix);
        this.javascriptLoadFileController = new JavascriptLoadFileController_1.default(this.window);
        this.window.addEventListener('message', (event) => this.handleMessage(event));
        void this.postMessage({ type: messagePrefix + '_api.ready' });
        this.startPinging();
    }
    /** @internal */
    postMessage(data) {
        const invocationUid = (0, generator_1.generateUniqueHash)();
        this.window.setTimeout(() => void this.window.parent?.postMessage({
            invocationUid,
            ...data,
        }, '*'));
        return new Promise((resolve, reject) => {
            this.invocationResponseEmitter.once(invocationUid, (responseData) => {
                if (responseData.error) {
                    if (responseData.error.error) {
                        reject((0, errorHelper_1.createError)(responseData.error.error));
                    }
                    else {
                        reject(responseData.error);
                    }
                }
                else {
                    resolve(responseData.success);
                }
            });
        });
    }
    /**
     * The `onReady()` method accepts an optional listener which is called after the sos API are loaded and ready. It also returns a promise
     * which is resolved at the same time as the listener.
     *
     * @param listener Optional listener which is called when the sos API is ready.
     * @return {Promise<void>} Promise which is resolved when the sos API is ready.
     * @since 1.0.0
     *
     * @example
     * sos.onReady().then(async function () {
     *   console.log(await sos.app.getType());
     * });
     *
     * sos.onReady(async function () {
     *   console.log(await sos.app.getType());
     * });
     */
    onReady(listener) {
        return new Promise((resolve) => {
            this.readyEmitter.once('ready', () => {
                resolve();
                if (typeof listener !== 'undefined') {
                    listener();
                }
            });
            this.checkIfReady();
        });
    }
    /**
     * The `restore()` method clears all previously played videos, streams, clear display view. The typical case of usage for digital signage
     * is playing a loop with some specified duration. This method should be called always when the loop is changed. It will clear all
     * previously played videos, streams, clear display views, and notify for garbage collection.
     *
     * It stops all video playback and clears out the memory. The following function should be triggered only in a case the whole playback
     * needs to be restarted as it's completely switching the playback 'loop/playlist'.
     *
     * @example
     * sos.onReady(async function () {
     *   // Some function which is called when the applet is ready
     *   sos.restore();
     * });
     */
    restore() {
        void this.postMessage({ type: this.getMessage('restore') });
        this.removeMediaListeners();
        this.forwardRestoreToFrontAppletWindows();
    }
    /**
     * The `refresh()` method initializes the refresh of the applet. Similar to window.location.reload(), but the applet is not downloaded again.
     *
     * @return {Promise<void>} Promise that is resolved when the refresh is completed.
     * @since 1.0.0
     *
     * @example
     * await sos.refresh();
     */
    async refresh() {
        await this.postMessage({
            type: this.getMessage('applet_refresh'),
        });
    }
    forwardRestoreToFrontAppletWindows() {
        const data = {
            type: this.messagePrefix + 'restore',
        };
        this.iframe.forwardToFrontAppletWindows(data);
    }
    removeMediaListeners() {
        this.video.removeEventListeners();
        this.stream.removeEventListeners();
        this.sync.removeEventListeners();
        this.input.removeEventListeners();
    }
    checkIfReady() {
        if (this.authHash !== undefined && this.config !== undefined && this.display.version !== undefined) {
            this.readyEmitter.emit('ready');
        }
    }
    handleMessage(event) {
        if (typeof event.data === 'object') {
            const source = event.source;
            if (source === this.window.parent) {
                const data = event.data;
                switch (data.type) {
                    case this.getMessage('invocation.success'):
                        this.invocationResponseEmitter.emit(data.invocationUid, { success: data });
                        break;
                    case this.getMessage('invocation.error'):
                        this.invocationResponseEmitter.emit(data.invocationUid, { error: data });
                        break;
                    case this.getMessage('config.update'):
                        // @ts-expect-error - Assigning config from external data
                        this.config = data.config;
                        this.checkIfReady();
                        break;
                    case this.getMessage('auth_hash.update'):
                        // @ts-expect-error - Assigning authHash from external data
                        this.authHash = data.authHash;
                        this.checkIfReady();
                        break;
                    case this.getMessage('front_display_version.update'):
                        this.display.version = data.version;
                        this.checkIfReady();
                        break;
                    case this.getMessage('applet_version.update'):
                        // @ts-expect-error - Assigning appletVersion from external data
                        this.appletVersion = data.version;
                        break;
                    case this.getMessage('restore'):
                        // TODO: shouldn't we call this.restore() instead?
                        this.removeMediaListeners();
                        break;
                    case this.getMessage('assets.load_js'):
                        void this.javascriptLoadFileController.load(data.src, this.window.document.body);
                        break;
                    default:
                        this.browser.handleMessageData(data);
                        this.sync.handleMessageData(data);
                        this.video.handleMessageData(data);
                        this.stream.handleMessageData(data);
                        this.input.handleMessageData(data);
                        this.command.handleMessageData(data);
                        this.fileSystem.handleMessageData(data);
                        this.sensors.handleMessageData(data);
                        this.management.wifi.handleMessageData(data);
                        this.hardware.handleMessageData(data);
                        this.touch.handleMessageData(data);
                        this.debug.handleMessageData(data);
                        this.connect.handleMessageData(data);
                }
                this.iframe.forwardToFrontAppletWindows(data);
            }
            else if (isWindow(source) && source !== this.window) {
                const data = event.data;
                switch (data.type) {
                    case this.messagePrefix + '_loader.ready':
                        this.iframe.registerFrontAppletLoaderWindow(source);
                        break;
                    case this.messagePrefix + '_api.ready':
                        this.iframe.registerBundledFrontAppletWindow(source);
                        break;
                    default:
                        this.iframe.forwardFromFrontAppletWindows(source, data);
                }
            }
        }
    }
    getMessage(name) {
        return this.messagePrefix + '.' + name;
    }
    startPinging() {
        const doPing = () => this.postMessage({ type: this.getMessage('ping') });
        this.window.setInterval(doPing, 30e3);
        void doPing();
    }
}
exports.default = FrontApplet;
function isWindow(source) {
    return Boolean(source);
}
//# sourceMappingURL=FrontApplet.js.map