"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ide_connection_1 = require("@opensumi/ide-connection");
const ide_core_common_1 = require("@opensumi/ide-core-common");
const path_1 = require("@opensumi/ide-utils/lib/path");
const common_1 = require("../common");
const activator_1 = require("../common/activator");
const ext_process_1 = require("../common/ext.process");
const utils_1 = require("../common/utils");
const vscode_1 = require("../common/vscode");
const ext_host_api_impl_1 = require("./api/sumi/ext.host.api.impl");
const ext_host_api_impl_2 = require("./api/telemetry/ext.host.api.impl");
const ext_host_api_impl_3 = require("./api/vscode/ext.host.api.impl");
const ext_host_extensions_1 = require("./api/vscode/ext.host.extensions");
const ext_host_secrets_1 = require("./api/vscode/ext.host.secrets");
const ext_host_storage_1 = require("./api/vscode/ext.host.storage");
const vscode_extension_1 = require("./vscode.extension");
const { enumValueToArray } = ide_core_common_1.arrays;
var EInternalModule;
(function (EInternalModule) {
    EInternalModule["VSCODE"] = "vscode";
    EInternalModule["KAITIAN"] = "kaitian";
    EInternalModule["SUMI"] = "sumi";
    EInternalModule["TELEMETRY"] = "vscode-extension-telemetry-wrapper";
})(EInternalModule || (EInternalModule = {}));
const __interceptModule = enumValueToArray(EInternalModule);
const __interceptModuleSet = new Set(__interceptModule);
class ApiImplFactory {
    constructor(rpcProtocol, extHost, injector) {
        this.rpcProtocol = rpcProtocol;
        this.extHost = extHost;
        this.injector = injector;
        this.apiFactory = this.createAPIFactory(rpcProtocol, extHost, injector);
        this.extAPIImpl = new Map();
    }
    load(extension, addonImpl) {
        if (!extension) {
            return;
        }
        let apiImpl = this.extAPIImpl.get(extension.id);
        if (!apiImpl) {
            try {
                apiImpl = this.apiFactory(extension);
                this.extAPIImpl.set(extension.id, apiImpl);
            }
            catch (e) { }
        }
        if (addonImpl) {
            return {
                ...addonImpl,
                ...apiImpl,
            };
        }
        return apiImpl;
    }
}
class VSCodeAPIImpl extends ApiImplFactory {
    createAPIFactory(rpcProtocol, extHost, injector) {
        return (0, ext_host_api_impl_3.createApiFactory)(rpcProtocol, extHost, injector.get(ext_process_1.ExtHostAppConfig));
    }
}
class OpenSumiAPIImpl extends ApiImplFactory {
    createAPIFactory(rpcProtocol, extHost, injector) {
        const appConfig = injector.get(ext_process_1.ExtHostAppConfig);
        return (0, ext_host_api_impl_1.createAPIFactory)(rpcProtocol, extHost, 'node', injector.get(ide_core_common_1.IReporter), appConfig.sumiApiExtenders);
    }
}
class TelemetryAPIImpl extends ApiImplFactory {
    createAPIFactory(rpcProtocol, extHost) {
        return (0, ext_host_api_impl_2.createAPIFactory)(rpcProtocol, extHost, 'node');
    }
}
class ExtensionHostServiceImpl {
    constructor(rpcProtocol, logger, injector) {
        this.logger = logger;
        this.extensionsChangeEmitter = new ide_core_common_1.Emitter();
        this.extensionErrors = new WeakMap();
        this._extHostErrorStackTraceExtended = false;
        this.rpcProtocol = rpcProtocol;
        this.storage = new ext_host_storage_1.ExtHostStorage(rpcProtocol);
        this.secret = new ext_host_secrets_1.ExtHostSecret(rpcProtocol);
        const reporter = injector.get(ide_core_common_1.IReporter);
        this.vscodeAPIImpl = new VSCodeAPIImpl(rpcProtocol, this, injector);
        this.openSumiAPIImpl = new OpenSumiAPIImpl(rpcProtocol, this, injector);
        this.telemetryAPIImpl = new TelemetryAPIImpl(rpcProtocol, this, injector);
        this.localization = rpcProtocol.get(vscode_1.ExtHostAPIIdentifier.ExtHostLocalization);
        this.reporterService = new ide_core_common_1.ReporterService(reporter, {
            host: ide_core_common_1.REPORT_HOST.EXTENSION,
        });
    }
    /**
     * 收集插件未捕获异常
     * @param error
     */
    reportUnexpectedError(error) {
        // 在此先访问一下 stack 触发 Error.prepareStackTrace 分析的插件异常信息
        const stackTraceMassage = error.stack;
        const extension = this.extensionErrors.get(error);
        if (extension && stackTraceMassage) {
            this.reportRuntimeError(error, extension, stackTraceMassage);
        }
    }
    async $getActivatedExtensions() {
        return this.extensionsActivator.all().map((e) => e.toJSON());
    }
    $getExtensions() {
        return this.extensions;
    }
    async close() {
        await Promise.race([(0, ide_core_common_1.timeout)(4000), this.extensionsActivator.deactivate]);
    }
    async init() {
        this.extensionsActivator = new activator_1.ExtensionsActivator(this.logger);
        this.defineAPI();
    }
    getExtensions() {
        return this.extensions.map((ext) => new vscode_extension_1.KTExtension(ext, this, this.rpcProtocol.getProxy(vscode_1.MainThreadAPIIdentifier.MainThreadExtensionService), this.getExtensionExports(ext.id), this.getExtendExports(ext.id)));
    }
    extendExtHostErrorStackTrace() {
        if (this._extHostErrorStackTraceExtended) {
            return;
        }
        this._extHostErrorStackTraceExtended = true;
        Error.stackTraceLimit = 100;
        Error.prepareStackTrace = (error, stackTrace) => {
            let extension;
            let stackTraceMessage = `Error: ${error.message}`;
            let fileName;
            for (const call of stackTrace) {
                stackTraceMessage += `\n\tat ${call.toString()}`;
                fileName = call.getFileName();
                if (!extension && fileName) {
                    //
                    // 遍历异常堆栈中的 filename，尝试查找抛出异常的插件
                    //
                    extension = this.findExtension(fileName);
                }
            }
            // 存下当前异常属于哪一个插件以便上报
            this.extensionErrors.set(error, extension);
            const traceMassage = `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
            return traceMassage;
        };
    }
    async $updateExtHostData() {
        const extensions = await this.rpcProtocol
            .getProxy(vscode_1.MainThreadAPIIdentifier.MainThreadExtensionService)
            .$getExtensions();
        // node 层 extensionLocation 不使用 static 直接使用 file
        // node 层 extension 实例和 vscode 保持一致，并继承 IExtensionProps
        this.extensions = extensions.map((item) => {
            var _a, _b, _c, _d, _e, _f;
            return ({
                ...item,
                l10n: (_a = item.packageJSON) === null || _a === void 0 ? void 0 : _a.l10n,
                displayName: item.displayName || item.packageJSON.displayName,
                isUnderDevelopment: !!item.isDevelopment,
                publisher: (_b = item.packageJSON) === null || _b === void 0 ? void 0 : _b.publisher,
                version: (_c = item.packageJSON) === null || _c === void 0 ? void 0 : _c.version,
                engines: (_d = item.packageJSON) === null || _d === void 0 ? void 0 : _d.engines,
                identifier: new vscode_1.ExtensionIdentifier(item.id),
                uuid: (_f = (_e = item.packageJSON) === null || _e === void 0 ? void 0 : _e.__metadata) === null || _f === void 0 ? void 0 : _f.id,
                extensionLocation: ide_core_common_1.Uri.file(item.path),
            });
        });
        this.logger.debug('extensions', this.extensions.map((extension) => extension.packageJSON.name));
        this.extendExtHostErrorStackTrace();
    }
    async $fireChangeEvent() {
        this.extensionsChangeEmitter.fire();
    }
    createExtension(extensionDescription) {
        const activateExtension = this.extensionsActivator.get(extensionDescription.id);
        return new vscode_extension_1.KTExtension(extensionDescription, this, this.rpcProtocol.getProxy(vscode_1.MainThreadAPIIdentifier.MainThreadExtensionService), activateExtension && activateExtension.exports, activateExtension && activateExtension.extendExports);
    }
    getExtension(extensionId) {
        const extensionDescription = this.extensions.find((extension) => (0, common_1.getExtensionId)(extensionId) === (0, common_1.getExtensionId)(extension.id));
        if (extensionDescription) {
            return this.createExtension(extensionDescription);
        }
    }
    findExtension(filePath) {
        return this.extensions.find((extension) => filePath.startsWith(extension.realPath));
    }
    lookup(extensionModule, depth) {
        if (depth >= 3) {
            return undefined;
        }
        const extension = this.findExtension(extensionModule.filename);
        if (extension) {
            return extension;
        }
        if (extensionModule.parent) {
            return this.lookup(extensionModule.parent, (depth += 1));
        }
        return undefined;
    }
    defineAPI() {
        const module = (0, utils_1.getNodeRequire)()('module');
        const originalLoad = module._load;
        const that = this;
        module._load = function load(request, parent, isMain) {
            if (!__interceptModuleSet.has(request)) {
                return originalLoad.apply(this, arguments);
            }
            // 可能存在开发插件时通过 npm link 的方式安装的依赖
            // 只通过 parent.filename 查找插件无法兼容这种情况
            // 因为 parent.filename 拿到的路径并不在同一个目录下
            // 往上递归遍历依赖的模块是否在插件目录下
            // 最多只查找 3 层，因为不太可能存在更长的依赖关系
            const extension = that.lookup(parent, 0);
            if (!extension) {
                return;
            }
            switch (request) {
                case EInternalModule.VSCODE:
                    return that.vscodeAPIImpl.load(extension);
                case EInternalModule.SUMI:
                case EInternalModule.KAITIAN:
                    return that.openSumiAPIImpl.load(extension, that.vscodeAPIImpl.load(extension));
                case EInternalModule.TELEMETRY:
                    return that.telemetryAPIImpl.load(extension);
                default:
                    break;
            }
        };
    }
    getExtensionExports(extensionId) {
        const activateExtension = this.extensionsActivator.get(extensionId);
        if (activateExtension) {
            return activateExtension.exports;
        }
    }
    getExtendExports(extensionId) {
        const activatedExtension = this.extensionsActivator.get(extensionId);
        if (activatedExtension) {
            return activatedExtension.extendExports;
        }
    }
    containsSumiContributes(extension) {
        if (extension.packageJSON.sumiContributes) {
            return true;
        }
        return false;
    }
    isActivated(extensionId) {
        return this.extensionsActivator.has(extensionId);
    }
    reportRuntimeError(err, extension, stackTraceMessage) {
        var _a;
        /**
         * 可能存在一些第三方库会主动抛出空的 Error ，例如 https://github.com/BrunoCesarAngst/daoo/blob/32e85c7799f9929685be299f07011fe4afa72f9d/aula13/node_modules/bluebird/js/release/async.js#L3
         * 这会导致上报的异常信息干扰非常大，无法正常基于堆栈排查问题
         * 所以这里过滤掉 message 为空的 Error
         */
        if (err && err.message) {
            this.reporterService.point(ide_core_common_1.REPORT_NAME.RUNTIME_ERROR_EXTENSION, extension.id, {
                stackTraceMessage,
                error: err.message,
                version: (_a = extension.packageJSON) === null || _a === void 0 ? void 0 : _a.version,
            });
            this.logger.error(`extension ${extension.id} throw error`, err.message);
        }
    }
    async activateExtension(id) {
        const extension = this.extensions.find((ext) => ext.id === id);
        if (!extension) {
            this.logger.error(`extension ${id} not found`);
            return;
        }
        await this.localization.initializeLocalizedMessages(extension);
        if (this.extensionsActivator.get(id)) {
            this.logger.warn(`extension ${id} is already activated.`);
            return;
        }
        const isSumiContributes = this.containsSumiContributes(extension);
        const modulePath = extension.path;
        this.logger.debug(`active ${extension.name} from ${modulePath}`);
        this.logger.debug(`active extension host process by ${modulePath}`);
        const extendProxy = this.getExtendModuleProxy(extension, isSumiContributes);
        const context = await this.loadExtensionContext(extension, modulePath, this.storage, this.secret, extendProxy);
        let activationFailed = false;
        let activationFailedError = null;
        let extendModule;
        let exportsData;
        let extendExports;
        let extensionModule = {};
        if (extension.packageJSON.main) {
            const reportTimer = this.reporterService.time(ide_core_common_1.REPORT_NAME.LOAD_EXTENSION_MAIN);
            try {
                extensionModule = (0, utils_1.getNodeRequire)()(modulePath);
                reportTimer.timeEnd(extension.id);
            }
            catch (error) {
                activationFailed = true;
                activationFailedError = error;
                this.logger.error(`active extension ${extension.id} failure by\n${error}`);
            }
            if (extensionModule.activate) {
                this.logger.debug(`try activate ${extension.name}`);
                // FIXME: 考虑在 Context 这里直接注入服务注册的能力
                try {
                    const reportTimer = this.reporterService.time(ide_core_common_1.REPORT_NAME.ACTIVE_EXTENSION);
                    const extensionExports = (await extensionModule.activate(context)) || extensionModule;
                    reportTimer.timeEnd(extension.id, {
                        version: extension.packageJSON.version,
                    });
                    exportsData = extensionExports;
                }
                catch (error) {
                    activationFailed = true;
                    activationFailedError = error;
                    this.logger.error(`active extension ${extension.id} failure by\n${error}`);
                }
            }
        }
        if (extension.packageJSON.sumiContributes && extension.packageJSON.sumiContributes.nodeMain) {
            try {
                const reportTimer = this.reporterService.time(ide_core_common_1.REPORT_NAME.ACTIVE_EXTENSION);
                extendModule = (0, utils_1.getNodeRequire)()((0, path_1.join)(extension.path, extension.packageJSON.sumiContributes.nodeMain));
                reportTimer.timeEnd(extension.id, {
                    version: extension.packageJSON.version,
                });
            }
            catch (error) {
                activationFailed = true;
                activationFailedError = error;
                this.reportRuntimeError(error, extension, error.stack);
                this.logger.error(`active extension ${extension.id} failure by\n${error}`);
            }
        }
        else if (extension.extendConfig && extension.extendConfig.node && extension.extendConfig.node.main) {
            extendModule = (0, utils_1.getNodeRequire)()((0, path_1.join)(extension.path, extension.extendConfig.node.main));
            if (!extendModule) {
                this.logger.warn(`Can not find extendModule ${extension.id}`);
            }
        }
        if (extendModule && extendModule.activate) {
            try {
                const extendModuleExportsData = await extendModule.activate(context);
                extendExports = extendModuleExportsData;
            }
            catch (error) {
                activationFailed = true;
                activationFailedError = error;
                this.reportRuntimeError(error, extension, error.stack);
                this.logger.error(`active extension extend module failure by\n${error}`);
            }
        }
        this.extensionsActivator.set(id, new activator_1.ActivatedExtension(id, extension.packageJSON.displayName || extension.name, extension.packageJSON.description || '', 'node', activationFailed, activationFailedError, extensionModule, exportsData, context.subscriptions, undefined, extendExports, extendModule));
        // 如果有异常，则向上抛出
        if (activationFailedError) {
            throw activationFailedError;
        }
    }
    getExtensionViewModuleProxy(extension, viewsProxies) {
        return viewsProxies.reduce((proxies, viewId) => {
            proxies[viewId] = this.rpcProtocol.getProxy({
                serviceId: `${common_1.EXTENSION_EXTEND_SERVICE_PREFIX}:${extension.id}:${viewId}`,
            });
            proxies[viewId] = new Proxy(proxies[viewId], {
                get: (obj, prop) => {
                    if (typeof prop === 'symbol') {
                        return obj[prop];
                    }
                    return obj[`$${prop}`];
                },
            });
            return proxies;
        }, {});
    }
    /**
     * @example
     * "sumiContributes": {
     *  "viewsProxies": ["ViewComponentID"],
     * }
     */
    getExtendModuleProxy(extension, isSumiContributes) {
        if (isSumiContributes &&
            extension.packageJSON.sumiContributes &&
            extension.packageJSON.sumiContributes.viewsProxies) {
            return this.getExtensionViewModuleProxy(extension, extension.packageJSON.sumiContributes.viewsProxies);
        }
        else if (extension.extendConfig && extension.extendConfig.browser && extension.extendConfig.browser.componentId) {
            return this.getExtensionViewModuleProxy(extension, extension.extendConfig.browser.componentId);
        }
        else {
            return {};
        }
    }
    registerExtendModuleService(exportsData, extension) {
        const service = {};
        for (const key in exportsData) {
            // eslint-disable-next-line no-prototype-builtins
            if (exportsData.hasOwnProperty(key)) {
                if (typeof exportsData[key] === 'function') {
                    service[`$${key}`] = exportsData[key];
                }
            }
        }
        this.logger.debug('extension extend service', extension.id, 'service', service);
        this.rpcProtocol.set(new ide_connection_1.ProxyIdentifier(`${common_1.EXTENSION_EXTEND_SERVICE_PREFIX}:${extension.id}`), service);
    }
    async $activateExtension(id) {
        return this.activateExtension(id);
    }
    async loadExtensionContext(extensionDescription, modulePath, storageProxy, secretProxy, extendProxy) {
        const extensionId = extensionDescription.id;
        const registerExtendFn = (exportsData) => this.registerExtendModuleService(exportsData, extensionDescription);
        const exthostTermianl = this.rpcProtocol.get(vscode_1.ExtHostAPIIdentifier.ExtHostTerminal);
        const context = new ext_host_extensions_1.ExtensionContext({
            extensionDescription,
            extensionId,
            createExtension: this.createExtension.bind(this),
            extensionPath: modulePath,
            extensionLocation: extensionDescription.extensionLocation,
            storageProxy,
            secretProxy,
            extendProxy,
            registerExtendModuleService: registerExtendFn,
            exthostTerminal: exthostTermianl,
        });
        return Promise.all([context.globalState.whenReady, context.workspaceState.whenReady]).then(() => Object.freeze(context));
    }
}
exports.default = ExtensionHostServiceImpl;
//# sourceMappingURL=ext.host.js.map