I'm developing a Microsoft Teams custom app that can add connectors to Teams channels.
My connector has been working fine for a few months now, but a few weeks ago, the ability to register new connectors has stopped working:
After clicking “Save” on the connector configuration page, “loading” shows up until it times out (see the console screenshot below).
When I look in the browser console, I see that the outbound request was actually successful and notifySuccess() was called on the save event, but Teams does not register it (see full JS code below).
Also, a handleMessageError message is emitted, but I could not figure out what the issue is.
I tried this in the native app and in Chrome, and a client tried it in another instance of Teams as well.
Is this a bug or a (undocumented?) change in the Teams API?
Console / UI screenshot
JSON Error Message:
{
"seq": 1615787354693,
"timestamp": 1615793440583,
"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": "fc0ee140-b62a-4947-9af1-d19a66a00af8",
"name": "handleMessageError"
}
JS code that runs on the connector's configuration page:
const XHR = new XMLHttpRequest();
const subscriptionApiUrl = "https://XYZ.execute-api.us-east-1.amazonaws.com/Prod/subscriptions/";
const channelsApiBaseURL = "https://www.example.com/api/library/v2/channels/";
const defaultChannelParameters = "sorting=latest&language=en&excludeReviews=true";
const url = new URL(window.location.href);
const clientId = url.searchParams.get("clientid");
const clientSecret = url.searchParams.get("clientsecret");
const teamsSettings = {
entityId: "Example",
contentUrl: "https://www.example.com/xyz",
configName: "Example"
};
var saveEvent;
console.log("Example Connector initializing");
microsoftTeams.initialize();
microsoftTeams.settings.setValidityState(true); // make Save button enabled
microsoftTeams.settings.registerOnSaveHandler(handleSaveEvent);
function handleSaveEvent(e) {
saveEvent = e;
microsoftTeams.settings.setSettings(teamsSettings);
microsoftTeams.settings.getSettings(storeSettings);
}
function storeSettings(settings) {
XHR.addEventListener("load", reportSuccess);
XHR.addEventListener("error", reportFailure);
XHR.open("POST", subscriptionApiUrl);
XHR.setRequestHeader("Content-Type", "application/json");
XHR.send(composePayload(settings.webhookUrl));
console.log("Request to store Example Connector sent");
}
function composePayload(webhookUrl) {
return JSON.stringify({
webhookUrl: webhookUrl,
gaChannelUrl: channelsApiBaseURL + document.getElementById("ga-channel-id").value + "/items?" + defaultChannelParameters,
gaClientId: clientId,
gaClientSecret: clientSecret,
cronSchedule: "0 " + document.getElementById("time").value + " * * " + document.getElementById("frequency"),
postNow: document.getElementById("post-now").checked ? true : false
});
}
function reportSuccess(e) {
console.log("Example Connector registered!");
saveEvent.notifySuccess();
}
function reportFailure(e) {
let msg = "Could not connect to subscription API.";
console.log(msg);
saveEvent.notifyFailure(msg);
}
I got the same error. I registered the connector and downloaded the generated manifest.json then packaged (with icons) and tried to sideload the zip. I got the same error when trying to "Save".
I then tried to edit the generated manifest using the App Studio (App Studio -> Import an existing app) and this time I got a meaningful error saying the property needsIdentity is not valid according the schema from the manifest (https://developer.microsoft.com/en-us/json-schemas/teams/v1.3/MicrosoftTeams.schema.json)
Removing this property fixed the issue and I was able to save the connector configuration.
I could not find any documentation about this property. I checked the last version of the schema by now (1.8) and it's not there !!
I created an issue : https://github.com/MicrosoftDocs/msteams-docs/issues/2949
Related
I am a newbie to outlook js. I am developing a very simple add-in. The add-in simply will forward a selected email to a defined email address. So we click a button and forward the message. My command handler gets called, but that is about all I have gotten to work. The first problem is the authorization does not appear to work. I have followed the example on https://learn.microsoft.com/en-us/outlook/add-ins/use-rest-api.
The permission in my manifest is set to ReadWriteMailbox.
var accessToken;
Office.onReady(info => {
// If needed, Office.js is ready to be called
Office.context.mailbox.getCallbackTokenAsync({ isRest: true }, function(result) {
if (result.status === "succeeded") {
accessToken = result.value;
} else {
accessToken = "error";
}
});
});
function MyButtonClick(event) {
const message = {
type: Office.MailboxEnums.ItemNotificationMessageType.InformationalMessage,
message: "Performed action. Access token: " + accessToken,
icon: "Icon.80x80",
persistent: true
}
Office.context.mailbox.item.notificationMessages.replaceAsync("action", message);
event.completed();
}
I have tried moving the getCallbackTokenAsync all around, but it seems not to work properly. The accessToken is always undefined.
I have been messing with this for the past day. So I am assuming I am missing something.
We are primarily targeting Outlook 2016 on the mac and windows 10.
Any thoughts?
Tom
I'm doing the tutorial for IBM Watson Speech-to-text. In the section "Using the WebSocket interface", subsection "Opening a connection and passing credentials", I copied the following code:
var token = watsonToken;
console.log(token); // token looks good
var wsURI = 'wss://stream.watsonplatform.net/speech-to-text/api/v1/recognize?watson-token=' +
token + '&model=es-ES_BroadbandModel';
var websocket = new WebSocket(wsURI);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
I'm using Angular so I made a value for the token:
app.value('watsonToken', 'Ln%2FV...');
I get back an error message:
WebSocket connection to 'wss://stream.watsonplatform.net/speech-to-text/api/v1/recognize?watson-toke...&model=es-ES_BroadbandModel' failed: HTTP Authentication failed; no valid credentials available
I tried hardcoding the token:
var wsURI = 'wss://stream.watsonplatform.net/speech-to-text/api/v1/recognize?watson-token=Ln%2FV2...&model=es-ES_BroadbandModel';
Same error message.
IBM's documentation on tokens says that an expired or invalid token will return a 401 error, which I didn't get, so I presume that my token is neither expired nor invalid. Any suggestions?
I think you can see the Official Example from IBM Developers here.
The error is because the authentication does not work fine before you send the request to recognize, try to follow the same step inside this repository, like:
const QUERY_PARAMS_ALLOWED = ['model', 'X-Watson-Learning-Opt-Out', 'watson-token', 'customization_id'];
/**
* pipe()-able Node.js Readable/Writeable stream - accepts binary audio and emits text in it's `data` events.
* Also emits `results` events with interim results and other data.
* Uses WebSockets under the hood. For audio with no recognizable speech, no `data` events are emitted.
* #param {Object} options
* #constructor
*/
function RecognizeStream(options) {
Duplex.call(this, options);
this.options = options;
this.listening = false;
this.initialized = false;
}
util.inherits(RecognizeStream, Duplex);
RecognizeStream.prototype.initialize = function() {
const options = this.options;
if (options.token && !options['watson-token']) {
options['watson-token'] = options.token;
}
if (options.content_type && !options['content-type']) {
options['content-type'] = options.content_type;
}
if (options['X-WDC-PL-OPT-OUT'] && !options['X-Watson-Learning-Opt-Out']) {
options['X-Watson-Learning-Opt-Out'] = options['X-WDC-PL-OPT-OUT'];
}
const queryParams = extend({ model: 'en-US_BroadbandModel' }, pick(options, QUERY_PARAMS_ALLOWED));
const queryString = Object.keys(queryParams)
.map(function(key) {
return key + '=' + (key === 'watson-token' ? queryParams[key] : encodeURIComponent(queryParams[key])); // our server chokes if the token is correctly url-encoded
})
.join('&');
const url = (options.url || 'wss://stream.watsonplatform.net/speech-to-text/api').replace(/^http/, 'ws') + '/v1/recognize?' + queryString;
const openingMessage = extend(
{
action: 'start',
'content-type': 'audio/wav',
continuous: true,
interim_results: true,
word_confidence: true,
timestamps: true,
max_alternatives: 3,
inactivity_timeout: 600
},
pick(options, OPENING_MESSAGE_PARAMS_ALLOWED)
);
This code is from IBM Developers and for my project I'm using and works perfectly.
You can see in the code line #53, set the listening to true, otherwise it will eventually timeout and close automatically with inactivity_timeout applies when you're sending audio with no speech in it, not when you aren't sending any data at all.
Have another example, see this example from IBM Watson - Watson Developer Cloud using Javascript for Speech to Text.
Elementary, my dear Watson! There are three or four things to pay attention to with IBM Watson tokens.
First, you won't get a token if you use your IBMid and password. You have to use the username and password that were provided for a project. That username is a string of letters and numbers with hyphens.
Second, the documentation for tokens gives you code for getting a token:
curl -X GET --user {username}:{password}
--output token
"https://stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/text-to-speech/api"
Part of that code is hidden on the webpage, specifically the part that says /text-to-speech/. You need to change that to the Watson product or service you want to use, e.g., /speech-to-text/. Tokens are for specific projects and specific services.
Third, tokens expire in one hour.
Lastly, I had to put in backslashes to get the code to run in my terminal:
curl -X GET --user s0921i-s002d-dh9328d9-hd923:wy928ye98e \
--output token \
"https://stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api"
I am working on a Firefox extension that would display the SSL certificate information to the user. The actual information would be the same as the one built in to the browser, but I will be experimenting with layouts and other information for UX.
I've been working with Firefox extensions instead of add ons due to deprecation of add-ons in 2017, but this project will be finished before then.
I was trying the example found here, but the extension seems to stop on the require("chrome").
Next I tried writing simpler code to figure out how the example works, but this code doesn't have a channel attached to the request. My code, minus all sorts of printing statements, is below:
document.getElementById("click_button").addEventListener("click",
function(e) {
var url = "https://secure-website-example.google.com";
xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.addEventListener("error",
function(e) {
dumpSecurityInfo(xhr, -1);
}, false);
xhr.onload = function(e) {
dumpSecurityInfo(xhr);
};
xhr.send();
});
function dumpSecurityInfo(xhr, error) {
var channel = xhr.channel;
try {
console.log("Connection status:");
if (!error) { console.log("Succeeded"); }
else { console.log("Failed :("); }
var securityInfo = channel.securityInfo;
} catch(err) {
alert(err);
}
}
with a manifest like this:
"manifest_version": 2,
"name": "Certificate Browser",
"version": "1.0",
...
"permissions": [
"activeTab",
"webRequest",
"https://secure-website-example.google.com/*"
],
"browser_action": {
...
"default_popup": "popup/certificate_information.html"
}
Am I missing any permissions necessary to have access to the certificate? Is there a better way of grabbing certificate information?
The wiki page you linked to refers to APIs available in the addon sdk and bootstrapped extensions. The kind of manifest indicates you're writing a webextensions which are far more limited.
I am using NodeJS and Sinch-RTC (for IM feature) and I am unable to receive IM messages on my server - Meaning my onIncomingMessage is never called even though a message is sent to my username via phone. I am using the following code on my server.
var SinchClient = require('sinch-rtc');
var sinchClient = new SinchClient({
applicationKey: 'MY-APP-KEY',
capabilities: {messaging: true, calling: true},
supportActiveConnection: true,
});
var messageClient = sinchClient.getMessageClient();
sinchClient.start({username: 'MY_USERNAME', password: 'MY_PASSWORD'}).then(function() {
console.log('Success!');
global_username = username;
}).fail(handleFail)
var handleFail = function() {console.log("Message Sending failed");};
var eventListener = {
onIncomingMessage: function(message) {
console.log( message.textBody);
}
}
messageClient.addEventListener(eventListener);
When I run this program as "node sinchReceive.js" I am unable to receive any sinch message sent to MY_USERNAME. However, I can send a message to a different username and that doesn't have any problem. Please tell me what could be wrong or if I am missing something. Thanks.
I am having difficulties using the Kapsel OData plugin to retrieve data from a store when the device is offline.
Here is the situation :
Cordova application for Windows platform
When the app opens, I start by opening a store against my OData service (similar to the Northwind service)
The store is created and opened. I then try and retrieve data from the store using OData.read or by setting a model and then calling read() on it.
The store will successfully open. However, my call to read the data will succeed when the device is online, and fail when it is offline, no matter which of the two previous methods I use.
Here is my code :
function openStore() {
var properties = {
"name": "Emergency",
"host": applicationContext.registrationContext.serverHost,
"port": applicationContext.registrationContext.serverPort,
"https": applicationContext.registrationContext.https,
"serviceRoot": appId,
"definingRequests": {
"Products": "/Products"
}
};
store = sap.OData.createOfflineStore(properties);
store.open(openStoreSuccessCallback, errorCallback);
}
function openStoreSuccessCallback() {
sap.OData.applyHttpClient();
retrieveWithModel();//retrieveWithOData();
}
function retrieveWithModel() {
var uri = applicationContext.applicationEndpointURL;
var user = applicationContext.registrationContext.user;
var password = applicationContext.registrationContext.password;
var headers = { "X-SMP-APPCID": applicationContext.applicationConnectionId };
var oModel = new sap.ui.model.odata.ODataModel(uri, {
json: "true",
user: user,
password: password,
headers: headers
});
sap.ui.getCore().setModel(oModel);
oModel.read("/Products", {
success: function (oEvent) {
var msg = new Windows.UI.Popups.MessageDialog("Success");
msg.showAsync();
},
error: function (err) {
console.log("you have failed");
var msg = new Windows.UI.Popups.MessageDialog("Fail");
msg.showAsync();
}
});
}
function retrieveWithOData() {
var sURL = applicationContext.applicationEndpointURL + "/Products";
var oHeaders = {};
oHeaders['Authorization'] = authStr;
oHeaders['X-SMP-APPCID'] = applicationContext.applicationConnectionId;
//oHeaders['Content-Type'] = "application/json";
//oHeaders['X-CSRF-Token'] = "FETCH";
var request = {
headers: oHeaders,
requestUri: sURL,
method: "GET"
};
OData.read(request,
function (data, response) {
console.log('Success');
},
function (err) {
console.log('Fail');
}
);
}
Kapsel SDK version is 3.8.0
SMP SDK is SP08
Cordova version 5.3.3
I am wondering if this is an issue with the way the app is launched. I need a way to open the same instance of the application every time, so the offline store will be kept with all its data. Because Cordova-generated Visual Studio projects do not generate an .exe file (only .appx files which would need to be signed and sideloaded to be used), the way I proceed is : I run the application in online mode from Visual Studio, then pin it to the taskbar or start menu, close it and switch the device to offline mode, and reopen it from the task bar.
However, more and more it seems like this method does not work as expected.
Can anyone confirm that a Visual Studio project, opened from the taskbar, should run the same way as when it is run from VS, with the same dependencies, libraries etc? If such is the case (and I can't really imagine why it wouldn't be), does anyone have any experience with these technologies and see what a potential issue could be?
Any help would be greatly appreciated.
Thanks!
Ok I found the solution to my problem. In case anyone ever encounters the same issue, the problem was that my offline store was not being used (you can see with Fiddler that there are outbound requests to the backend system even in offline mode).
The Visual studio project does keep the store from one build or launch to the next.