azure-webpubsub
azure-webpubsub copied to clipboard
Metadata on the connection
At this moment a connection (ConnectionContext) consists of a UserId only. For scenarios I have in mind, it should be possible to add some metadata or additional context to the connection at the point of creation.
var clientUri = _client.GetClientAccessUri("John",
on the server side we then should be able to get this information by getting it from the connection context again.
Thanks for the feedback. Do you have some scenarios for such metadata, for example, can clients edit such metadata or should it be readonly to the clients?
ConnectionContext
contains the headers and query strings of the client URL when the client connects. So one way is to append some query string to the generated URI so that connection. But in this way, such metadata is editable by the clients.
Another way would be for the SDK to add such metadata into claims
.
At this point, i add some metadata like subprotocol and some other claim-like values. So this metadata is something that the server would retrieve one time from a database or cosmos and then 'add' it to the connection. This way when the client starts sending messages, the server should have access to that metadata without having to look it up every single time.
Right now, i do the following and put the metadata inside the "clientid". This way i can get that data out again on the server side using the ConnectionContext. But this is not a neat way to do it.
var url = _webPubSubServiceClient.GetClientAccessUri($"{metadata1}:{metadata2}:{subProtocol}").AbsoluteUri;
Maybe its a nice feature to be able to manipulate the ConnectionContext and add some contextual or metadata information after the client has connected. Something like below in an Azure function:
[FunctionName("connected")] public async Task Connected([WebPubSubTrigger(WebPubSubEventType.System, "connected")] ConnectionContext connectionContext) { // lookup the metadata from some store var metaData = await _cosmosClient.GetMetaDataAsync(connectionContext.UserId); await connectionContext.AddMetaDataAsync(metaData) }
after this, we should be able to get that metadata from the connectionContext inside the Azure Function that triggers upon a message receive.
Hope my feature is clear?
Yes, the feature makes total sense to me.
The concern of adding the metadata to GetClientAccessUri
is that it actually sets the info inside the jwt token which then is a query parameter of the URL, there are some limitations of this approach that:
- URL has a length limit
- People can extract what the metadata is with the token, it might also be a concern when the server does not want to expose the metadata to the client.
Proposal
How about adding such metadata
in connect
event so that such metadata is just between the WebPubSub service and the server (e.g. Azure Function)?
Detail:
So for now we can set userId
, subprotocol
, groups
and roles
in connect
response for a connection https://azure.github.io/azure-webpubsub/references/protocol-cloudevents#success-response-format
How about adding a metadata
field there like:
{
"groups": [],
"userId": "",
"roles": [],
"subprotocol": "",
"metadata": {
"foo": "bar",
"more": { }
}
}
Later on, such metadata
can be fetched from ConnectionContext
.
I can see that "claim-like" metadata can still be useful, maybe we can support both ways. Comments?
Yes i agree with you that adding the metadata to the GetClientAccessUri is not clean.
Upon the "connect" event would be the perfect place. A client connects once (or a few times, depending on how stable the internet is e.g.) and the "backend" retrieves the metadata from somewhere and stores it on the ConnectionContext.
This solution would perfectly fit our use-case.
Thanks!
set state
is now available in https://www.npmjs.com/package/@azure/web-pubsub-express/v/1.0.0-beta.3
Sample usage:
const handler = new WebPubSubEventHandler("chat", ["https://xxx.webpubsub.azure.com"], {
handleConnect(req, res) {
// You can set the state for the connection, it lasts throughout the lifetime of the connection
res.setState("calledTime", 1);
res.success();
},
handleUserEvent(req, res) {
var calledTime = req.context.states.calledTime++;
console.log(calledTime);
// You can also set the state here
res.setState("calledTime", calledTime);
res.success();
}
});