How to extend Link Unfurling template - OfficeDev/TeamsFx GitHub Wiki

How to use Zero Install Link Unfurling in Teams

Zero Install Link Unfurling requires link unfurling app to be published. You need an admin account to publish an app into your org.

Login your admin account in Teams. Go to Manage your apps -> Upload an app. Click Upload an app to your org's app catalog to upload your app's zip file.

upload

Switch to another user account. Without installing this app, paste the link "https://www.botframework.com" into chat box, and you should see the adaptive card like below.

zeroInstall

How to add link unfurling cache in Teams

This template removes cache by default to provide convenience for debug. To add cache, REMOVE following JSON part from adaptive card in linkUnfurlingApp.ts (linkUnfurlingApp.js):

suggestedActions: {
          actions: [
            {
              title: "default",
              type: "setCachePolicy",
              value: '{"type":"no-cache"}',
            },
          ],
        }

After removing this, the link unfurling result will be cached in Teams for 30 minutes. Please refer to link unfurling document for more details.

How to customize Zero Install Link Unfurling's adaptive cards

The supported types for Zero Install Link Unfurling are "result" and "auth" and this template uses "result" as default. By changing it to "auth", the adaptive card will be:

zeroInstallAuth

For card with type "auth", the Teams client strips away any action buttons from the card, and adds a sign in action button. Please refer to zero install link unfurling document for more details.

How to add stage view

Stage View is a full screen UI component that you can invoke to surface your web content. You can turn URLs into a tab using an Adaptive Card and Chat Services.

Collaborative Stage View is an enhancement to Stage View that allows users to engage with your app content in a new Teams window. When a user opens Collaborative Stage View from an Adaptive Card, the app content pops-out in a new Teams window instead of the default Stage View modal.

Follow these steps to add stage view or collaborative stage view to your link unfurling template.

Step 1: Update manifest.json

In appPackage/manifest.json, update validDomains section.

    "validDomains": [
        "${{BOT_DOMAIN}}"
    ]

Step 2: Add route for /tab

For typescript and javascript template, in src/index.ts (src/index.js), add following code.

server.get("/tab", async (req, res) => {
  const body = `<!DOCTYPE html>
  <html lang="en">
  
  <div class="text-center">
    <h1 class="display-4">Tab in stage View</h1>
  </div>
  
  </html>`;
  res.writeHead(200, {
    'Content-Length': Buffer.byteLength(body),
    'Content-Type': 'text/html'
  });
  res.write(body);
  res.end();
});

For c# template, in Controllers/, add a new TabController:

[Route("tab")]
[ApiController]
public class TabController : ControllerBase
{
    [HttpPost, HttpGet]
    public ContentResult Get()
    {
        // Delegate the processing of the HTTP POST to the adapter.
        // The adapter will invoke the bot.

        var html = "<h1>Tab in stage view</h1>";
        return new ContentResult
        {
            Content = html,
            ContentType = "text/html"
        };
    }
}

Step 3: Set BOT_DOMAIN and TEAMS_APP_ID in environment variables or appsettings.json

For local debug:

If it's typescript or javascript template, update action file/createOrUpdateEnvironmentFile in teamsapp.local.yml, add TEAMS_APP_ID and BOT_DOMAIN to env.

  - uses: file/createOrUpdateEnvironmentFile # Generate runtime environment variables
    with:
      target: ./.localConfigs
      envs:
        BOT_ID: ${{BOT_ID}}
        BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
        TEAMS_APP_ID: ${{TEAMS_APP_ID}}
        BOT_DOMAIN: ${{BOT_DOMAIN}}

If it's c# template, update action file/createOrUpdateJsonFile in teamsapp.local.yml, add TEAMS_APP_ID and BOT_DOMAIN to appsettings.json.

  - uses: file/createOrUpdateJsonFile
    with:
      target: ./appsettings.Development.json
      appsettings:
        BOT_ID: ${{BOT_ID}}
        BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
        TEAMS_APP_ID: ${{TEAMS_APP_ID}}
        BOT_DOMAIN: ${{BOT_DOMAIN}}

For remote:

Update infra/azure.parameters.json. Add following to parameters:

    "teamsAppId":{
      "value": "${{TEAMS_APP_ID}}"
    }

