Forge Realtime is now available as an Early Access Program (EAP). To start testing this feature, sign up using this form. This feature is currently only available in Jira, with support for other products to be added in future.
APIs and features under EAP are unsupported and subject to change without notice. APIs and features under EAP are not recommended for use in production environments.
For more details, see Forge EAP, Preview, and GA.
This API enables Forge apps to publish events to realtime channels. This is suited for scenarios where live updates are sent beyond the current page or to multiple users. Unlike the existing Async events API, realtime events are emitted via WebSocket connections rather than on the window object.
Forge functions can only publish events at this time. You will need to set up the frontend of your app to first subscribe to events using the respective realtime @forge/bridge API before receiving the published events.
To publish an event, first import the Realtime publish API into your Forge resolver file:
1 2import { publish } from '@forge/realtime';
Alternatively, to publish an event to a globally scoped channel, import the global publish API:
1 2import { publishGlobal } from '@forge/realtime';
Publishes events if there is an existing subscription for the same channel context. The resulting eventId and eventTimestamp will be null if there are no existing subscribers.
1 2const publish = ( channel: string, payload: string options?: PublishOptions ): Promise<PublishResult> const publishGlobal = ( channel: string, payload: string options?: PublishOptions ): Promise<PublishResult> enum Jira { Board = 'board', Issue = 'issue', Project = 'project' } type ProductContext = Jira; interface PublishOptions { token?: string; contextOverrides?: ProductContext[]; } interface PublishResult { eventId: string | null; eventTimestamp: string | null; errors: any; }
signRealtimeToken method that is used to restrict a channel's scope. The published event will only be received by subscriptions that have been created with a token containing the same channel context claims.PublishResult: A PublishResult which contains information about the published event.
1 2import Resolver from '@forge/resolver'; import { publish } from '@forge/realtime'; const resolver = new Resolver(); resolver.define("publishRealtimeEvent", ({ payload, context }) => { const channel = 'my-example-channel'; const payload = { foo: 'foo', bar: 'bar' }; publish(channel, JSON.stringify(payload)); return { example: `Published event with payload: ${JSON.stringify(payload)}` }; }); export const handler = resolver.getDefinitions();
Subscriptions created with the subscribe() function are scoped to channels for the current module context by default. This means that a subscription in a module, for example jira:issuePanel, will only receive messages that are published from the same module in the same Jira issue. It will not receive messages from a jira:issueContext on that issue, or the jira:issuePanel on a different issue, even if they share the same channel name.
See the in-depth guide on how to authorize channels for more detail.
Although the APIs for publish and publishGlobal are identical, the publishGlobal API does not enforce full permission scopes according to the Atlassian app and extension contexts your app is installed in. Events in global channels are sent across modules and Atlassian app contexts. This means that any users with access to your app may receive events from a private Jira issue or Confluence page that they do not have access to.
As a result, events sent by the publish API can only be received by subscriptions created using the subscribe @forge/bridge API, and publishGlobal events can only be received by subscriptions created using the subscribeGlobal bridge API.
token argument to secure channel contextThe token argument allows for more fine-grained control over the permissions scopes of your realtime events. See Authorizing Realtime channels for more information on how to manage authorization within your app.
To obtained a signed realtime token, import the signRealtimeToken function into your resolver:
1 2import { publishGlobal, signRealtimeToken } from '@forge/realtime';
1 2const signRealtimeToken = ( channel: string, claims: object ): Promise<TokenResult> interface TokenResult = { token: string | null; expiresAt: number | null; errors?: any; }
TokenResult which contains the signed token result.
token argument in your publish(), publishGlobal(), subscribe() or subscribeGlobal() calls1 2import Resolver from '@forge/resolver'; import { publish, signRealtimeToken } from '@forge/realtime'; const resolver = new Resolver(); const TOKEN_EXPIRY_BUFFER = 5000; // 5 seconds resolver.define('publishEvent', ({ payload, context }) => { const customClaims = { allowedUsers: ['accountId-1', 'accountId-2'], }; const { token, expiresAt } = signRealtimeToken('my-test-channel', customClaims); // expiresAt is an epoch timestamp expressed in seconds (in accordance with the JWT // standard for the exp field), so it needs to be converted to milliseconds before // comparing against a Date timestamp. if (Date.now() - TOKEN_EXPIRY_BUFFER < expiresAt * 1000) { return publish('my-test-channel', 'This is an event payload', { token }); } }); export const handler = resolver.getDefinitions();
Rate this page: