"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const request_promise_native_1 = __importDefault(require("request-promise-native"));
const jsdom_1 = require("jsdom");
const debug_1 = __importDefault(require("debug"));
const apexConstants_1 = require("./apexConstants");
const co_body_1 = __importDefault(require("co-body"));
const url_1 = require("url");
const log = debug_1.default('localdevserver');
const ONE_APP_URL = '/one/one.app';
let cachedConfig = null;
class ApexResourceLoader extends jsdom_1.ResourceLoader {
    constructor(orgRequest, instanceUrl) {
        super();
        this.orgRequest = orgRequest;
        this.instanceUrl = instanceUrl;
    }
    fetch(url, options) {
        const instanceUrl = new url_1.URL(this.instanceUrl);
        const parsedUrl = new url_1.URL(url, this.instanceUrl);
        if (instanceUrl.origin == parsedUrl.origin &&
            parsedUrl.pathname.endsWith('/inline.js')) {
            log(`loading external url: ${url} as ${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`);
            return this.orgRequest
                .get({
                url: `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`
            })
                .then(res => {
                return Buffer.from(res);
            });
        }
        log(`skipped external url: ${url}`);
        return null;
    }
}
exports.ApexResourceLoader = ApexResourceLoader;
function apexMiddleware(connectionParams) {
    return async function (req, res, next) {
        if (req.url.startsWith('/api/apex/execute') && connectionParams) {
            const body = await co_body_1.default.json(req);
            const classname = body.classname;
            if (typeof classname !== 'string') {
                return sendError(res, 'classname must be specified');
            }
            const method = body.method;
            if (typeof method !== 'string') {
                return sendError(res, 'method must be specified');
            }
            const namespace = body.namespace;
            if (typeof namespace !== 'string') {
                return sendError(res, 'namespace must be specified');
            }
            const cacheable = body.cacheable;
            if (typeof cacheable !== 'boolean') {
                return sendError(res, 'cacheable must be specified');
            }
            const params = body.params;
            if (!cachedConfig) {
                try {
                    cachedConfig = await getConfig(connectionParams);
                    if (cachedConfig == null) {
                        res.status(500).send('error parsing or finding aura config: window.Aura not found');
                        return;
                    }
                }
                catch (e) {
                    console.error(e);
                    res.status(500).send(e.message);
                    return;
                }
            }
            const apexRequest = {
                namespace,
                classname,
                method,
                cacheable,
                params
            };
            const response = await callAuraApexRequest(connectionParams, cachedConfig, apexRequest);
            try {
                const parsed = JSON.parse(response);
                const actionResult = parsed.actions[0];
                if (actionResult.state === 'ERROR') {
                    res.status(500)
                        .type('json')
                        .send({ error: actionResult.error });
                }
                else {
                    res.type('json').send(actionResult.returnValue);
                }
            }
            catch (e) {
                log(`invalid apex response: ${response}`);
                res.status(500).send(`error parsing apex response: ${e.message}`);
            }
            return;
        }
        next();
    };
}
exports.apexMiddleware = apexMiddleware;
function sendError(res, message) {
    res.status(500).send(message);
}
async function callAuraApexRequest(connectionParams, auraconfig, { namespace, classname, method, cacheable, params }) {
    log('Calling apex controller');
    const context = {
        mode: auraconfig.context.mode,
        fwuid: auraconfig.context.fwuid,
        app: auraconfig.context.app,
        loaded: auraconfig.context.loaded,
        dn: auraconfig.context.dn || [],
        globals: auraconfig.context.globals || {},
        uad: auraconfig.context.uad || true
    };
    const auraActionMessage = {
        actions: [
            {
                id: '0',
                descriptor: 'aura://ApexActionController/ACTION$execute',
                callingDescriptor: 'UNKNOWN',
                params: {
                    namespace,
                    classname,
                    method,
                    cacheable,
                    isContinuation: false,
                    params
                }
            }
        ]
    };
    const form = {
        message: JSON.stringify(auraActionMessage),
        'aura.pageURI': '/lightning/n/Apex',
        'aura.context': JSON.stringify(context),
        'aura.token': auraconfig.token
    };
    const orgRequest = getOrgRequest(connectionParams);
    const apexResponse = await orgRequest.post({
        url: '/aura?aura.ApexAction.execute=1',
        form
    });
    return apexResponse;
}
function getOrgRequest({ accessToken, instanceUrl }) {
    const jar = request_promise_native_1.default.jar();
    const sid = request_promise_native_1.default.cookie(`sid=${accessToken}`);
    jar.setCookie(sid, instanceUrl + '/');
    const orgRequest = request_promise_native_1.default.defaults({
        baseUrl: instanceUrl,
        jar
    });
    return orgRequest;
}
async function getConfig(connectionParams) {
    log('Getting aura configuration');
    const orgRequest = getOrgRequest(connectionParams);
    const response = await orgRequest.get({
        url: ONE_APP_URL
    });
    if (response.indexOf('window.location.replace(') != -1) {
        throw new Error('error retrieving aura config: unauthenticated');
    }
    const resourceLoader = new ApexResourceLoader(orgRequest, connectionParams.instanceUrl);
    const waitForInitConfig = new Promise((resolve, reject) => {
        let config = null;
        const Aura = {};
        const oneApp = new jsdom_1.JSDOM(response, {
            resources: resourceLoader,
            runScripts: 'dangerously',
            url: connectionParams.instanceUrl + ONE_APP_URL,
            referrer: connectionParams.instanceUrl + ONE_APP_URL,
            beforeParse: window => {
                Object.defineProperty(window, 'Aura', {
                    get: () => Aura,
                    set: () => { },
                    enumerable: true
                });
                Object.defineProperty(Aura, 'frameworkJsReady', {
                    get: () => false,
                    set: () => { },
                    enumerable: true
                });
                Object.defineProperty(Aura, 'initConfig', {
                    get: () => {
                        return config;
                    },
                    set: newConfig => {
                        log(`Recieved initConfig ${newConfig}`);
                        config = newConfig;
                        resolve(config);
                    },
                    enumerable: true
                });
                window.Aura = Aura;
            }
        });
    });
    const waitForInitConfigTimeout = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('Timed out waiting for initConfig');
        }, apexConstants_1.WAIT_FOR_ONE_APP_LOAD);
    });
    try {
        const config = await Promise.race([
            waitForInitConfig,
            waitForInitConfigTimeout
        ]);
        log(`initConfig Contents = ${JSON.stringify(config)}`);
        return config;
    }
    catch (e) {
        log(`Error waiting for initConfig: ${e}`);
    }
    return null;
}
//# sourceMappingURL=apexMiddleware.js.map