Global Events

Global Events require service accounts. Service accounts are currently only available to internal Bentley products.

iModelHub sends IModelHubGlobalEvents for operations occuring on any iModel in iModelHub.

To receive events, the service has to:

  1. Subscribe to global events, specifying types of events it wants to receive.
  2. Get a GlobalEventSAS token, that is used to authenticate to Global Events service. See GlobalEventHandler.getSASToken or getting global events.
  3. Send a request to get the first event from that subscription's queue. See getting global events.

Instead of repeating steps 2 and 3, it's possible to create a listener that continuously receives events from a subscription.

Global Event Types

When a user subscribes to global events, they have to provide GlobalEventTypes of IModelHubGlobalEvents they want to receive. iModelHub sends these global events:

Type Description
ChangeSetCreatedEvent ChangeSet was created.
HardiModelDeleteEvent iModel was completely deleted from archive.
IModelCreatedEvent iModel was created.
NamedVersionCreatedEvent Named Version was created.
SoftiModelDeleteEvent iModel was deleted and placed into archive.

Creating Global Events Subscription

To receive IModelHubGlobalEvents, service has to create a GlobalEventSubscription. Creating a subscription requires the service to specify an array of global event types it wants to receive.

Creating subscription requires service to have an access token. Service has to specify an Guid identifier that service would have stored securely. It can be used later to retrieve same subscription.

Number of subscriptions is limited. It is required to reuse previously created subscriptions rather than creating new ones.

Example:

  const id = "c41580e2-6ac9-473c-9194-2c9a36187dbd";
  const subscription: GlobalEventSubscription = await imodelHubClient.globalEvents
    .subscriptions.create(authorizedRequestContext, id, ["iModelCreatedEvent", "NamedVersionCreatedEvent"]);

After creating the subscription, subscription.wsgId can be used when subscription id is requested. If this id is forgotten, a repeated GlobalEventSubscriptionHandler.create with same Guid will return the same subscription instead of creating a new one.

Each subscription contains its own queue of events. Subscriptions expire after a month of inactivity.

Getting global events

To get events, service has to have access token and have a global event subscription.

First service has to get an GlobalEventSAS token.

  const sasToken: GlobalEventSAS = await imodelHubClient.globalEvents.getSASToken(authorizedRequestContext);

Then service can get the first event from its subscription's queue. This will immediately delete the event from the queue.

  const globalEvent: IModelHubGlobalEvent | undefined = await imodelHubClient.globalEvents
    .getEvent(requestContext, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId, 60);

If GlobalEventHandler.getEvent is called with a timeout duration specified, this request will perform long polling. If this request didn't find a global event on the queue, it would wait until one is available or the timeout expires. This way the event reaches the service faster and less requests are sent between the client service and iModelHub.

GlobalEventHandler.getEvent can fail because GlobalEventSAS has expired. It can also return undefined if no events have been found.

Non-destructive read

Instead of immediately removing global events from the queue, it's possible to peek and lock them. Locked events will not appear in the queue when doing subsequent GlobalEventHandler.getEvent requests.

Service will have to delete locked event after they finish processing it. If service doesn't delete the event in a minute, the lock will expire and the event will reappear in the queue.

When getting event, service has to specify that they want to use GetEventOperationType.Peek:

  const globalEventWithLock: IModelHubGlobalEvent | undefined = await imodelHubClient.globalEvents
    .getEvent(requestContext, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId, 60, GetEventOperationType.Peek);

Then after processing the event, service has to delete it:

  await globalEventWithLock.delete(authorizedRequestContext);

Create a Global Events Listener

GlobalEventHandler.createListener can be used to handle repeated calls to GlobalEventHandler.getEvent and GlobalEventHandler.getSASToken. Listener will use always delete events when retrieving them. If you want to use non-destructive GlobalEventHandler.getEvent, see getting events.

Authentication callback example, similar to getting access token. AuthorizationToken could be retrieved from credentials stored somewhere else or refreshed before it expires.

async function authenticate(): Promise<AccessToken> {
  const requestContext = new ClientRequestContext();
  const authorizationToken: AuthorizationToken = await authorizationClient
    .getToken(requestContext, userCredentials);
  return imodelHubClient.getAccessToken(requestContext, authorizationToken);
}

Listener callback example. This callback just logs the type of the IModelHubGlobalEvent received.

function processGlobalEvent(event: IModelHubGlobalEvent): void {
  Logger.logInfo("example", `Global Event of the type ${typeof event} received.`);
}

To create the listener itself, user has to have a global event subscription.

  const deleteCallback = await imodelHubClient.globalEvents // tslint:disable-line:await-promise
    .createListener(authorizedRequestContext, authenticate, subscription.wsgId, processGlobalEvent);

Deleting the listener after it's no longer necessary is just calling the callback received when creating it.

  // Delete callback when events should be no longer received
  deleteCallback();

Event listener will work in the background, continuously getting events for a specific GlobalEventSubscription. Once an IModelHubGlobalEvent is received, all registered listener callbacks for that subscription are called. If GlobalEventSAS expires, GlobalEventHandler.getSASToken will be called automatically. If AccessToken expires, authentication callback will be called to refresh that token.

Event listener will stop if there's an error getting events for that subscription or when all listeners for it are deleted. In the latter case, any outstanding long polling requests could still complete.

Listeners for the same subscription will only make a single event request at a time. Listeners for different subscriptions work independently.

Last Updated: 08 January, 2020