Link Unfurling
By default, after a user posts an external link to a chat channel, Space unfurls the link or, in other words, shows a preview of the page behind the link. Space supports link unfurling not only in chat messages but also in documents, issue descriptions, and commit and code review titles.
In order Space could build a link preview, the page behind the link must meet the following requirements:
The page must provide preview info in its social meta tags.
The page must be publicly available and doesn't require authentication.
If the page doesn't meet the requirements, Space won't be able to show its preview. For such cases, you can create an application that will authenticate in the corresponding external system, fetch the required content, and provide the content to Space.
Currently, Space supports only attachment previews. An attachment preview is a fragment of page content next to the chat message that contains the link to the page. If the link is a part of a document or issue description, Space shows the preview in the link tooltip. To provide attachment previews, applications require the Unfurl.App.ProvideAttachment
permission.
To make unfurling work, Space must somehow notify the application each time a user posts an external link. For every posted link, Space sends a short generic notification to the application and saves link data (a user ID, a link URL, etc.) to a special queue. To get the data on a particular link (i.e., queue item), the application must query the queue with an API call. The lifetime of a queue item is 30 minutes.
This approach reduces network workload and prevents the application from losing the list of not-yet-processed links during temporary outages.
Queue items are instances of the ApplicationUnfurlQueueItem
type with the following properties:
etag: long
is an entity tag that helps the application track its position in the queue. It is a unique number that is increased for each new item. The application must store this number in some persistent storage.id: string
is a unique string identifier of the item. Use it to identify the item when calling back to the Space API to provide a content or request authentication.target: string
is a URL of the posted link.context: ApplicationUnfurlContext
provides inforomation about the exact place in Space where the link was posted: a chat message, a blog article, a document, a code review title, a git commit message, or an issue description.authorUserId: ProfileIdentifier.Id
is an ID of the user who posted the link. Returnsnull
if the user cannot be identified, e.g., a user who pushed a commit doesn't belong to the current organization.
When a new item appears in the queue, Space sends a notification with the payload of the NewUnfurlQueueItemsPayload
type. The payload itself doesn't contain any information about the item. On receiving the notification, the application must poll the queue with a call to the applications/unfurls/queue
Space API endpoint. We also recommend that the application polls the queue right after the start because there's a chance that some queue items were missed while the application was unavailable.
As the queue can contain a lot of items, you should request them in batches. For example, this is how you can get a batch of 100 items starting with the item with etag=50:
import space.jetbrains.api.runtime.resources.applications
import space.jetbrains.api.runtime.types.NewUnfurlQueueItemsPayload
// variable that stores the etag of the last processed item
// in real application, it must be stored in a persistent storage
private var lastEtag: Long? = null
when (val payload = readPayload(body)) {
// ...
is NewUnfurlQueueItemsPayload -> {
// poll the link queue
val queueApi = spaceClient.applications.unfurls.queue
// as the queue may contain many items, get items in batches
var queueItems = queueApi.getUnfurlQueueItems(lastEtag, batchSize = 100)
while (queueItems.isNotEmpty()) {
queueItems.forEach { item ->
// generate preview for each item
// ...
}
// get ETag of the last processed item
lastEtag = queueItems.last().etag
queueItems = queueApi.getUnfurlQueueItems(lastEtag, batchSize = 100)
}
}
}
GET https://mycompany.jetbrains.space/api/http/applications/unfurls/queue?fromEtag=50&batchSize=100
Authorization: Bearer <here-goes-auth-token>
Accept: application/json
To build the link preview, the application must fetch the content of the external page behind the link. If this external system requires authentication, the application must first authenticate in the system on behalf of the user who posted the link.
To request user authentication, use the
/api/http/applications/unfurls/queue/request-external-auth
API call. In the call, the application must provide means for the user to authenticate. For this purpose, you can use the Space message constructor and an instance of theNavigateUrlAction
type. This action navigates the user to the specified URL which must be some endpoint of your application.For example, this is how you can create a message with a single Authenticate button that leads to
https://myapplication.url/oauth?user=space-user-id
:Kotlin Space SDKHTTP requestspaceClient.applications.unfurls.queue.requestExternalSystemAuthentication( item.id, unfurl { section { text("Authenticate in Slack to get link previews in Space") controls { button( "Authenticate", NavigateUrlAction( "https://myapplication.url/oauth?user=$spaceUserId", // BackUrl lets the app return the user back to the // page where they clicked the button withBackUrl = true, openInNewTab = false ) ) } } } )
POST https://mycompany.jetbrains.space/api/http/applications/unfurls/queue/request-external-auth Authorization: Bearer here-goes-access-token Accept: application/json Content-Type: application/json { "queueItemId": "here-goes-item-id", "message": { "style": "PRIMARY", "sections": [ { "className": "MessageSectionV2", "elements": [ { "className": "MessageControlGroup", "elements": [ { "className": "MessageButton", "text": "", "style": "REGULAR", "action": { "className": "NavigateUrlAction", "url": "https://myapplication.url/oauth?user=here-goes-space-user-id", "withBackUrl": true, "openInNewTab": false } } ] } ] } ] } }
In the application, add an endpoint (in our example it's
/oauth
) that will handle the authentication call. The endpoint must redirect the user to the external service OAuth endpoint. After the user authenticates in the system, the system must issue an access token for our application.The exact implementation of this functionality depends on the OAuth flow implemented in the external system. Therefore, we will not provide any generic instructions in this guide. You can find an example in our tutorial on how to unfurl Slack messages.
note
Currently, external system authentication requests are supported only in Space chats. Links posted in other contexts like documents or issue descriptions will generate unfurling queue items, but Space will ignore the corresponding authentication requests. Nevertheless, the link preview feature is still quite useful in these contexts – it just means that the application has to use an app-level token for the external system. Another option is to pre-authenticate a user in the external system via the link preview in a chat message.
After the application gets authorized in the external system and fetches the required content, it can prepare and send this content to Space. To do this, send a POST request to the applications/unfurls/queue/content
Space API endpoint. The request must contain an instance of the ApplicationUnfurl
class with the following data:
queueItemID: string
is the unfurling queue ID of the original message.content: ApplicationUnfurlContent
is the message content. Two types of content are possible:ApplicationUnfurlContent.Message
is a message built with the message constructor DSL.ApplicationUnfurlContent.Image
is an image. Space downloads and saves the image when processing the preview. Updates of the image on the external server don't update the preview in Space.
For example:
// Message
val content: ApplicationUnfurlContent.Message = unfurl {
MessageOutlineV2(
elements = listOf(
MessageIcon(
icon = ApiIcon("bug"),
style = MessageStyle.PRIMARY
),
MessageInlineText(
text = "Title goes here",
style = null
)
)
)
section {
MessageSectionV2(
elements = listOf(
MessageText(
accessory = null,
style = MessageStyle.PRIMARY,
size = null,
content = "Here goes unfurled content."
)
),
style = null,
textSize = null
)
}
}
spaceClient.applications.unfurls.queue.postUnfurlsContent(
listOf(ApplicationUnfurl("here-goes-item-id", content))
)
// Image
spaceClient.applications.unfurls.queue.postUnfurlsContent(
unfurls = listOf(ApplicationUnfurl(
// item represents a queue item
queueItemId = item.id,
content = ApplicationUnfurlContent.Image(
icon = null,
title = "Title goes here",
url = "https://externalservice.url/image.png"
)
))
)
# Message
POST https://mycompany.jetbrains.space/api/http/applications/unfurls/queue/content
Authorization: Bearer here-goes-access-token
Accept: application/json
Content-Type: application/json
{
"unfurls": [
{
"queueItemId": "here-goes-item-id",
"content": {
"className": "ApplicationUnfurlContent.Message",
"style": "PRIMARY",
"outline": {
"className": "MessageOutlineV2",
"elements": [
{
"className": "MessageIcon",
"icon": {
"icon": "bug"
},
"style": "PRIMARY"
},
{
"className": "MessageInlineText",
"text": "Title goes here"
}
]
},
"sections": [
{
"className": "MessageSectionV2",
"elements": [
{
"className": "MessageText",
"style": "PRIMARY",
"content": "Here goes unfurled content."
}
]
}
]
}
}
]
}
# Image
POST https://mycompany.jetbrains.space/api/http/applications/unfurls/queue/content
Authorization: Bearer here-goes-access-token
Accept: application/json
Content-Type: application/json
{
"unfurls": [
{
"queueItemId": "here-goes-item-id",
"content": {
"className": "ApplicationUnfurlContent.Image",
"title": "Title goes here",
"url": "https://externalservice.url/image.png"
}
}
]
}
If the application uses the message constructor DSL, it can add UI elements (currently, only buttons) to the unfurled content. When a user interacts with these elements (e.g., clicks a button), Space sends a request to the application. The request contains a payload of the UnfurlActionPayload
type. The payload content is almost identical to that of MessageActionPayload
. The only difference is the action source. Instead of the original message, UnfurlActionPayload
provides two properties that point to the origin:
link: string
is the URL of the unfurled link that provided the triggered action.context: ApplicationUnfurlContext
provides inforomation about the exact place in Space where the link was posted: a chat message, a blog article, a document, a code review title, a git commit message, or an issue description. It is the same context as in an unfurling queue item.
Thanks for your feedback!