"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtensionWorkerHost = void 0;
exports.initRPCProtocol = initRPCProtocol;
const ide_connection_1 = require("@opensumi/ide-connection");
const message_port_1 = require("@opensumi/ide-connection/lib/common/connection/drivers/message-port");
const ide_core_common_1 = require("@opensumi/ide-core-common");
const common_1 = require("../common");
const activator_1 = require("../common/activator");
const vscode_1 = require("../common/vscode");
const protocols_1 = require("../common/vscode/protocols");
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 worker_host_api_impl_1 = require("./api/worker/worker.host.api.impl");
const extension_log_1 = require("./extension-log");
const vscode_extension_1 = require("./vscode.extension");
function initRPCProtocol() {
    const channel = new MessageChannel();
    self.postMessage(channel.port2, [channel.port2]);
    const msgPortConnection = new message_port_1.MessagePortConnection(channel.port1);
    const extProtocol = new ide_connection_1.SumiConnectionMultiplexer(msgPortConnection, {
        io: (0, ide_connection_1.createExtMessageIO)(protocols_1.knownProtocols),
    });
    return extProtocol;
}
class ExtensionWorkerHost {
    constructor(rpcProtocol, injector) {
        this.rpcProtocol = rpcProtocol;
        this.injector = injector;
        this.sumiExtAPIImpl = new Map();
        this.initDeferred = new ide_core_common_1.Deferred();
        this.activatedExtensions = new Map();
        this.extensionsChangeEmitter = new ide_core_common_1.Emitter();
        this._extHostErrorStackTraceExtended = false;
        const reporter = this.injector.get(ide_core_common_1.IReporter);
        this.logger = new extension_log_1.ExtensionLogger(rpcProtocol);
        this.storage = new ext_host_storage_1.ExtHostStorage(rpcProtocol);
        this.secret = new ext_host_secrets_1.ExtHostSecret(rpcProtocol);
        this.sumiAPIFactory = (0, worker_host_api_impl_1.createAPIFactory)(this.rpcProtocol, this);
        this.mainThreadExtensionService = this.rpcProtocol.getProxy(vscode_1.MainThreadAPIIdentifier.MainThreadExtensionService);
        rpcProtocol.set(vscode_1.ExtHostAPIIdentifier.ExtHostStorage, this.storage);
        this.reporterService = new ide_core_common_1.ReporterService(reporter, {
            host: ide_core_common_1.REPORT_HOST.WORKER,
        });
    }
    async $getActivatedExtensions() {
        return Array.from(this.activatedExtensions.values()).map((e) => e.toJSON());
    }
    async init() {
        this.staticServicePath = await this.mainThreadExtensionService.$getStaticServicePath();
    }
    getExtensionExports(id) {
        var _a;
        return (_a = this.activatedExtensions.get(id)) === null || _a === void 0 ? void 0 : _a.exports;
    }
    getExtensions() {
        return this.extensions.map((ext) => new vscode_extension_1.KTWorkerExtension(ext, this, this.mainThreadExtensionService, this.getExtensionExports(ext.id)));
    }
    createExtension(extensionDescription) {
        const activated = this.activatedExtensions.get(extensionDescription.id);
        return new vscode_extension_1.KTWorkerExtension(extensionDescription, this, this.mainThreadExtensionService, activated === null || activated === void 0 ? void 0 : activated.exports);
    }
    getExtension(extensionId) {
        const extension = this.extensions.find((e) => e.id === extensionId);
        if (extension) {
            return this.createExtension(extension);
        }
    }
    isActivated(id) {
        return this.activatedExtensions.has(id);
    }
    async $updateExtHostData() {
        await this.init();
        const extensions = await this.mainThreadExtensionService.$getExtensions();
        this.extensions = extensions.map((ext) => ({
            ...ext,
            identifier: new vscode_1.ExtensionIdentifier(ext.id),
            extensionLocation: ide_core_common_1.Uri.from(ext.extensionLocation),
        }));
        this.logger.verbose('worker $handleExtHostCreated', this.extensions.map((extension) => extension.packageJSON.name));
        this.extendExtHostErrorStackTrace();
        this.initDeferred.resolve(undefined);
    }
    extendExtHostErrorStackTrace() {
        if (this._extHostErrorStackTraceExtended) {
            return;
        }
        this._extHostErrorStackTraceExtended = true;
        Error.stackTraceLimit = 100;
        Error.prepareStackTrace = (error, stackTrace) => {
            let extension;
            let stackTraceMessage = '';
            for (const call of stackTrace) {
                stackTraceMessage += `\n\tat ${call.toString()}`;
                if (call.isEval() && !extension) {
                    const scriptPath = call.getEvalOrigin();
                    const maybeExtension = this.findExtensionFormScriptPath(scriptPath);
                    if (maybeExtension) {
                        extension = maybeExtension;
                        const columnNumber = call.getColumnNumber();
                        const lineNumber = call.getLineNumber();
                        stackTraceMessage =
                            `\n\tat ${extension.name} (${extension.workerScriptPath}:${lineNumber}:${columnNumber})` +
                                stackTraceMessage;
                    }
                }
            }
            if (extension) {
                const traceMessage = `${extension && extension.name} - ${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
                this.reportRuntimeError(error, extension, traceMessage);
                return traceMessage;
            }
            return error.stack;
        };
    }
    reportRuntimeError(err, extension, stackTraceMessage) {
        var _a;
        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,
            });
        }
    }
    findExtensionFormScriptPath(scriptPath) {
        return this.extensions.find((extension) => extension.workerScriptPath === scriptPath);
    }
    getExtendModuleProxy(extension) {
        /**
         * @example
         * "sumiContributes": {
         *  "viewsProxies": ["ViewComponentID"],
         * }
         */
        if (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 {};
        }
    }
    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;
        }, {});
    }
    registerExtendModuleService(exportsData, extension) {
        const service = {};
        for (const key in exportsData) {
            if (exportsData.hasOwnProperty(key)) {
                if (typeof exportsData[key] === 'function') {
                    service[`$${key}`] = exportsData[key];
                }
            }
        }
        this.rpcProtocol.set({ serviceId: `${common_1.EXTENSION_EXTEND_SERVICE_PREFIX}:${extension.id}` }, service);
    }
    async loadContext(extensionDescription) {
        const componentProxy = this.getExtendModuleProxy(extensionDescription);
        const registerExtendFn = (exportsData) => this.registerExtendModuleService(exportsData, extensionDescription);
        const context = new ext_host_extensions_1.ExtensionContext({
            extensionDescription,
            createExtension: this.createExtension.bind(this),
            extensionId: extensionDescription.id,
            extendProxy: componentProxy,
            registerExtendModuleService: registerExtendFn,
            extensionPath: extensionDescription.realPath,
            storageProxy: this.storage,
            secretProxy: this.secret,
            extensionLocation: extensionDescription.extensionLocation,
        });
        return Promise.all([context.globalState.whenReady, context.workspaceState.whenReady]).then(() => Object.freeze(context));
    }
    async $activateExtension(id) {
        await this.initDeferred.promise;
        return this.activateExtension(id);
    }
    async activateExtension(id) {
        const extension = this.extensions.find((extension) => extension.id === id);
        if (!extension) {
            this.logger.error(`[Worker-Host] extension worker not found ${id} `);
            return;
        }
        this.logger.verbose(`[Worker-Host] extension worker start activate ${id} ${extension.workerScriptPath}`);
        if (extension.workerScriptPath) {
            const response = await fetch(decodeURIComponent(extension.workerScriptPath));
            if (response.status !== 200) {
                this.logger.error(response.statusText);
                return;
            }
            // https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Debug_eval_sources
            const initFn = new Function('module', 'exports', 'require', 'window', (await response.text()) + `\n//# sourceURL=${extension.workerScriptPath}`);
            const _exports = {};
            const _module = { exports: _exports };
            const _require = (request) => {
                if (ExtensionWorkerHost.workerApiNamespace.includes(request)) {
                    let sumiAPIImpl = this.sumiExtAPIImpl.get(id);
                    if (!sumiAPIImpl) {
                        try {
                            sumiAPIImpl = this.sumiAPIFactory(extension);
                            this.sumiExtAPIImpl.set(id, sumiAPIImpl);
                        }
                        catch (e) {
                            this.logger.error('[Worker-Host] worker error');
                            this.logger.error(e);
                        }
                    }
                    return sumiAPIImpl;
                }
            };
            try {
                initFn(_module, _exports, _require, self);
            }
            catch (err) {
                this.logger.error(`[Worker-Host] failed to initialize extension ${extension.id} \n`, err);
            }
            let extensionActivateFailed;
            let moduleExports;
            if (_module.exports && _module.exports.activate) {
                const workerExtContext = await this.loadContext(extension);
                try {
                    moduleExports = await _module.exports.activate(Object.freeze(workerExtContext));
                }
                catch (err) {
                    extensionActivateFailed = err;
                    this.logger.error(`[Worker-Host] failed to activate extension ${extension.id} \n\n ${err.message}`);
                }
                const activatedExtension = new activator_1.ActivatedExtension(id, extension.packageJSON.displayName || extension.name, extension.packageJSON.description || '', 'worker', !!extensionActivateFailed, extensionActivateFailed, _module.exports, moduleExports, workerExtContext.subscriptions, undefined, undefined, undefined);
                this.activatedExtensions.set(id, activatedExtension);
            }
        }
        else {
            this.logger.error('[Worker-Host] extension worker activate error', extension);
        }
    }
}
exports.ExtensionWorkerHost = ExtensionWorkerHost;
ExtensionWorkerHost.workerApiNamespace = [
    'sumi',
    'sumi-browser',
    // @deprecated
    'kaitian',
    // @deprecated
    'kaitian-worker',
    'vscode',
];
//# sourceMappingURL=worker.host.js.map