Add following to infra/azure.bicep (if it's c# template, remove line WEBSITE_NODE_DEFAULT_VERSION: '~18'):

param teamsAppId string 

resource webAppSettings 'Microsoft.Web/sites/config@2022-09-01' = {
  parent: webApp
  name: 'appsettings'
  properties: {
    BOT_DOMAIN: webApp.properties.defaultHostName
    BOT_ID: botAadAppClientId
    BOT_PASSWORD: botAadAppClientSecret
    RUNNING_ON_AZURE: '1'
    TEAMS_APP_ID: teamsAppId
    WEBSITE_NODE_DEFAULT_VERSION: '~18'
    WEBSITE_RUN_FROM_PACKAGE: '1'
  }
}

Step 4: Update adaptive card for stage view

In src/adaptiveCards/helloWorldCard.json or Resources/helloWorldCard.json, update actions to be following.

"actions": [
        {
            "type": "Action.OpenUrl",
            "title": "View Via Deep Link",
            "url": "https://teams.microsoft.com/l/stage/${appId}/0?context=%7B%22contentUrl%22%3A%22https%3A%2F%2F${url}%2Ftab%22%2C%22websiteUrl%22%3A%22https%3A%2F%2F${url}%2Ftab%22%2C%22name%22%3A%22DemoStageView%22%7D"
        }
      ],

For typescript or javascript templates:

Run npm install adaptive-expressions adaptivecards-templating to install the package for adaptivecards templating.

In src/linkUnfurlingApp.ts (src/linkUnfurlingApp.js), update variable attachment to be following.

    const template = new ACData.Template(helloWorldCard);
    const card = template.expand({
      $root: {
        url: process.env.BOT_DOMAIN,
        appId: process.env.TEAMS_APP_ID
      },
    });
    const attachment = { ...CardFactory.adaptiveCard(card), preview: previewCard };

For c# templates:

run dotnet add package AdaptiveCards.Templating to install the package for adaptivecards templating.

Update Config.cs:

    public class ConfigOptions
    {
        public string BOT_ID { get; set; }
        public string BOT_PASSWORD { get; set; }
        public string TEAMS_APP_ID { get; set; }
        public string BOT_DOMAIN { get; set; }
    }

Update LinkUnfurlingApp class:

    private readonly ConfigOptions _config;

    public LinkUnfurlingApp(ConfigOptions config)
    {
        _config = config;
    }

Update variable adaptiveCard to be following:

        var data = new { url = _config.BOT_DOMAIN, appId = _config.TEAMS_APP_ID };
        var template = new AdaptiveCards.Templating.AdaptiveCardTemplate(adaptiveCardJson);

        var adaptiveCard =AdaptiveCard.FromJson(template.Expand(data)).Card;

Update program.cs:

    builder.Services.AddTransient<IBot>(sp => new LinkUnfurlingApp(config));

In Teams, the adaptive card will be like:

stageView

Opening stage view from Adaptive card:

viaDeepLink

In Outlook, the adaptive card will be like:

stageView

Opening stage view from Adaptive card via deep link:

viaDeepLink

Please refer to Stage view document for more details.

Step 4: Update adaptive card for collaborative stage view

In src/adaptiveCards/helloWorldCard.json or Resources/helloWorldCard.json, update actions to be following.

"actions": [
        {
            "type": "Action.Submit",
            "title": "View Via card",
            "data":{
                "msteams": {
                    "type": "invoke",
                    "value": {
                        "type": "tab/tabInfoAction",
                        "tabInfo": {
                            "contentUrl": "https://${url}/tab",
                            "websiteUrl": "https://${url}/tab",
                            "entityId": "entityId"
                        }
                    }
                }
            }
        }
      ],

For typescript or javascript templates:

Run npm install @microsoft/adaptivecards-tools to install the package for adaptivecards templating.

In src/linkUnfurlingApp.ts (src/linkUnfurlingApp.js), update variable attachment to be following.

    const data = { url: process.env.BOT_DOMAIN, appId: process.env.TEAMS_APP_ID };

    const renderedCard = AdaptiveCards.declare(helloWorldCard).render(data);

    const attachment = { ...CardFactory.adaptiveCard(renderedCard), preview: previewCard };

For c# templates:

run dotnet add package AdaptiveCards.Templating to install the package for adaptivecards templating.

Update Config.cs:

    public class ConfigOptions
    {
        public string BOT_ID { get; set; }
        public string BOT_PASSWORD { get; set; }
        public string TEAMS_APP_ID { get; set; }
        public string BOT_DOMAIN { get; set; }
    }

Update LinkUnfurlingApp class:

    private readonly ConfigOptions _config;

    public LinkUnfurlingApp(ConfigOptions config)
    {
        _config = config;
    }

Update variable adaptiveCard to be following:

        var data = new { url = _config.BOT_DOMAIN, appId = _config.TEAMS_APP_ID };
        var template = new AdaptiveCards.Templating.AdaptiveCardTemplate(adaptiveCardJson);

        var adaptiveCard =AdaptiveCard.FromJson(template.Expand(data)).Card;

Update program.cs:

    builder.Services.AddTransient<IBot>(sp => new LinkUnfurlingApp(config));

In Teams client, the adaptive card will be like:

collabStageView

Opening collaborative stage view from Adaptive card:

collabStageView

Please refer to Collaborative Stage View for more details.

How to add task module (Teams)

Once your link is unfurled into an Adaptive Card and sent in conversation, you can use Task modules to create modal pop-up experiences in your Teams application. Follow the instructions below to add task module in your link unfurling app.

Step 1: Update adaptive card

In src/adaptiveCards/helloWorldCard.json (Resources/helloWorldCard.json for c# template), update actions to be following.

    "actions": [
        {
            "type": "Action.Submit",
            "title": "Task module",
            "data": {
                "msteams": {
                    "type": "task/fetch",
                    "data": "task module"
                }
            }
        }
      ],

Step 2: Add handleTeamsTaskModuleFetch function in handler

For typescript template, in src/linkUnfurlingApp.ts, add following method to LinkUnfurlingApp class.

  public async handleTeamsTaskModuleFetch(context: TurnContext, taskModuleRequest: TaskModuleRequest): Promise<TaskModuleResponse> {
    return {
      task: {
        type: "continue",
        value: {
          title: "Task Module Fetch",
          height: 200,
          width: 400,
          card: CardFactory.adaptiveCard({
            version: '1.0.0',
            type: 'AdaptiveCard',
            body: [
              {
                type: 'TextBlock',
                text: 'Enter Text Here'
              },
              {
                type: 'Input.Text',
                id: 'usertext',
                placeholder: 'add some text and submit',
                IsMultiline: true
              }
            ],
            actions: [
              {
                type: 'Action.Submit',
                title: 'Submit'
              }
            ]
          })
        },
      },
    };
  }

For javascript template, in src/linkUnfurlingApp.js, add following method to LinkUnfurlingApp class.

  handleTeamsTaskModuleFetch(context, taskModuleRequest) {
    return {
      task: {
        type: "continue",
        value: {
          title: "Task Module Fetch",
          height: 200,
          width: 400,
          card: CardFactory.adaptiveCard({
            version: '1.0.0',
            type: 'AdaptiveCard',
            body: [
              {
                type: 'TextBlock',
                text: 'Enter Text Here'
              },
              {
                type: 'Input.Text',
                id: 'usertext',
                placeholder: 'add some text and submit',
                IsMultiline: true
              }
            ],
            actions: [
              {
                type: 'Action.Submit',
                title: 'Submit'
              }
            ]
          })
        },
      },
    };
  }

For c# template, in LinkUnfurling/LinkUnfurlingApp.cs, add following method to LinkUnfurlingApp class.

   protected override Task<TaskModuleResponse> OnTeamsTaskModuleFetchAsync(ITurnContext<IInvokeActivity> turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
    {
        return Task.FromResult(new TaskModuleResponse
        {
            Task = new TaskModuleContinueResponse
            {
                Type = "continue",
                Value = new TaskModuleTaskInfo
                {
                    Title = "Task Module Fetch",
                    Height = 200,
                    Width = 400,
                    Card = new Attachment
                    {
                        ContentType = "application/vnd.microsoft.card.adaptive",
                        Content = new AdaptiveCard("1.0.0")
                        {
                            Version = "1.0.0",
                            Type = "AdaptiveCard",
                            Body = new List<AdaptiveElement>
                        {
                            new AdaptiveTextBlock
                            {
                                Text = "Enter Text Here",
                                Type = "TextBlock"
                            },
                            new AdaptiveTextInput
                            {
                                Id = "usertext",
                                Placeholder = "add some text and submit",
                                IsMultiline = true,
                                Type = "Input.Text"
                            }
                        },
                            Actions = new List<AdaptiveAction>
                        {
                            new AdaptiveSubmitAction
                            {
                                Title = "Submit",
                                Type = "Action.Submit"
                            }
                        }
                        }
                    }
                }
            }
        });
    }

Step 3: Add handleTeamsTaskModuleSubmit function in handler

For typescript template, in src/linkUnfurlingApp.ts, add following method to LinkUnfurlingApp class.

  public async handleTeamsTaskModuleSubmit(context: TurnContext, taskModuleRequest: TaskModuleRequest): Promise<TaskModuleResponse> {
    return {
      task: {
        type: 'message',
        value: 'Thanks!'
      }
    };
  }

For javascript template, in src/linkUnfurlingApp.js, add following method to LinkUnfurlingApp class.

  handleTeamsTaskModuleSubmit(context, taskModuleRequest) {
    return {
      task: {
        type: 'message',
        value: 'Thanks!'
      }
    };
  }

For c# template, in LinkUnfurling/LinkUnfurlingApp.cs, add following method to LinkUnfurlingApp class.

    protected override Task<TaskModuleResponse> OnTeamsTaskModuleSubmitAsync(ITurnContext<IInvokeActivity> turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
    {
        return Task.FromResult(new TaskModuleResponse
        {
            Task = new TaskModuleMessageResponse
            {
                Type = "message",
                Value = "Thanks!"
            }
        });
    }

In Teams, the adaptive card will be like:

taskModule

Click "Task module" button:

taskModuleFetch

Click "Submit" button:

taskModuleSubmit

Please refer to Task module document for more details.

How to add adaptive card action (Teams)

Adaptive Card actions allow users to interact with your card by clicking a button or selecting a choice. Follow the instructions below to add adaptive card action in your link unfurling app.

Step 1: Update bots section in manifest

The card action requires bot capability. In appPackage/manifest.json, update bots section to be following.

    "bots": [
        {
            "botId": "${{BOT_ID}}",
            "scopes": [
                "team",
                "personal",
                "groupchat"
            ],
            "supportsFiles": false,
            "isNotificationOnly": false
        }
    ]

Step 2: Update adaptive card

In src/adaptiveCards/helloWorldCard.json (Resources/helloWorldCard.json for c# template), update actions to be following.

    "actions": [
        {
            "type": "Action.Execute",
            "title": "card action",
            "verb": "cardAction",
            "id": "cardAction"
        }
    ],

Step 3: Add onAdaptiveCardInvoke function in handler

For typescript template, in src/linkUnfurlingApp.ts, add following method to LinkUnfurlingApp class.

  public async onAdaptiveCardInvoke(context: TurnContext, invokeValue: AdaptiveCardInvokeValue): Promise<AdaptiveCardInvokeResponse> {
    const card = {
      "type": "AdaptiveCard",
      "body": [
        {
          "type": "TextBlock",
          "text": "Your response was sent to the app",
          "size": "Medium",
          "weight": "Bolder",
          "wrap": true
        },
      ],
      "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
      "version": "1.4"
    };
    const res = { statusCode: 200, type: "application/vnd.microsoft.card.adaptive", value: card };
    return res;
  }

For javascript template, in src/linkUnfurlingApp.js, add following method to LinkUnfurlingApp class.

  onAdaptiveCardInvoke(context, invokeValue) {
    const card = {
      "type": "AdaptiveCard",
      "body": [
        {
          "type": "TextBlock",
          "text": "Your response was sent to the app",
          "size": "Medium",
          "weight": "Bolder",
          "wrap": true
        },
      ],
      "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
      "version": "1.4"
    };
    const res = { statusCode: 200, type: "application/vnd.microsoft.card.adaptive", value: card };
    return res;
  }

For c# template, in LinkUnfurling/LinkUnfurlingApp.cs, add following method to LinkUnfurlingApp class.

    protected override Task<AdaptiveCardInvokeResponse> OnAdaptiveCardInvokeAsync(ITurnContext<IInvokeActivity> turnContext, AdaptiveCardInvokeValue invokeValue, CancellationToken cancellationToken)
    {
        var card = new AdaptiveCard(new AdaptiveSchemaVersion("1.4"))
        {
            Body = new List<AdaptiveElement>
        {
            new AdaptiveTextBlock
            {
                Text = "Your response was sent to the app",
                Size = AdaptiveTextSize.Medium,
                Weight = AdaptiveTextWeight.Bolder,
                Wrap = true,
                Type = "TextBlock"
            }
        }
        };
        var response = new AdaptiveCardInvokeResponse
        {
            StatusCode = 200,
            Type = "application/vnd.microsoft.card.adaptive",
            Value = card,
        };
        return Task.FromResult(response);
    }

In Teams, the adaptive card will be like:

cardAction

Click card action button, the adaptive card will be updated to be following:

cardActionClick

Please refer to Universal actions document for more details.

How to Extend this template with Notification, Command and Workflow bot.

The Notification, Command and Workflow Bot are scenario templates provided by Teams Toolkit. These templates have similar structure. This guide takes Notification Bot as an example.

Step 1: Create a Notification Bot template using Teams Toolkit.

Select the Teams Toolkit icon on the left in the VS Code toolbar. Choose "Create a New App"->"Bot"->"Chat Notification Message". Wait for the download complete.

Step 2: Update source code.

Copy all methods from src/linkUnfurlingApp.ts class to Notification Bot's empty TeamsActivityHandler in src/teamsBot.ts.

Step 3: Update manifest.

Copy composeExtension section in your appPackage/manifest.json to Notification bot's appPackage/manifest.json.

Now your Notification bot project has both notification and link unfurling function.

notification link unfurling

⚠️ **GitHub.com Fallback** ⚠️