UNPKG

@itwin/core-backend

Version:
515 lines • 25.5 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module IModelHost */ Object.defineProperty(exports, "__esModule", { value: true }); exports.FileNameResolver = exports.KnownLocations = exports.Platform = exports.IModelHost = exports.IModelHostConfiguration = void 0; // To avoid circular load errors, the "Element" classes must be loaded before IModelHost. require("./IModelDb"); // DO NOT REMOVE OR MOVE THIS LINE! const NativePlatform_1 = require("./internal/NativePlatform"); const os = require("os"); require("reflect-metadata"); // this has to be before @itwin/object-storage-* and @itwin/cloud-agnostic-core imports because those packages contain decorators that use this polyfill. const imodeljs_native_1 = require("@bentley/imodeljs-native"); const cloud_agnostic_core_1 = require("@itwin/cloud-agnostic-core"); const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const object_storage_azure_1 = require("@itwin/object-storage-azure"); const object_storage_core_1 = require("@itwin/object-storage-core"); const BackendLoggerCategory_1 = require("./BackendLoggerCategory"); const BisCoreSchema_1 = require("./BisCoreSchema"); const BriefcaseManager_1 = require("./BriefcaseManager"); const CloudSqlite_1 = require("./CloudSqlite"); const FunctionalSchema_1 = require("./domains/FunctionalSchema"); const GenericSchema_1 = require("./domains/GenericSchema"); const GeoCoordConfig_1 = require("./GeoCoordConfig"); const IModelJsFs_1 = require("./IModelJsFs"); const DevToolsRpcImpl_1 = require("./rpc-impl/DevToolsRpcImpl"); const IModelReadRpcImpl_1 = require("./rpc-impl/IModelReadRpcImpl"); const IModelTileRpcImpl_1 = require("./rpc-impl/IModelTileRpcImpl"); const SnapshotIModelRpcImpl_1 = require("./rpc-impl/SnapshotIModelRpcImpl"); const RpcBackend_1 = require("./RpcBackend"); const TileStorage_1 = require("./TileStorage"); const Settings_1 = require("./workspace/Settings"); const inversify_1 = require("inversify"); const path_1 = require("path"); const WorkspaceImpl_1 = require("./internal/workspace/WorkspaceImpl"); const SettingsImpl_1 = require("./internal/workspace/SettingsImpl"); const SettingsSchemasImpl_1 = require("./internal/workspace/SettingsSchemasImpl"); const Symbols_1 = require("./internal/Symbols"); const loggerCategory = BackendLoggerCategory_1.BackendLoggerCategory.IModelHost; /** Configuration of core-backend. * @public */ class IModelHostConfiguration { static defaultTileRequestTimeout = 20 * 1000; static defaultLogTileLoadTimeThreshold = 40; static defaultLogTileSizeThreshold = 20 * 1000000; /** @internal */ static defaultMaxTileCacheDbSize = 1024 * 1024 * 1024; appAssetsDir; cacheDir; /** @beta */ workspace; hubAccess; /** The AuthorizationClient used to obtain [AccessToken]($bentley)s. */ authorizationClient; /** @beta */ restrictTileUrlsByClientIp; compressCachedTiles; /** @beta */ tileCacheAzureCredentials; /** @internal */ tileTreeRequestTimeout = IModelHostConfiguration.defaultTileRequestTimeout; /** @internal */ tileContentRequestTimeout = IModelHostConfiguration.defaultTileRequestTimeout; /** @internal */ logTileLoadTimeThreshold = IModelHostConfiguration.defaultLogTileLoadTimeThreshold; /** @internal */ logTileSizeThreshold = IModelHostConfiguration.defaultLogTileSizeThreshold; /** @internal */ crashReportingConfig; /** * Configuration controlling whether to use the thinned down native instance functions for element, model, and aspect CRUD operations * or use the previous behavior of using the native side for all CRUD operations. Set to true to revert to the previous behavior. * @beta */ disableThinnedNativeInstanceWorkflow; } exports.IModelHostConfiguration = IModelHostConfiguration; /** * Settings for `IModelHost.appWorkspace`. * @note this includes the default dictionary from the SettingsSpecRegistry */ class ApplicationSettings extends SettingsImpl_1.SettingsImpl { _remove; verifyPriority(priority) { if (priority > Settings_1.SettingsPriority.application) // only application or lower may appear in ApplicationSettings throw new Error("Use IModelSettings"); } updateDefaults() { const defaults = {}; for (const [schemaName, val] of IModelHost.settingsSchemas.settingDefs) { if (val.default) defaults[schemaName] = val.default; } this.addDictionary({ name: "_default_", priority: 0 }, defaults); } constructor() { super(); this._remove = IModelHost.settingsSchemas.onSchemaChanged.addListener(() => this.updateDefaults()); this.updateDefaults(); } close() { if (this._remove) { this._remove(); this._remove = undefined; } } } const definedInStartup = (obj) => { if (obj === undefined) throw new Error("IModelHost.startup must be called first"); return obj; }; /** IModelHost initializes ($backend) and captures its configuration. A backend must call [[IModelHost.startup]] before using any backend classes. * See [the learning article]($docs/learning/backend/IModelHost.md) * @public */ class IModelHost { constructor() { } /** The AuthorizationClient used to obtain [AccessToken]($bentley)s. */ static authorizationClient; static backendVersion = ""; static _profileName; static _cacheDir = ""; static _settingsSchemas; static _appWorkspace; // Omit the hubAccess field from configuration so it stays internal. static configuration; /** * The name of the *Profile* directory (a subdirectory of "[[cacheDir]]/profiles/") for this process. * * The *Profile* directory is used to cache data that is specific to a type-of-usage of the iTwin.js library. * It is important that information in the profile cache be consistent but isolated across sessions (i.e. * data for a profile is maintained between runs, but each profile is completely independent and * unaffected by the presence or use of others.) * @note **Only one process at a time may be using a given profile**, and an exception will be thrown by [[startup]] * if a second process attempts to use the same profile. * @beta */ static get profileName() { return this._profileName; } /** The full path of the Profile directory. * @see [[profileName]] * @beta */ static get profileDir() { return (0, path_1.join)(this._cacheDir, "profiles", this._profileName); } /** Event raised during startup to allow loading settings data */ static onWorkspaceStartup = new core_bentley_1.BeEvent(); /** Event raised just after the backend IModelHost was started */ static onAfterStartup = new core_bentley_1.BeEvent(); /** Event raised just before the backend IModelHost is to be shut down */ static onBeforeShutdown = new core_bentley_1.BeEvent(); /** @internal */ static session = { applicationId: "2686", applicationVersion: "1.0.0", sessionId: "" }; /** A uniqueId for this session */ static get sessionId() { return this.session.sessionId; } static set sessionId(id) { this.session.sessionId = id; } /** The Id of this application - needs to be set only if it is an agent application. The applicationId will otherwise originate at the frontend. */ static get applicationId() { return this.session.applicationId; } static set applicationId(id) { this.session.applicationId = id; } /** The version of this application - needs to be set if is an agent application. The applicationVersion will otherwise originate at the frontend. */ static get applicationVersion() { return this.session.applicationVersion; } static set applicationVersion(version) { this.session.applicationVersion = version; } /** A string that can identify the current user to other users when collaborating. */ static userMoniker = "unknown"; /** Root directory holding files that iTwin.js caches */ static get cacheDir() { return this._cacheDir; } /** The application [[Workspace]] for this `IModelHost` * @note this `Workspace` only holds [[WorkspaceContainer]]s and [[Settings]] scoped to the currently loaded application(s). * All organization, iTwin, and iModel based containers or settings must be accessed through [[IModelDb.workspace]] and * attempting to add them to this Workspace will fail. * @beta */ static get appWorkspace() { return definedInStartup(this._appWorkspace); } /** The registry of schemas describing the [[Setting]]s for the application session. * Applications should register their schemas via methods like [[SettingsSchemas.addGroup]]. * @beta */ static get settingsSchemas() { return definedInStartup(this._settingsSchemas); } /** The optional [[FileNameResolver]] that resolves keys and partial file names for snapshot iModels. * @deprecated in 4.10 - will not be removed until after 2026-06-13. When opening a snapshot by file name, ensure to pass already resolved path. Using a key to open a snapshot is now deprecated. */ static snapshotFileNameResolver; // eslint-disable-line @typescript-eslint/no-deprecated /** Get the current access token for this IModelHost, or a blank string if none is available. * @note for web backends, this will *always* return a blank string because the backend itself has no token (but never needs one either.) * For all IpcHosts, where this backend is servicing a single frontend, this will be the user's token. For ElectronHost, the backend * obtains the token and forwards it to the frontend. * @note accessTokens expire periodically and are automatically refreshed, if possible. Therefore tokens should not be saved, and the value * returned by this method may change over time throughout the course of a session. */ static async getAccessToken() { try { return (await IModelHost.authorizationClient?.getAccessToken()) ?? ""; } catch { return ""; } } static loadNative(options) { (0, NativePlatform_1.loadNativePlatform)(); if (options.crashReportingConfig && options.crashReportingConfig.crashDir && !core_bentley_1.ProcessDetector.isElectronAppBackend && !core_bentley_1.ProcessDetector.isMobileAppBackend) { NativePlatform_1.IModelNative.platform.setCrashReporting(options.crashReportingConfig); core_bentley_1.Logger.logTrace(loggerCategory, "Configured crash reporting", { enableCrashDumps: options.crashReportingConfig?.enableCrashDumps, wantFullMemoryDumps: options.crashReportingConfig?.wantFullMemoryDumps, enableNodeReport: options.crashReportingConfig?.enableNodeReport, uploadToBentley: options.crashReportingConfig?.uploadToBentley, }); if (options.crashReportingConfig.enableNodeReport) { if (process.report !== undefined) { process.report.reportOnFatalError = true; process.report.reportOnUncaughtException = true; process.report.directory = options.crashReportingConfig.crashDir; core_bentley_1.Logger.logTrace(loggerCategory, "Configured Node.js crash reporting"); } else { core_bentley_1.Logger.logWarning(loggerCategory, "Unable to configure Node.js crash reporting"); } } } } /** @internal */ static tileStorage; static _hubAccess; /** @internal */ static [Symbols_1._setHubAccess](hubAccess) { this._hubAccess = hubAccess; } /** get the current hubAccess, if present. * @internal */ static [Symbols_1._getHubAccess]() { return this._hubAccess; } /** Provides access to the IModelHub for this IModelHost * @internal * @note If [[IModelHostOptions.hubAccess]] was undefined when initializing this class, accessing this property will throw an error. * To determine whether one is present, use [[_getHubAccess]]. */ static get [Symbols_1._hubAccess]() { if (IModelHost._hubAccess === undefined) throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "No BackendHubAccess supplied in IModelHostOptions"); return IModelHost._hubAccess; } static initializeWorkspace(configuration) { const settingAssets = (0, path_1.join)(KnownLocations.packageAssetsDir, "Settings"); this._settingsSchemas = (0, SettingsSchemasImpl_1.constructSettingsSchemas)(); this._settingsSchemas.addDirectory((0, path_1.join)(settingAssets, "Schemas")); this._appWorkspace = (0, WorkspaceImpl_1.constructWorkspace)(new ApplicationSettings(), configuration.workspace); // Create the CloudCache for Workspaces. This will fail if another process is already using the same profile. try { this.appWorkspace.getCloudCache(); } catch (e) { throw (e.errorNumber === core_bentley_1.DbResult.BE_SQLITE_BUSY) ? new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_BUSY, `Profile [${this.profileDir}] is already in use by another process`) : e; } this.appWorkspace.settings.addDirectory(settingAssets, Settings_1.SettingsPriority.defaults); GeoCoordConfig_1.GeoCoordConfig.onStartup(); // allow applications to load their default settings this.onWorkspaceStartup.raiseEvent(); } static _isValid = false; /** true between a successful call to [[startup]] and before [[shutdown]] */ static get isValid() { return IModelHost._isValid; } /** This method must be called before any iTwin.js services are used. * @param options Host configuration data. * Raises [[onAfterStartup]]. * @see [[shutdown]]. */ static async startup(options) { if (this._isValid) return; // we're already initialized this._isValid = true; options = options ?? {}; if (this.sessionId === "") this.sessionId = core_bentley_1.Guid.createValue(); this.authorizationClient = options.authorizationClient; this.backendVersion = require("../../package.json").version; // eslint-disable-line @typescript-eslint/no-require-imports (0, RpcBackend_1.initializeRpcBackend)(options.enableOpenTelemetry); this.loadNative(options); this.setupCacheDir(options); this.initializeWorkspace(options); BriefcaseManager_1.BriefcaseManager.initialize((0, path_1.join)(this._cacheDir, "imodels")); [ IModelReadRpcImpl_1.IModelReadRpcImpl, IModelTileRpcImpl_1.IModelTileRpcImpl, SnapshotIModelRpcImpl_1.SnapshotIModelRpcImpl, // eslint-disable-line @typescript-eslint/no-deprecated DevToolsRpcImpl_1.DevToolsRpcImpl, ].forEach((rpc) => rpc.register()); // register all of the RPC implementations [ BisCoreSchema_1.BisCoreSchema, GenericSchema_1.GenericSchema, FunctionalSchema_1.FunctionalSchema, ].forEach((schema) => schema.registerSchema()); // register all of the schemas const { hubAccess, ...otherOptions } = options; if (undefined !== hubAccess) this._hubAccess = hubAccess; this.configuration = otherOptions; this.setupTileCache(); process.once("beforeExit", IModelHost.shutdown); this.onAfterStartup.raiseEvent(); } static setupCacheDir(configuration) { this._cacheDir = (0, path_1.normalize)(configuration.cacheDir ?? imodeljs_native_1.NativeLibrary.defaultCacheDir); IModelJsFs_1.IModelJsFs.recursiveMkDirSync(this._cacheDir); this._profileName = configuration.profileName ?? "default"; core_bentley_1.Logger.logInfo(loggerCategory, `cacheDir: [${this.cacheDir}], profileDir: [${this.profileDir}]`); } /** This method must be called when an iTwin.js host is shut down. Raises [[onBeforeShutdown]] */ static async shutdown() { // Note: This method is set as a node listener where `this` is unbound. Call private method to // ensure `this` is correct. Don't combine these methods. return IModelHost.doShutdown(); } /** * Create a new iModel. * @returns the Guid of the newly created iModel. * @throws [IModelError]($common) in case of errors. * @note If [[IModelHostOptions.hubAccess]] was undefined in the call to [[startup]], this function will throw an error. */ static async createNewIModel(arg) { return this[Symbols_1._hubAccess].createNewIModel(arg); } static async doShutdown() { if (!this._isValid) return; this._isValid = false; this.onBeforeShutdown.raiseEvent(); this.configuration = undefined; this.tileStorage = undefined; this._appWorkspace?.close(); this._appWorkspace = undefined; this._settingsSchemas = undefined; CloudSqlite_1.CloudSqlite.CloudCaches.destroy(); process.removeListener("beforeExit", IModelHost.shutdown); } /** * Add or update a property that should be included in a crash report. * @internal */ static setCrashReportProperty(name, value) { NativePlatform_1.IModelNative.platform.setCrashReportProperty(name, value); } /** * Remove a previously defined property so that will not be included in a crash report. * @internal */ static removeCrashReportProperty(name) { NativePlatform_1.IModelNative.platform.setCrashReportProperty(name, undefined); } /** * Get all properties that will be included in a crash report. * @internal */ static getCrashReportProperties() { return NativePlatform_1.IModelNative.platform.getCrashReportProperties(); } /** The directory where application assets may be found */ static get appAssetsDir() { return undefined !== IModelHost.configuration ? IModelHost.configuration.appAssetsDir : undefined; } /** The time, in milliseconds, for which IModelTileRpcInterface.requestTileTreeProps should wait before returning a "pending" status. * @internal */ static get tileTreeRequestTimeout() { return IModelHost.configuration?.tileTreeRequestTimeout ?? IModelHostConfiguration.defaultTileRequestTimeout; } /** The time, in milliseconds, for which IModelTileRpcInterface.requestTileContent should wait before returning a "pending" status. * @internal */ static get tileContentRequestTimeout() { return IModelHost.configuration?.tileContentRequestTimeout ?? IModelHostConfiguration.defaultTileRequestTimeout; } /** The backend will log when a tile took longer to load than this threshold in seconds. */ static get logTileLoadTimeThreshold() { return IModelHost.configuration?.logTileLoadTimeThreshold ?? IModelHostConfiguration.defaultLogTileLoadTimeThreshold; } /** The backend will log when a tile is loaded with a size in bytes above this threshold. */ static get logTileSizeThreshold() { return IModelHost.configuration?.logTileSizeThreshold ?? IModelHostConfiguration.defaultLogTileSizeThreshold; } /** Whether external tile caching is active. * @internal */ static get usingExternalTileCache() { return undefined !== IModelHost.tileStorage; } /** Whether to restrict tile cache URLs by client IP address. * @internal */ static get restrictTileUrlsByClientIp() { return undefined !== IModelHost.configuration && (IModelHost.configuration.restrictTileUrlsByClientIp ? true : false); } /** Whether to compress cached tiles. * @internal */ static get compressCachedTiles() { return false !== IModelHost.configuration?.compressCachedTiles; } static setupTileCache() { (0, core_bentley_1.assert)(undefined !== IModelHost.configuration); const config = IModelHost.configuration; const storage = config.tileCacheStorage; const credentials = config.tileCacheAzureCredentials; if (!storage && !credentials) { NativePlatform_1.IModelNative.platform.setMaxTileCacheSize(config.maxTileCacheDbSize ?? IModelHostConfiguration.defaultMaxTileCacheDbSize); return; } NativePlatform_1.IModelNative.platform.setMaxTileCacheSize(0); if (credentials) { if (storage) throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Cannot use both Azure and custom cloud storage providers for tile cache."); this.setupAzureTileCache(credentials); } if (storage) IModelHost.tileStorage = new TileStorage_1.TileStorage(storage); } static setupAzureTileCache(credentials) { const config = { // eslint-disable-next-line @typescript-eslint/naming-convention ServerSideStorage: { dependencyName: "azure", accountName: credentials.account, accountKey: credentials.accessKey, baseUrl: credentials.baseUrl ?? `https://${credentials.account}.blob.core.windows.net`, }, }; const ioc = new inversify_1.Container(); ioc.bind(cloud_agnostic_core_1.Types.dependenciesConfig).toConstantValue(config); new object_storage_azure_1.AzureServerStorageBindings().register(ioc, config.ServerSideStorage); IModelHost.tileStorage = new TileStorage_1.TileStorage(ioc.get(object_storage_core_1.ServerStorage)); } /** @internal */ static computeSchemaChecksum(arg) { return NativePlatform_1.IModelNative.platform.computeSchemaChecksum(arg); } } exports.IModelHost = IModelHost; /** Information about the platform on which the app is running. * @public */ class Platform { /** Get the name of the platform. */ static get platformName() { return process.platform; } } exports.Platform = Platform; /** Well known directories that may be used by the application. * @public */ class KnownLocations { /** The directory where the imodeljs-native assets are stored. */ static get nativeAssetsDir() { return NativePlatform_1.IModelNative.platform.DgnDb.getAssetsDir(); } /** The directory where the core-backend assets are stored. */ static get packageAssetsDir() { return (0, path_1.join)(__dirname, "assets"); } /** The temporary directory. */ static get tmpdir() { return os.tmpdir(); } } exports.KnownLocations = KnownLocations; /** Extend this class to provide custom file name resolution behavior. * @note Only `tryResolveKey` and/or `tryResolveFileName` need to be overridden as the implementations of `resolveKey` and `resolveFileName` work for most purposes. * @see [[IModelHost.snapshotFileNameResolver]] * @public * @deprecated in 4.10 - will not be removed until after 2026-06-13. When opening a snapshot by file name, ensure to pass already resolved path. Using a key to open a snapshot is now deprecated. */ class FileNameResolver { /** Resolve a file name from the specified key. * @param _fileKey The key that identifies the file name in a `Map` or other similar data structure. * @returns The resolved file name or `undefined` if not found. */ tryResolveKey(_fileKey) { return undefined; } /** Resolve a file name from the specified key. * @param fileKey The key that identifies the file name in a `Map` or other similar data structure. * @returns The resolved file name. * @throws [[IModelError]] if not found. */ resolveKey(fileKey) { const resolvedFileName = this.tryResolveKey(fileKey); if (undefined === resolvedFileName) { throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, `${fileKey} not resolved`); } return resolvedFileName; } /** Resolve the input file name, which may be a partial name, into a full path file name. * @param inFileName The partial file name. * @returns The resolved full path file name or `undefined` if not found. */ tryResolveFileName(inFileName) { return inFileName; } /** Resolve the input file name, which may be a partial name, into a full path file name. * @param inFileName The partial file name. * @returns The resolved full path file name. * @throws [[IModelError]] if not found. */ resolveFileName(inFileName) { const resolvedFileName = this.tryResolveFileName(inFileName); if (undefined === resolvedFileName) { throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, `${inFileName} not resolved`); } return resolvedFileName; } } exports.FileNameResolver = FileNameResolver; //# sourceMappingURL=IModelHost.js.map