I’m having an issue getting my Angular 12 front end connected to my Azure SignalR and Azure Functions (server less Typescript) backend. I have negotiate()
firing but erroring back in the client, and haven’t gotten upstream triggering (posts) working.
I have a two part question:
- What is wrong with my Azure Function version 4 programming model binding syntax?
- Given my
negotiate()
function returns hard coded values (see 'The Preliminary Stuff - Client Side' andsignalRConnectionInfo
below) how do I solve the angular 'Error: None of the transports supported by the client are supported by the server.' when connecting?
TLDR: The negotiate()
function firing works with no additional input bindings, but returns an error to the Angular call to this.hubConnection.start().then().catch()
method, e.g.: 'Error: None of the transports supported by the client are supported by the server.'.
When I do try to add the input bindings formatted as described below the Function crashes returning status 500
before it starts execution.
The following is the walkthrough of what I found, and how I got to the binding I’m using at present. I outline all of this because I have not seen anywhere how to do this. In fact, according to Microsoft, they don’t even have examples on how to do this yet: “Example binding for Node.js model v4 not yet available.”.
To find that disclosure by Microsoft, select Programming Model version 4 (it’s a tab you can select halfway down the page). See https://learn.microsoft.com/en-us/azure/azure-functions/add-bindings-existing-function?tabs=python-v2%2Cisolated-process%2Cnode-v4&pivots=programming-language-typescript
My stack: Serverless Azure Function (Typescript) with version 4.0 programming model, Azure SignalR Free-tier Serverless. Angular 12 front end severed on Azure Static Web App (with custom domain).
The Preliminary Stuff - Client Side
My angular app is calling my Azure Function root level (e.g.: https://<FUNCTION-URL>/api/
) in the construction of the HubConnectionBuilder() (see @microsoft/signalr
doco). I’m passing the hard coded options
of endpoint
and accessKey
(and authType: ‘azure.msi;Version=1.0;’
) in the same way as shown in this example:
See https://medium.com/medialesson/serverless-real-time-messaging-with-azure-functions-and-azure-signalr-service-c70e781ff3c3
In the above article (Step 3), note the format of the SignalRConnectionInfo
object being passed as the options
parameter. As mentioned above, I’ve tried it with both the accessKey
syntax and my own guess at using an authType
parameter to try and get Server Managed Identity working.
NOTE: Yes I intend to have a secure Azure Function where I can request this prior to constructing the HubConnectionBuilder object, but for now that is unnecessary detail, and does not work being the negotiate()
input binding issue.
This gives me something in my Angular code that looks like this (in class constructor):
const options: any = {
endpoint: this.config?.signalRHostName,
authType: 'azure.msi;Version=1.0;',
// accessKey: 'ZYp9h52IfYouCanReadThisYouAreTooClosenweLQDoXMg='
};
let functionAppUrl = this.config?.functionHostName + '/api/';
this.hubConnection = new HubConnectionBuilder()
.configureLogging(LogLevel.Debug)
.withUrl(functionAppUrl, options)
.build();
this.hubConnection
.start()
.then(() => {
console.log('************* SignalR connection started');
this.sendHi();
})
.catch((err: any) => {
console.log('************* Error while starting SignalR connection', err)
});
The Preliminary Stuff - Server Side
The server side needs an Azure Function called negotiate()
that has the extra binding of signalRConnectionInfo
and returns the connection string. This second binding being the topic of the part 1) question. To address the question I looked for examples of version 4.0 programming model binding to understand how binding works and how we might game this out, in the absence of quality tutorials and examples. The following are the articles I’ve found that describe how they might bind.
I started with version 3 examples of the
negotiate()
function’s bindings (these examples are the old style per the other ‘answer’ and I add the here for prosperity of the full narrative). The example shows we need asignalRConnectionInfo
input binding as mentioned above and the values I’m going to try and replicate using version 4 programming model. See https://medium.com/holisticon-consultants/serverless-real-time-application-with-azure-signalr-5300e46a8b4bWe note the version 4 programming model of Azure Functions is different, and this article touches on how you might expect to pass those bindings now that we don’t have a
functions.json
file to play with. Particularly note section "Extra inputs and outputs“ and also not theimport
statement bringing ininput
andoutput
. Also note the example showsoutput.storageBlob
. When I try this, Visual Studio Code provides autocomplete for storageBlob, but if I tryoutput.signalr
there is nothing! Make sure you read the part “Generic inputs and outputs ” also (because I thought this might be needed per the next point). See https://learn.microsoft.com/en-au/azure/azure-functions/functions-reference-node?tabs=typescript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#supported-versionsThis link shows the Azure Functions version 4 programming model extensions. As you can see, SignalR is included (as is StorageBlob) and I would assume available Visual Studio Code autocomplete feature. However, SignalR is NOT found, so I might be (probably am) wrong on this. I’m open to input on why my assumption that SignalR should autocomplete is wrong. See https://github.com/Azure/azure-functions-extension-bundles/blob/v4.x/src/Microsoft.Azure.Functions.ExtensionBundle/extensions.json
The following article seems to imply that SignalR is compatible with the new version 4 binding extensibility model. See https://learn.microsoft.com/en-au/azure/azure-functions/functions-versions?tabs=isolated-process%2Cv4&pivots=programming-language-typescript
The Hackathon
Given the inputs and outputs for the additional bindings per point number 2) above, I was hoping I could bind configuration input data for the negotiate()
function like this:
import { app, input, output, HttpResponseInit, InvocationContext } from '@azure/functions';
import { Log, IRequest, AuthUtils, AzureADHelper } from '@wetware/viewdu-fxa';
export async function negotiate(request: IRequest, context: InvocationContext): Promise<HttpResponseInit> {
await Log.setup(context);
Log.debug(`>>> Http function processed request for url "${request.url}"`);
context.log('************* signalR context.extraInputs', context.extraInputs);
const connection = {
hubName: 'deafult',
// "connectionStringSetting": "Endpoint=https://sgr-xxxxx.service.signalr.net;AccessKey=ZYp9h52IfYouCanReadThisYouAreTooClosenweLQDoXMg=;Version=1.0;"
connectionStringSetting: 'Endpoint=https://sgr-xxxxx.service.signalr.net;AuthType=azure.msi;Version=1.0;'
};
return {
status: 200,
body: JSON.stringify(connection, null, 2)
};
};
const signalRInput = input.generic({
type: 'signalRConnectionInfo',
name: 'connectionInfo',
hubName: 'default',
// "connectionStringSetting": "AzureSignalRConnectionString",
// "connectionStringSetting": "Endpoint=https://sgr-xxxxx.service.signalr.net;AccessKey=ZYp9h52IfYouCanReadThisYouAreTooClosenweLQDoXMg=;Version=1.0;"
connectionStringSetting: 'Endpoint=https://sgr-xxxxx.service.signalr.net;AuthType=azure.msi;Version=1.0;'
})
app.http('negotiate', {
methods: ['POST', 'GET'],
authLevel: 'anonymous',
handler: negotiate,
extraInputs: [signalRInput]
});
To try and have a signalR trigger an upstream Azure Function, I was hoping I could trigger bind the Azure Function like this: See https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service-trigger?tabs=isolated-process&pivots=programming-language-javascript
import { app, input, output, HttpResponseInit, InvocationContext } from '@azure/functions';
import { Log, IRequest, AuthUtils, AzureADHelper } from '@wetware/viewdu-fxa';
export async function signalRTrigger(request: IRequest, context: InvocationContext): Promise<HttpResponseInit> {
await Log.setup(context);
Log.debug(`>>> Http function processed request for url "${request.url}"`);
context.log('************* signalR context.extraInputs', context.extraInputs);
const connection = {
hubName: 'default',
// connectionStringSetting: 'Endpoint=https://sgr-xxxxx.service.signalr.net;AccessKey=ZYp9h52IfYouCanReadThisYouAreTooClosenweLQDoXMg=;Version=1.0;',
connectionStringSetting: 'Endpoint=https://sgr-xxxxx.service.signalr.net;AuthType=azure.msi;Version=1.0;'
};
return {
status: 200,
body: JSON.stringify(connection, null, 2)
};
};
// This should provide the signalR upstream target to trigge
const signalrInput = input.generic({
type: 'signalRTrigger',
name: 'invocation',
hubName: 'default',
category: 'messages',
event: 'SendMessage',
parameterNames: [
'message'
]
});
/*
"type": "signalRTrigger",
"name": "invocation",
"hubName": "SignalRTest",
"category": "messages",
"event": "SendMessage",
"parameterNames": [
"message"
],
"direction": "in"
*/
app.http('signalr-trigger', {
methods: ['POST', 'GET'],
authLevel: 'anonymous',
handler: signalRTrigger,
extraInputs: [signalrInput]
});
Has anyone had success using the Azure Function version 4.0 model with binding specifically to Azure SignalR (server-less)? Please help.
OK, I've solved part 1 and am sharing how I got v4 Programming Model binding working using generics. I'll add to this when I solve for the 'Error: None of the transports supported by the client are supported by the server.' error.
NOTE: Even though these bindings work and the values are available within the Function, the
output
binding appears to be sending aPOST
request to SignalR with a Bearer Token. Theinput
appears to work (I'm not getting failed authentication errors in my Angular code) when I accessoptions
in thenegotiate()
function:context.log('************* signalR context', context.options.extraInputs);
But this Microsoft site states that
options
fromhttp.output()
doesn't work and not sure what that means - per above the output binding itself seems to work. See https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-output?tabs=isolated-process%2Cnodejs-v4&pivots=programming-language-typescriptIf anyone can help solve that part 2 question I'm happy to still give you the points!
Binding for the signalRTrigger Azure Function:
The new code:
For the
input
andoutput
binding make sure you have yourlocal.settings.json
and/or Azure Function configurations present or you'll get a 500 response error.For the
input
binding:I'm using the following code:
For the
output
binding:Inspiration from here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-table-output?tabs=isolated-process%2Cnodejs-v4%2Ctable-api&pivots=programming-language-typescript
I'm using the following code: