UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

465 lines (463 loc) 18.6 kB
import { Subject } from 'rxjs'; import { CoreEnvironment } from '../data/core-environment'; import { NativeQ } from '../data/native-q'; import { LogLevel } from '../diagnostics/log-level'; import { Logging } from '../diagnostics/logging'; import { SmeWebTelemetry } from '../diagnostics/sme-web-telemetry'; import { RpcBase, RpcInboundCommands, RpcOutboundCommands, RpcType, rpcVersion } from './rpc-base'; import { RpcManager } from './rpc-manager'; import { RpcSeekMode } from './seek/rpc-seek-model'; /** * The Rpc class. */ export class Rpc { // RPC timeout 10 seconds. static rpcTimeout = 10 * 1000; /** * This subject is updated whenever there's new reported data */ rpcSubjects; /** * Deferred response collection. */ deferredCollection = new Map(); /** * Active status of rpc by Observable. */ stateChangedInternal = new Subject(); /** * Active status of rpc. */ stateActiveInternal = false; /** * Inbound module handlers to process when rpc is called. * - called from Module to Shell. */ rpcInboundHandlers = { FailedHandler: (data) => { return this.processNextForSubject(RpcInboundCommands.Failed, data); }, SeekHandler: (data) => { return Promise.resolve({ name: data.sourceName, subName: data.sourceSubName }); } }; /** * Outbound shell handlers to process when rpc is called. * - called from Shell to Module. * - if code reached a handler, module is not ready yet. * set timeout for RPC call. */ rpcOutboundHandlers = { InitHandler: (data) => this.createTimerPromise(RpcOutboundCommands.Init, Rpc.rpcTimeout, { locale: data.locale }), OpenHandler: (rpcOpenData) => this.createTimerPromise(RpcOutboundCommands.Open, Rpc.rpcTimeout, rpcOpenData), ActivateHandler: (data) => this.createTimerPromise(RpcOutboundCommands.Activate, Rpc.rpcTimeout, data), Deactivate2Handler: (data) => this.createTimerPromise(RpcOutboundCommands.Deactivate2, Rpc.rpcTimeout, data), ShutdownHandler: (data) => this.createTimerPromise(RpcOutboundCommands.Shutdown, Rpc.rpcTimeout, data), PingHandler: () => { this.changeActiveState(true); return Promise.resolve({ name: 'pong' }); } }; /** * Rpc manager object. */ rpcManager = new RpcManager(); /** * Initializes a new instance of the Rpc class. * * @param http the Http class instance injected. */ constructor() { this.rpcSubjects = {}; const commands = Object.keys(RpcInboundCommands); commands.forEach((value) => { this.rpcSubjects[RpcInboundCommands[value]] = new Subject(); }); } /** * Gets observable to watch the state change. */ get stateChanged() { this.stateChangedInternal.next(this.stateActiveInternal); return this.stateChangedInternal.asObservable(); } /** * Gets the state of rpc. */ get stateActive() { return this.stateActiveInternal; } /** * Gets whether rpc is running on the shell. */ get isShell() { if (this.rpcManager.rpcChannel == null) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcNotInitialized.message; throw new Error(message); } return this.rpcManager.rpcChannel.rpcMode === 0 /* RpcMode.Shell */; } /** * Register inbound command handler. * * @param command The command name. * @param handler The command handler. */ registerInboundHandler(command, handler) { const handlerName = RpcBase.commandToHandlerName(command); this.rpcInboundHandlers[handlerName] = handler; this.rpcManager.registerInboundHandler(command, handler); } /** * Register outbound command handler. * * @param command The command name. * @param handler The command handler. */ registerOutboundHandler(command, handler) { const handlerName = RpcBase.commandToHandlerName(command); this.rpcOutboundHandlers[handlerName] = handler; } /** * Initializes Rpc configuration */ init() { this.rpcManager.init(this.rpcInboundHandlers, this.rpcOutboundHandlers); if (this.isShell) { this.changeActiveState(true); } } /*************************************************************** * Section for Shell usage. ***************************************************************/ /** * This updates its value every time there's a reported data from the rpc channel */ moduleSubjects(commandType) { return this.rpcSubjects[RpcInboundCommands[commandType]]; } /** * Connect a module with name and iframe. * - start pinging to iframe to wait for response. * * @param name the name of the module. * @param path the path to open the module the module name. * @param iframe the iframe window object. * @param primary the primary window to affect router url. * @return Promise<string> the promise with subName created for the window. */ moduleConnect(name, path, iframe, primary) { return this.rpcManager.connectRpcOutbound(name, path, iframe, primary); } /** * Init the module. * * @param name the name of module. * @param subName the sub name of rpc channel. * @param entryPointType the entry point type. * @return Promise<void> the promise object of init result. */ moduleInit(name, subName, entryPoint) { const rpc = this.rpcManager.rpcChannel.getRpc(name, subName, RpcType.Outbound); const self = MsftSme.self(); const data = { entryPointType: entryPoint.entryPointType, entryPointName: entryPoint.name, theme: self.Resources.theme, assets: self.Resources.assets, moduleAssets: self.Resources.moduleAssets, locale: self.Resources.localeId, localeRegional: self.Resources.localeRegionalId, lib: self.Resources.lib, sessionId: self.Init.sessionId, modules: self.Environment.modules, accessibilityMode: self.Resources.accessibilityMode, sessionExpiration: self.Init.sessionExpiration, performanceProfile: MsftSme.getPerformanceProfile(), connectivityLevel: self.Init.connectivityLevel, consoleDebug: MsftSme.consoleDebug(), sideLoad: MsftSme.sideLoad(), experiments: MsftSme.experiments(), shellVersion: self.Init.shellVersion, gatewayApiVersion: self.Init.gatewayApiVersion, gatewayPlatform: self.Init.gatewayPlatform }; return rpc.init(data).then(result => { const strings = MsftSme.getStrings().MsftSmeShell.Core.Rpc; // copy the version string of remote module. if (result && result.version) { rpc.version = result.version; Logging.log({ level: LogLevel.Verbose, message: strings.Version.message.format(rpcVersion, name, rpc.version), source: 'rpc.moduleInit' }); } else { const message = strings.MissingVersion.message.format(name); Logging.log({ level: LogLevel.Critical, message, source: 'rpc.moduleInit' }); throw new Error(message); } this.changeActiveState(true); return result; }); } /** * Open the module by specifying the path and parameters. * * @param name the name of module. * @param subName the sub name of rpc channel. * @param path the open path. * @return Promise<RpcOpenResult> the promise object of RpcOpenResult. */ moduleOpen(name, subName, path) { const rpc = this.rpcManager.rpcChannel.getRpc(name, subName, RpcType.Outbound); return rpc.open({ path }).then(result => { this.changeActiveState(true); return result; }); } /** * Activate the module to start receiving data. * * @param name the module name. * @param subName the sub name of rpc channel. * @param primary the primary window to affect router url. * @param url the inner url to open. * @return Promise<void> the promise of activation result. */ moduleActivate(name, subName, primary, url) { const rpc = this.rpcManager.reconnectRpcOutbound(name, subName, primary); return rpc.activate({ url }) .then(data => { this.changeActiveState(true); return data; }); } /** * Deactivate 2 the module to stop receiving data. * * @param name the module name. * @param subName the sub name of rpc channel. * @return Promise<void> the promise of deactivation result. */ moduleDeactivate2(name, subName) { this.changeActiveState(false); const rpc = this.rpcManager.rpcChannel.getRpc(name, subName, RpcType.Outbound); return rpc.deactivate2({}); } /** * Request to shutdown the module. * * @param name the module name. * @param subName the sub name of rpc channel. * @param primary the primary window to affect router url. * @param force the forcible state. * @return Promise<RpcShutdownResult> the promise object of result. */ moduleShutdown(name, subName, primary, force) { const rpc = this.rpcManager.reconnectRpcOutbound(name, subName, primary); return rpc.shutdown({ force: force }); } /** * Remove the module from the rpc channel. * * @param name the module name. * @param subName the sub name of rpc channel. */ moduleRemove(name, subName) { this.rpcManager.removeRpcOutbound(name, subName); } /** * Get module version string. * * @param name the name of module. * @param subName the sub name of rpc channel. * @return string the RPC version of module. */ moduleVersion(name, subName) { const rpc = this.rpcManager.rpcChannel.getRpc(name, subName, RpcType.Outbound); return rpc && rpc.version; } register(command, handler) { if (typeof command === 'string') { const commandName = command; this.rpcManager.rpcInbound.register(commandName, handler); return; } const deferredData = this.deferredCollection[command]; if (deferredData) { delete this.deferredCollection[command]; handler(deferredData.data).then(deferredData.deferred.resolve, deferredData.deferred.reject); return; } if (command === RpcOutboundCommands.Init) { const modifiedHandler = (data) => { // node context is used before passing request to handler. this.changeActiveState(true); const self = MsftSme.self(); self.Init.entryPointType = data.entryPointType; self.Init.entryPointName = data.entryPointName; self.Init.sessionId = data.sessionId; self.Init.sessionExpiration = data.sessionExpiration; self.Init.performanceProfile = data.performanceProfile; self.Init.shellVersion = data.shellVersion; // default is 1.0.0 and Windows which indicate it's running legacy GW. self.Init.gatewayApiVersion = data.gatewayApiVersion ?? '1.0.0', self.Init.gatewayPlatform = data.gatewayPlatform ?? 'Windows'; self.Environment.modules = data.modules; self.Init.connectivityLevel = data.connectivityLevel; self.Resources.lib = data.lib; self.Resources.moduleAssets = data.moduleAssets; if (!MsftSme.isNullOrUndefined(self.Resources.accessibilityMode) && !MsftSme.isNullOrUndefined(data.accessibilityMode) && CoreEnvironment.accessibilityManager) { self.Resources.accessibilityMode = data.accessibilityMode; CoreEnvironment.accessibilityManager.changeAccessibilityMode(self.Resources.accessibilityMode); } // ensure that global environment settings persist across origins if (!MsftSme.isNullOrUndefined(data.consoleDebug) && MsftSme.consoleDebug() !== data.consoleDebug) { MsftSme.consoleDebug(data.consoleDebug); } if (!MsftSme.isNullOrUndefined(data.sideLoad)) { const sideLoadKeys = Object.keys(MsftSme.sideLoad() || {}); const dataSideLoadKeys = Object.keys(data.sideLoad || {}); if (sideLoadKeys.length !== dataSideLoadKeys.length || sideLoadKeys.some(key => !!data.sideLoad[key])) { MsftSme.sideLoadReset(); dataSideLoadKeys.forEach(k => MsftSme.sideLoad(k)); } } if (!MsftSme.isNullOrUndefined(data.experiments)) { const experiments = MsftSme.experiments(); if (experiments.length !== data.experiments.length || experiments.some((v, i) => v !== data.experiments[i])) { MsftSme.experiments(data.experiments); } } if (CoreEnvironment.assetManager && data.theme && data.assets) { CoreEnvironment.assetManager.loadAssets(data.theme, data.assets); } for (const item of data.modules) { // NOTES: the next 'if' block code can be removed only if all extensions adapted newer shell. (after 1.1114.0) if (!self.Init.shellVersion && item.name === 'msft.sme.shell-extensions') { // look for shell version if missing from older MSI/ShellUI. self.Init.shellVersion = item.version; } if (item.name === self.Init.moduleName) { self.Environment.version = item.version || self.Environment.version; } } const localeLoader = CoreEnvironment.moduleLoadLocale({ id: data.localeRegional || data.locale, neutral: data.locale }); const passingData = { locale: data.locale, localeRegional: data.localeRegional, sessionId: data.sessionId }; // update metaTags so sessionId is properly updated in telemetry SmeWebTelemetry.updateFromRpcInit({ 'wac-session-id': data.sessionId, 'wac-extension-version': self.Environment.version }); return localeLoader .then(() => this.seekShell(RpcSeekMode.Create)) .then(() => handler(passingData)) .then(() => ({ version: rpcVersion })); }; this.rpcManager.rpcInbound.register(RpcOutboundCommands[command], modifiedHandler); } else { this.rpcManager.rpcInbound.register(RpcOutboundCommands[command], handler); } } /** * Module report a failure. */ failed(data) { const rpc = this.rpcManager.rpcInbound; return rpc.failed(data); } /** * Seek shell frame. * * @param Promise<any> the promise object. */ seekShell(mode) { return this.rpcManager.seekShell(mode); } /** * Validate existing outbound connection and remove if it doesn't live anymore. * * @return number the count of removed outbound. */ validate() { let count = 0; const collection = this.rpcManager.getCurrentRpcOutbound(); collection.forEach((rpc) => { if (!this.rpcManager.rpcChannel.validate(rpc)) { count++; } }); return count; } /** * Change the active status of rpc. */ changeActiveState(state) { this.stateActiveInternal = state; this.stateChangedInternal.next(state); } /** * Create auto-failed timer promise. * * @param command the outbound command type. * @param timeoutMs the timeout milliseconds. * @param data the data context. * @return Promise<any> the promise. */ createTimerPromise(command, timeoutMs, data) { const deferred = NativeQ.defer(); this.deferredCollection[command] = { deferred: deferred, data: data }; setTimeout(() => { const deferredData = this.deferredCollection[command]; if (deferredData) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcTimeout.message; deferredData.deferred.reject(message); delete this.deferredCollection[command]; } }, timeoutMs); return deferred.promise; } /** * Create promise that does not timeout. * * @param command the outbound type. * @param data the data context. * @return Promise<any> the promise. */ createPromise(command, data) { const deferred = NativeQ.defer(); this.deferredCollection[command] = { deferred: deferred, data: data }; return deferred.promise; } /** * Process data pushing into next call of subject with deferred data type. * * @param command the inbound command type. * @param data the rpc data came from a module/iframe. * @return Promise the promise which receiver must settle within fixed waiting time (10 seconds) */ processNextForSubject(command, data) { const subject = this.rpcSubjects[RpcInboundCommands[command]]; if (subject.closed) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcSubjectClosed.message; throw new Error(message.format(RpcInboundCommands[command])); } const deferredData = { data: data, deferred: NativeQ.defer() }; subject.next(deferredData); return deferredData.deferred.promise; } } //# sourceMappingURL=rpc.js.map