I am using Microsoft Teams Toolkit to create a configurable tab. When I am in the configurable tab, I would like to save the Deeplink for this tab to one of my database. However what I see is the App id is changing for the app package I have. I generated the app package using the Teams toolkit and then I uploaded in two Teams Channel on different tenant. I see the app id is changed in both the link. How can I make sure that app id remain same. Also is there any way I can get app id and Tab id dynamically in Tab itself?
[![enter image description here][1]][1]
Here is my code:
microsoftTeams.getContext((context) => {
teamId = context.groupId;
tenantid = context.tid;
channelId = context.channelId;
const entityId =
"_djb2_msteams_prefix_" +
Util.djb2_hash("<<AppId>>" + ":" + context.entityId.replace(/\+/g, " "));
var ctx ;
if (context.subEntityId || context.channelId) {
ctx = encodeURIComponent(JSON.stringify({
subEntityId: context.subEntityId,
channelId: context.channelId
}));
};```
//How to get the app id and the tab id
linkUrl="https://teams.microsoft.com/l/entity/**<<AppId>>**/"+entityId+"/" + "&context=" + ctx+ "&groupId="+context.groupId+"&tenantId="+context.tid;
});
};
[1]: https://i.stack.imgur.com/T5jGc.png
If you're using the same app package, then the app id will be the same in both cases (it's the one defined in your manifest file inside the package), and the entity id will be the same in both tenants also for the same reason.
I have managed to resolve the issue. Below is the way, you can get the app id in your configurable tab dynamically:
microsoftTeams.getTabInstances(function (tabInstances) {
// alert(JSON.stringify(tabInstances));
var tab = JSON.parse(JSON.stringify(tabInstances.teamTabs.find(x => x.channelId ==channelId)));
console.log(JSON.stringify(tab));
appid = tab["appId"];
});
Note: TabInstance doesnt have appId as a property, but when you stringly the object , you see the value.
Getting the App ID
When a customer in a tenant installs your App, this actually creates an instance of that app in their tenant.
If you, as a developer - installs or sideloads multiple copies of the same app in different scopes, this also creates new app-instance
Same for individual manifest uploads to a Team
This teams-app-instance is assigned an 'Internal App-ID'
you can see this in the Teams AppCatalog :
where the externalid is the id you specified in your App's manifest.json
In my personal experience you should be able to use the Teams_App_Manifest_id , but that you may/will into trouble when that resolves to more than one app-instance with that same external-id.
Note that links generated by the teams client tend to use the internal id. I assume to avoid that issue
Getting the Tab ID
as in the Context you have the Team.id and Channel.id and your Tab's Entity.id is, you then can use Graph to iterate across the tabs in that channel
https://graph.microsoft.com/beta/teams/{groupId}/channels/{channelId}/tabs?$expand=teamsApp&$filter=configuration/entityId eq '{entityId}'
Tenant App Catalog:
graph explorer
{
"#odata.context": "https://graph.microsoft.com/beta/$metadata#appCatalogs/teamsApps",
"value": [
{
"id": "fcb9685a-8bc4-4b82-947b-12f8d2146ec3",
"externalId": "73fb4e73-97e2-44f6-a880-160933ea881e",
"displayName": "App SSO in csharp",
"distributionMethod": "organization"
},
App install by user from tenant catalog (organization)
graph explorer
{
"#odata.context": "https://graph.microsoft.com/beta/$metadata#users('cb4d216f-1e86-4b8b-94f0-c213b258d376')/teamwork/installedApps(teamsApp())",
"#odata.count": 86,
"value": [
{
"id": "Y2I0ZDIxNmYtMWU4Ni00YjhiLTk0ZjAtYzIxM2IyNThkMzc2IyNmY2I5Njg1YS04YmM0LTRiODItOTQ3Yi0xMmY4ZDIxNDZlYzM=",
"teamsApp": {
"id": "fcb9685a-8bc4-4b82-947b-12f8d2146ec3",
"externalId": "73fb4e73-97e2-44f6-a880-160933ea881e",
"displayName": "App SSO in csharp",
"distributionMethod": "organization"
}
},
adding the binary-same Teams app 'for me'
results in the app showing twice in the left nav (as expected, so this also adds an App-instance with its own (internal) App id
"#odata.count": 87,
"value": [
{
"id": "Y2I0ZDIxNmYtMWU4Ni00YjhiLTk0ZjAtYzIxM2IyNThkMzc2IyNmY2I5Njg1YS04YmM0LTRiODItOTQ3Yi0xMmY4ZDIxNDZlYzM=",
"teamsApp": {
"id": "fcb9685a-8bc4-4b82-947b-12f8d2146ec3",
"externalId": "73fb4e73-97e2-44f6-a880-160933ea881e",
"displayName": "App SSO in csharp",
"distributionMethod": "organization"
}
},
// ....
{
"id": "Y2I0ZDIxNmYtMWU4Ni00YjhiLTk0ZjAtYzIxM2IyNThkMzc2IyMyNWYzMDBjMS05ZGE2LTRjYTMtYWUwZS1iN2EwZDJkMDQzZGY=",
"teamsApp": {
"id": "25f300c1-9da6-4ca3-ae0e-b7a0d2d043df",
"externalId": "73fb4e73-97e2-44f6-a880-160933ea881e",
"displayName": "App SSO in csharp",
"distributionMethod": "sideloaded"
}
},
Related
I have a use case where I need to show following information in my personal application.
List of channels in teams where bot is installed.
List of users in teams where bot is installed.
I was exploring connector client for the same and came up with following code:-
const credentials = new MicrosoftAppCredentials(appId, appPassword);
const connectorClient = new ConnectorClient(credentials, {
baseUri: serviceUrl
});
const token = await credentials.getToken();
axios.defaults.headers.common.Authorization = `Bearer ${ token }`;
# To get channels
const response = await axios.get(
'https://smba.trafficmanager.net/in/v3/teams/{teamId}/conversations'
);
# To get members
const users = await connectorClient.conversations.getConversationPagedMembers(teamId);
This works perfect as long as I have the teamId.
But the issue I am facing here is with respect to finding teamId in personal scope. I install my bot application as follows by choosing the Add option.
As far as I understand, the above installs the bot in the personal scope of the user. Now, in this scenario the team id information is not present in conversationUpdate event at all. Please note that this is the first time I am installing the bot in the team, so the data should be available as per Microsoft documentation, but the only information available in channel object is tenant.
{
"membersAdded": [
{
"id": "28:f5d48856-5b42-41a0-8c3a-c5f944b679b0"
}
],
"type": "conversationUpdate",
"timestamp": "2017-02-23T19:38:35.312Z",
"localTimestamp": "2017-02-23T12:38:35.312-07:00",
"id": "f:5f85c2ad",
"channelId": "msteams",
"serviceUrl": "https://smba.trafficmanager.net/amer-client-ss.msg/",
"from": {
"id": "29:1I9Is_Sx0OIy2rQ7Xz1lcaPKlO9eqmBRTBuW6XzkFtcjqxTjPaCMij8BVMdBcL9L_RwWNJyAHFQb0TRzXgyQvA"
},
"conversation": {
"isGroup": true,
"conversationType": "channel",
"id": "19:efa9296d959346209fea44151c742e73#thread.skype"
},
"recipient": {
"id": "28:f5d48856-5b42-41a0-8c3a-c5f944b679b0",
"name": "SongsuggesterBot"
},
"channelData": {
// for me this object is empty
"team": {
"id": "19:efa9296d959346209fea44151c742e73#thread.skype"
},
"eventType": "teamMemberAdded",
"tenant": {
"id": "72f988bf-86f1-41af-91ab-2d7cd011db47"
}
}
}
Next, I also tried to install the bot in the team scope by using Add To Teams option. In this case it prompts me to select a channel to install, in which I choose general.
Now, I do get the team object inside channelData in onConversationUpdate and this flow works perfectly fine.
{
"membersAdded": [
{
"id": "28:64564f44-dd7c-441a-b427-efcd662f21b5"
}
],
"type": "conversationUpdate",
"timestamp": "2021-10-14T13:22:01.6072361Z",
"id": "f:4ebc9a41-5140-7621-33f5-31d97275ce00",
"channelId": "msteams",
"serviceUrl": "https://smba.trafficmanager.net/in/",
"from": {
"id": "29:17ZGff4Pvqz_zSNqEexg-86uBFcB6vnOBZzCwu4_puGdDsrYWCW_DdlB15PrcjC--nLlqD5CwtLMJyzXPY5OSsg",
"aadObjectId": "eac26e98-104a-4785-87aa-bcf77ea1d7c1"
},
"conversation": {
"isGroup": true,
"conversationType": "channel",
"tenantId": "c8fef0de-e240-4456-b523-3285ecc62087",
"id": "19:y7qDBfGH2jE_Ze6G8mJS_CiWiqCaRFfH77jFZvJ1xgU1#thread.tacv2"
},
"recipient": {
"id": "28:64564f44-dd7c-441a-b427-efcd662f21b5",
"name": "Trick"
},
"channelData": {
"team": {
"aadGroupId": "5bc77aa9-9487-49ae-958f-b37b2191e64d",
"name": "test 5",
"id": "19:y7qDBfGH2jE_Ze6G8mJS_CiWiqCaRFfH77jFZvJ1xgU1#thread.tacv2"
},
"eventType": "teamMemberAdded",
"tenant": {
"id": "c8fef0de-e240-4456-b523-3285ecc62087"
}
}
}
So what I am trying to understand here is that, why is the information not coming in case the bot is installed in personal scope?
I am asking this mainly because without personal scope added for bot(i.e if I keep scope only as team), the application does not show for user, inside Apps. But if I allow the scope to be extended to personal the user might select that while installing the application and my teamId information will not be available to fetch the data, that I need.
This brings to my next question, which is, is there any way in which the default add button on the add app screen installs the bot in such a way that I get team object inside channelData, in conversationUpdate in every scenario, i.e whether I choose add or add to team?
Is this how it is supposed to behave or am I missing something. Would love to hear some thoughts on this. Thanks in advance.
It might be that you're over thinking this - here's a more simple view:
if you install a bot into a Team, you'll get a Team Id (and any related channel where it is installed).
if you have "personal" scope set up for the bot, then the user also has the option to install the bot into "personal" scope. As this implies, they are NOT installing the bot INTO an actual Team - that's why you're not receiving a Team Id. It's not broken - it's entirely correct.
If you don't WANT your bot to be able to be installed in personal scope, simply remove that option in your manifest (the "personal" scope) - you have the ability to choose because it depends what you're wanting the user to be able to do with your bot. Some bots only make sense inside a Team, others only in Personal Scope, others only in Group Chat or in a Meeting - you can allow your bot to installed in any/all of these are relevant.
I made a custom connector for teams that display notifications on channels and add Tab on teams.
For the tab parts -> Everything is working well
But I have a problem for the connector parts, I get an error when I try to save my settings in the developer console:
Received error from connectors {"seq":1585127802210,"timestamp":1585127814174,"flightSettings":{"Name":"ConnectorFrontEndSettings","AriaSDKT....
-> registerOnSaveHandler is called
-> setSettings is correctly set with entityId, contentUrl (same configurationUrl as the connector configuration) and the configName.
getSettings -> is called to save to my app the webhook url -> it works
-> notifySuccess is then called and
I checked on the connector dashboard everything seems fine, on the App Studio everything is green also!
I don't know what is happening..
My manifest
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5",
"version": "1.0.0",
"id": "ca153ede-92f2-46e7-8695-3726b5343bf4",
"packageName": "com.kagilum.icescrum",
"developer": {
"name": "Kagilum SAS",
"websiteUrl": "https://www.icescrum.com",
"privacyUrl": "https://www.icescrum.com/privacy",
"termsOfUseUrl": "https://www.icescrum.com/termsofuser"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "iceScrum",
"full": "Connect with iceScrum"
},
"description": {
"short": "A true Agile project management tool",
"full": "iceScrum is a web application for using Scrum while keeping the spirit of a collaborative workspace. It also offers virtual boards with post-its for sprint backlog, product backlog and others."
},
"accentColor": "#FFFFFF",
"configurableTabs": [
{
"configurationUrl": "https://preview.icescrum.com/msTeams/setupTab/",
"canUpdateConfiguration": true,
"scopes": [
"team",
"groupchat"
],
"supportedSharePointHosts": [
"sharePointFullPage",
"sharePointWebPart"
]
}
],
"connectors": [
{
"connectorId": "f00d8890-daa8-4c87-89f5-83cbab0bccd4",
"configurationUrl": "https://preview.icescrum.com/msTeams/setup/",
"scopes": [
"team"
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"preview.icescrum.com"
]
}
Piece of code related to
microsoftTeams.settings.registerOnSaveHandler(function(saveEvent) {
microsoftTeams.settings.setSettings({
entityId: 'icescrum-pkey-' + $scope.settings.project.pkey,
contentUrl: isSettings.serverUrl + '/msTeams/setup/',
configName: $scope.settings.project.name
});
microsoftTeams.settings.getSettings(function(settings) {
$scope.settings.webhookUrl = settings.webhookUrl;
return FormService.httpPost('msTeams/save', $scope.settings, true).then(function() {
saveEvent.notifySuccess(); //wait that the settings are really saved on iceScrum side
});
});
});
microsoftTeams.settings.getSettings(function(settings) {
$scope.setup = !settings.configName;
var tokenData = JSON.parse(localStorage.getItem("msTeams-oauth"));
var userData = JSON.parse(localStorage.getItem("msTeams-user"));
if (tokenData) {
FormService.httpGet('ws/project/user/' + userData.id + '?light=true', {headers: {'Authorization': 'Bearer ' + tokenData['accessToken']}}, true).then(function(projects) {
$scope.projects = projects;
if (settings.entityId) {
$scope.settings.project = _.find($scope.projects, {pkey: settings.entityId.split('icescrum-pkey-')[1]});
}
$scope.ready = true;
});
}
});
Full error:
angular.min.js:113 2020-03-25T20:35:25.953Z Received error from connectors {"seq":1585168484680,"timestamp":1585168525943,"flightSettings":{"Name":"ConnectorFrontEndSettings","AriaSDKToken":"d127f72a3abd41c9b9dd94faca947689-d58285e6-3a68-4cab-a458-37b9d9761d35-7033","SPAEnabled":true,"ClassificationFilterEnabled":true,"ClientRoutingEnabled":true,"EnableYammerGroupOption":true,"EnableFadeMessage":false,"EnableDomainBasedOwaConnectorList":false,"EnableDomainBasedTeamsConnectorList":false,"DevPortalSPAEnabled":true,"ShowHomeNavigationButtonOnConfigurationPage":false,"DisableConnectToO365InlineDeleteFeedbackPage":true},"status":500,"clientType":"SkypeSpaces","connectorType":"f00d8890-daa8-4c87-89f5-83cbab0bccd4","name":"handleMessageError"}
In case this helps anyone else, I spent ages today trying to get to the bottom of this and couldn't find a solution. Until...as a last gasp show of desperation I decide to use App Studio to recreate the entire connector manifest from scratch, including creating a new connector in the portal.
For some reason, this then worked fine - even though I can see that the two manifest files are identical with the exception of the ID (and I already tried regenerating the ID for the original one).
Bit late to the story, but having followed multiple github issues like this and stack overflow posts, I'm pretty convinced that its the problem with Connectors dashboard. If you made any change to settings, they are not really propageted/saved correctly.
So for example, if you changed validDomains or configurationPage URL, they won't actually do anything. You can verify that with your devtools. For me, after changing the configurationPage the Teams is still making request to old URL as well as the new one, but the old request produces error that's listed in question.
The only workaround I was able to find is to recreate connector in dashboard. Reported problem to MS Teams dev team, waiting to hear back.
Also late here, but I ran into this problem and the below was the solution after 3 days of frustration.
Despite everything mentioned in the documentation, the following is required otherwise you'll get this error. This fixed things for me.
microsoftTeams.settings.registerOnSaveHandler(saveEvent => {
microsoftTeams.settings.setSettings({
contentUrl: "https://xxxxxx.ngrok.io/teams/connector"
});
saveEvent.notifySuccess();
});
The documentation states that registering a save handler is optional and Teams will handle notify success if it's not declared. WRONG. You must register a save handler.
The documentation does not state that setSettings is required. WRONG. You must set settings or else you will receive this error.
The documentation does not state that you must save a contentURL. WRONG. You must set content URL in the setSettings. You can apparently omit other things when setting your settings, but not content URL.
The documentation does not specifically mention it, but the contentURL must comply with your validURLs in your manifest. If it does not, you'll also see this error.
So in your case, you must ensure that isSettings.serverUrl (setSettings() contentURL) does match preview.icescrum.com (manifest validURLs). If they do not, you'll see this error.
I am successfully able to add a Custom Tab to a Team / Channel via Programmatically Create Channel Tab (not from Configure popup)
However, the POST request shape looks something like
{
"displayName": "my-title-display-name",
"teamsApp#odata.bind": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/bd839104-9f20-4a1f-bbb9-226897156642",
"configuration": {
"contentUrl": "https://myurl.com/index.html",
}
}
the problem is determining the "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/bd839104-9f20-4a1f-bbb9-226897156642"
part
The "bd839104-9f20-4a1f-bbb9-226897156642" GUID here does not work, even if it is defined in my manifest.json:
(the error message is
Microsoft.Graph.ServiceException: 'Code: InvalidRequest
Message: App id bd839104-9f20-4a1f-bbb9-226897156642 needs to be installed to the team 07331d69-xxxxx and be in an unblocked state to install/update a tab
)
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5",
"version": "1.0.44",
"id": "bd839104-9f20-4a1f-bbb9-226897156642",
"packageName": "software.random.com",
"developer": {
"name": "Random Software",
(etc)
I used the Microsoft Graph /teams/id/installedApps and found a random GUID (the last one in the list, my custom app) and used that GUID successfully. (it is base64 encoded)
Question
How to I map from the manifest.json ID to the installedApp id?
I see the difference between the developer generated id and the AppCatalog Id; but only Global Admins have access to this graph endpoint (ref https://learn.microsoft.com/en-us/graph/api/teamsapp-list?view=graph-rest-1.0)
I'm using the bot builder to make an action-based messaging extension. I want to get the team's group ID, so I can use the group with the graph API, however I'm having trouble getting that ID.
In my OnTurnAsync method, turnContext has Activity.ChannelData that looks like this:
{
"channel": {
"id": "19:b7082f7e8a2547d49e0c730b9566fded#thread.skype"
},
"team": {
"id": "19:b7082f7e8a2547d49e0c730b9566fded#thread.skype"
},
"tenant": {
"id": "542f825f-b8d2-4170-8b2b-3524de6143bd"
},
"source": {
"name": "compose"
}
}
Is it correct for the team id to be the same as the channel id? How do I get the group ID that I can use to interact with the team's group in the graph API from the information I have in the OnTurnAsync method?
I am looking to get All Contact folders in Outlook using https://oauthplay.azurewebsites.net/. However, for some reason I cannot get the default "Contacts" folder, just the ones created by the user. For example, I did a GET on https://outlook.office.com/api/v2.0/me/contactfolders, and this is an example of the result:
{
"value": [
{
"Id": "AAMkAGU0ZjM2ZWQ2LTZiYjQtNDY2Ny1hMTBjLTZmOTM4ZTMyMmRlNQAuAAAAAADfJok2QyPPRKN0MNMI2ntdAQAjvTBpWNeCQYCyqAy3mDiPAAAAAAFbAAA=",
"ParentFolderId": "AAMkAGU0ZjM2ZWQ2LTZiYjQtNDY2Ny1hMTBjLTZmOTM4ZTMyMmRlNQAuAAAAAADfJok2QyPPRKN0MNMI2ntdAQAjvTBpWNeCQYCyqAy3mDiPAAAAAAEOAAA=",
"DisplayName": "Test1"
},
{
"Id": "AI2ntdAQAjvTBpWNeCQYCyqAy3mDiPAAAAAAFcAAA=",
"ParentFolderId": "0MNMI2ntdAQAjvTBpWNeCQYCyqAy3mDiPAAAAAAEOAAA=",
"DisplayName": "Test2"
}
]
}
Any idea how to get the default contact folder?
You could use this api:
https://outlook.office.com/api/v2.0/me/contactfolders
This will get the contact folder collection under the default Contacts folder of the signed-in user (.../me/contactfolders), or under the specified contact folder.
For more information, please refer to this link:
Outlook Contacts REST API reference (version 2.0)