0.191.0 Change Notes
Changes to IModelApp.startup
In previous versions of the @bentley/imodeljs-frontend
package, the recommendation was to create a subclass of IModelApp
to customize frontend behavior.
This proved problematic, since all of the members of IModelApp
are static, and IModelApp
itself is intended to be a singleton. This works at cross-purposes with JavaScript subclassing with static members, since each subclass creates a separate object (you can either have subclassing or a singleton, not both.)
To resolve this issue, it is now illegal to subclass from IModelApp, and instead all options for customization are supplied by the new IModelAppOptions argument to IModelApp.startup.
In addition, the static methods:
onStartup
supplyRenderSystem
supplyI18NOptions
on IModelApp have been removed, since their only purpose was to allow customization via subclasses. The latter two are replaced by members of the IModelAppOptions argument.
The onStartup
method was previously called on the subclass inside IModelApp.startup. A typical pattern was:
class MyApp extends IModelApp {
public static appData: MyAppData;
protected static onStartup() {
IModelApp.applicationId = "2322"; // Note: "this.applicationId" would not have worked previously
this.myAppData = new MyAppData();
. . . other startup code
}
}
This can now be replaced with code like:
class MyApp { // note, does not extend IModelApp
public static appData: MyAppData;
public static startup() {
IModelApp.startup({ applicationId: "2322" });
this.myAppData = new MyAppData();
. . . other startup code
}
}
Then the call to MyApp.startup()
elsewhere in your code can remain unchanged. Any place where you may have accessed members of IModelApp via MyApp
should be changed to instead access IModelApp
directly. E.g.:
const val = MyApp.i18n.translate("key");
can be replaced with
const val = IModelApp.i18n.translate("key");
In summary, it is no longer necessary (or possible) to subclass from IModelApp
. However, if your application has global information you wish to hold, you may simply create your own xxxApp
singleton class that does not derive from IModelApp
. This is an API breaking change, and all frontend apps will likely have some necessary change.
Changes to methods of EntityState
The method EntityState.getClassFullName
was renamed to an accessor get classFullName
for consistency with the same method on Entity.
The method EntityState.sqlName
was removed. It returned a string in the form "SchemaName.ClassName", whereas EntityState.classFullName
returns "SchemaName:ClassName" (colon vs. dot syntax.) Since ECSql
now supports both syntaxes, it is no longer necessary to have the sqlName
method. You may simply replace all uses of sqlName
with classFullName
.
Deprecation of standalone iModels
The confusing concept of standalone iModels has been removed from the frontend API and deprecated in the backend API. All API related to standalone iModels will be eliminated prior to the 1.0 release. All uses of the following standalone iModel functions must be migrated:
IModelDb.openStandalone
IModelDb.createStandalone
IModelConnection.openStandalone
Change history is essential for editing scenarios, so should use iModels managed by iModelHub. See:
Archival scenarios can use snapshot iModels. Once created, a snapshot iModel is read-only. See:
Node 10.15.3
The iModel.js backend now requires Node version 10.15.3 or later.
Starting up of backends
For web applications that have not migrated to using OpenId Connect for authentication, it's important that backends are started with a configuration that increases the allowed maximum size of headers to accommodate the larger SAML based access tokens, and the additional headers that are now needed to be passed through.
e.g., in package.json
...
"start:backend": "node --max-http-header-size=16000 lib/backend/main.js",
...
Electron 4.10.0
The electron version used internally has been updated to v4.10.0.
Changes to authorization of frontend applications
IModelApp.accessToken
has now been removed.- Frontend applications that need to access protected data from the iModelHub must now provide an implementation of IAuthorizationClient through the IModelApp.authorizationClient setting.
- The following implementations of IModelApp.authorizationClient can be used:
- Frontends running in a browser: OidcBrowserClient
- Test implementations that use
ImsCredentials
to fetch legacy SAML tokens: ImsTestAuthorizationClient
- Frontend methods that require authorization do not require the accessToken to be passed in anymore -
Breaking changes to RPC interfaces
- Breaking changes have been made to IModelReadRpcInterface and IModelWriteRpcInterface. This implies the frontend and backends based on the next version of iModel.js break compatibility and have to be deployed simultaneously.
- None of the RPC interfaces now need to pass the AccessToken as an argument. The RPC implementation provides the mechanism to to make this happen through a generic context. More on this below.
RpcOperationPolicy.requestId()
has now been removed
Enhancements to authorization and logging
A new family of classes starting with ClientRequestContext now provide generic context for a specific client request. The context carries the necessary information for making the API calls to generate usage metrics, logging diagnostics (telemetry), and authorizing the use of various services.
Instances of these classes are passed to almost all asynchronous calls that eventually call into the various services. For synchronous calls, a static "current" context avoids the need to pass the context as an argument.
The base class ClientRequestContext includes information on the
session
,application
andactivity
for logging and usage metrics. Note that this replaces the use ofActivityLoggingContext
in previous versions.The sub class AuthorizedClientRequestContext includes the AccessToken that carries the information on
authorization
. Note that this replaces the use ofActivityLoggingContext
andAccessToken
as parameters in various method calls. For example,function updateVersion(actx: ActivityLoggingContext, accessToken: AccessToken) { const version: Version = await iModelClient.versions.update(actx, accessToken, imodelId, version); ... }
becomes
function updateVersion(requestContext: AuthorizedClientRequestContext) { const version: Version = await iModelClient.versions.update(requestContext, imodelId, version); ... }
For web, desktop and mobile applications, at the frontend, the sub classes FrontendRequestContext and AuthorizedFrontendRequestContext can be used as helpers to create these contexts with the necessary session, application and authorization information (if applicable) filled out.
Similarly, for agent applications, at the backend, the sub classes BackendRequestContext and AuthorizedBackendRequestContext can be used as helpers to create these contexts with the necessary session and host information filled out.
In the case of web applications the context is serialized and passed as HTTP headers from the frontend to the backend. Note that all backends (for web frontends) must now be configured to allow the following headers to avoid CORS errors -
res.header("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, X-Correlation-Id, X-Session-Id, X-Application-Id, X-Application-Version, X-User-Id");
It is important for logging purposes that different frontend calls are tracked with a unique
activityId
. The system tries to ensure this by setting it up as a new Guid after every RPC request by default.Any frontend methods that contact multiple services (and not just the backend through RPC) need to explicitly create the context at the top level, and manage the context through multiple service calls. Right before the RPC request is made, call ClientRequestContext.useContextForRpc or AuthorizedClientRequestContext.useContextForRpc to setup the use of that context (and the contained
activityId
) for the subsequent RPC request.
Changes to authorization for Single Page Applications
OidcBrowserClient attempts to silently sign-in during initialization, when signIn() is called, or when the accesss token expires. The signIn() calls also takes a successRedirectUri parameter that can be used to control the redirection after the entire authorization process is completed.
Changes required for Usage Logging
Frontend applications must set the IModelApp.applicationId and IModelApp.applicationVersion fields to ensure the usage is logged. Bentley applications must set
applicationId
to the Bentley Global Product Registry Id (GPRID).Similarly agent applications must set these fields in IModelHost. Note that IModelHost.applicationId replaces IModelHost.backendVersion for consistency.
applicationId
may eventually be removed once it becomes possible to infer it from the AccessToken. A service to make this available is in the works.
Miscellaneous changes
- The IModelJsExpressServer class has been moved to its own package (
@bentley/express-server
).- This package has a dependency on express, so the first constructor argument to
IModelJsExpressServer
has been removed.
- This package has a dependency on express, so the first constructor argument to
- IModelConnection.openSnapshot throws an exception if IModelApp.startup has not been called.
- IModelDb.onOpened takes a callback with a different signature - AuthorizedClientRequestContext is now passed as the first argument.
- ImsActiveSecureTokenClient takes
ImsCredentials
as a single argument instead of separate email and password fields. - Deleted agent-test-app from the repository.
- RequestGlobalOptions.timeout is now available to control global timeouts for requests to various Bentley services.
Breaking changes to ImodelServices.openIModel
- The first argument to openIModel is now a context (project) id. It replaces passing in a ProjectInfo object.
Breaking changes to ViewState
To reduce errors in synchronizing a Viewport's state with that of the ViewState it controls, several mutator methods were removed from ViewState
and transferred to Viewport
. These include:
- The setter for the
viewFlags
property. - The
overrideSubCategory
,dropSubCategoryOverride
, getSubCategoryOverride, and
getSubCategoryAppearance` functions. - The
changeCategoryDisplay
function.
To adjust your code for these changes, change the call site to invoke the Viewport
functions/properties instead of those formerly defined on the ViewState
.
Additionally, the areFeatureOverridesDirty
property and setFeatureOverridesDirty
function were removed from ViewState
. The Viewport
now keeps track of discrete changes to its state which require feature symbology overrides to be regenerated. This also allows it to expose a variety of more granular events like onViewedCategoriesChanged
and onDisplayStyleChanged
. These are far more efficient than listening for onViewChanged
, which is dispatched immediately upon each change, sometimes multiple times per frame. The new events are dispatched once per frame during which the state they monitor changed.
To adjust your code for these changes:
- If you were previously using
setFeatureOverridesDirty
to notify theViewport
that it must refresh its feature symbology overrides, you should no longer need to do so when modifying the display style, displayed categories etc, provided theViewport
APIs are used. TheViewport
APIs automatically record the state changes and internally mark the overrides as 'dirty' if necessary. - If you were using
setFeatureOverridesDirty
to notify theViewport
that a FeatureOverrideProvider you registered with theViewport
had changed internally and therefore the overrides should be recalculated, use Viewport.setFeatureOverrideProviderChanged instead.
Last Updated: 08 January, 2020