diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props
index 1c43302c0b33..c6a8021960da 100644
--- a/eng/Packages.Data.props
+++ b/eng/Packages.Data.props
@@ -80,6 +80,7 @@
+
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md
new file mode 100644
index 000000000000..9a5a07edb942
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Release History
+
+## 1.0.0-beta.2 (Unreleased)
+
+## 1.0.0-beta.1 (2021-04-26)
+
+- The initial beta release of Microsoft.Azure.WebJobs.Extensions.WebPubSub 1.0.0
\ No newline at end of file
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Directory.Build.props b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Directory.Build.props
new file mode 100644
index 000000000000..805ca8beaf23
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Directory.Build.props
@@ -0,0 +1,7 @@
+
+
+ true
+
+
+
+
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Microsoft.Azure.WebJobs.Extensions.WebPubSub.sln b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Microsoft.Azure.WebJobs.Extensions.WebPubSub.sln
new file mode 100644
index 000000000000..f6d288f1fdd4
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Microsoft.Azure.WebJobs.Extensions.WebPubSub.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30803.129
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.WebJobs.Extensions.WebPubSub", "src\Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj", "{6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests", "tests\Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj", "{89759B66-5CF1-4A86-9D07-D3DA48300AE9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {89759B66-5CF1-4A86-9D07-D3DA48300AE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {89759B66-5CF1-4A86-9D07-D3DA48300AE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {89759B66-5CF1-4A86-9D07-D3DA48300AE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {89759B66-5CF1-4A86-9D07-D3DA48300AE9}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {AFE7981B-5322-4E4D-BACC-3380116A52F5}
+ EndGlobalSection
+EndGlobal
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md
new file mode 100644
index 000000000000..8548d66881c6
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md
@@ -0,0 +1,152 @@
+# Azure WebJobs Web PubSub client library for .NET
+
+This extension provides functionality for receiving Web PubSub webhook calls in Azure Functions, allowing you to easily write functions that respond to any event published to Web PubSub.
+
+## Getting started
+
+### Install the package
+
+Install the Web PubSub extension with [NuGet][nuget]:
+
+```Powershell
+dotnet add package Microsoft.Azure.WebJobs.Extensions.WebPubSub --prerelease
+```
+
+### Prerequisites
+
+You must have an [Azure subscription](https://azure.microsoft.com/free/) and an Azure resource group with a Web PubSub resource. Follow this [step-by-step tutorial](https://review.docs.microsoft.com/azure/azure-web-pubsub/howto-develop-create-instance?branch=release-azure-web-pubsub) to create an Azure Web PubSub instance.
+
+### Authenticate the client
+
+In order to let the extension work with Azure Web PubSub service, you will need to provide a valid `ConnectionString`.
+
+You can find the **Keys** for you Azure Web PubSub service in the [Azure Portal](https://portal.azure.com/).
+
+The `AzureWebJobsStorage` connection string is used to preserve the processing checkpoint information as required refer to [Storage considerations](https://docs.microsoft.com/azure/azure-functions/storage-considerations#storage-account-requirements)
+
+For the local development use the `local.settings.json` file to store the connection string, `` can be set to `WebPubSubConnectionString` as default supported in the extension, or you can set customized names by mapping it with `ConnectionStringSetting = ` in function binding attributes:
+
+```json
+{
+ "Values": {
+ "AzureWebJobsStorage": "UseDevelopmentStorage=true",
+ "": "Endpoint=https://.webpubsub.azure.com;AccessKey=;Version=1.0;"
+ }
+}
+```
+When deployed use the [application settings](https://docs.microsoft.com/azure/azure-functions/functions-how-to-use-azure-function-app-settings) to set the connection string.
+
+## Key concepts
+
+### Using Web PubSub input binding
+
+Please follow the [input binding tutorial](#functions-that-uses-web-pubsub-input-binding) to learn about using this extension for building `WebPubSubConnection` to create Websockets connection to service with input binding.
+
+### Using Web PubSub output binding
+
+Please follow the [output binding tutorial](#functions-that-uses-web-pubsub-output-binding) to learn about using this extension for publishing Web PubSub messages.
+
+### Using Web PubSub trigger
+
+Please follow the [trigger binding tutorial](#functions-that-uses-web-pubsub-trigger) to learn about triggering an Azure Function when an event is sent from service upstream.
+
+In `Connect` and `Message` events, function will respect return values to send back service. Then service will depend on the response to proceed the request or else. The responses and events are paired. For example, `Connect` will only respect `ConnectResponse` or `ErrorResponse`, and ignore other returns. When `ErrorResponse` is returned, service will drop client connection. Please follow the [trigger binding return value tutorial](#functions-that-uses-web-pubsub-trigger-return-value) to learn about using the trigger return value.
+
+## Examples
+
+### Functions that uses Web PubSub input binding
+
+```cs
+[FunctionName("WebPubSubInputBindingFunction")]
+public static WebPubSubConnection Run(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req,
+ [WebPubSubConnection(Hub = "simplechat", UserId = "{query.userid}")] WebPubSubConnection connection)
+{
+ Console.WriteLine("login");
+ return connection;
+}
+```
+
+### Functions that uses Web PubSub output binding
+
+```cs
+[FunctionName("WebPubSubOutputBindingFunction")]
+public static async Task RunAsync(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req,
+ [WebPubSub(Hub = "simplechat")] IAsyncCollector operation)
+{
+ await operation.AddAsync(new SendToAll
+ {
+ Message = BinaryData.FromString("Hello Web PubSub"),
+ DataType = MessageDataType.Text
+ });
+}
+```
+
+### Functions that uses Web PubSub trigger
+
+```cs
+[FunctionName("WebPubSubTriggerFunction")]
+public static void Run(
+ [WebPubSubTrigger("message", WebPubSubEventType.User)]
+ ConnectionContext context,
+ string message,
+ MessageDataType dataType)
+{
+ Console.WriteLine($"Request from: {context.userId}");
+ Console.WriteLine($"Request message: {message}");
+ Console.WriteLine($"Request message DataType: {dataType}");
+}
+```
+
+### Functions that uses Web PubSub trigger return value
+
+```cs
+[FunctionName("WebPubSubTriggerReturnValueFunction")]
+public static MessageResponse RunAsync(
+ [WebPubSubTrigger("message", WebPubSubEventType.User)] ConnectionContext context)
+{
+ return new MessageResponse
+ {
+ Message = BinaryData.FromString("ack"),
+ DataType = MessageDataType.Text
+ };
+}
+```
+
+## Troubleshooting
+
+Please refer to [Monitor Azure Functions](https://docs.microsoft.com/azure/azure-functions/functions-monitoring) for troubleshooting guidance.
+
+## Next steps
+
+Read the [introduction to Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-overview) or [creating an Azure Function guide](https://docs.microsoft.com/azure/azure-functions/functions-create-first-azure-function).
+
+## Contributing
+
+See our [CONTRIBUTING.md][contrib] for details on building,
+testing, and contributing to this library.
+
+This project welcomes contributions and suggestions. Most contributions require
+you to agree to a Contributor License Agreement (CLA) declaring that you have
+the right to, and actually do, grant us the rights to use your contribution. For
+details, visit [cla.microsoft.com][cla].
+
+This project has adopted the [Microsoft Open Source Code of Conduct][coc].
+For more information see the [Code of Conduct FAQ][coc_faq]
+or contact [opencode@microsoft.com][coc_contact] with any
+additional questions or comments.
+
+![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-net%2Fsdk%2Fsearch%2FMicrosoft.Azure.WebJobs.Extensions.WebPubSub%2FREADME.png)
+
+
+[source]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/search/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src
+[package]: https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.WebPubSub/
+[docs]: https://docs.microsoft.com/dotnet/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub
+[nuget]: https://www.nuget.org/
+
+[contrib]: https://github.com/Azure/azure-sdk-for-net/tree/master/CONTRIBUTING.md
+[cla]: https://cla.microsoft.com
+[coc]: https://opensource.microsoft.com/codeofconduct/
+[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
+[coc_contact]: mailto:opencode@microsoft.com
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub.netstandard2.0.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub.netstandard2.0.cs
new file mode 100644
index 000000000000..6d1b87d8a4e9
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub.netstandard2.0.cs
@@ -0,0 +1,234 @@
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class AddConnectionToGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public AddConnectionToGroup() { }
+ public string ConnectionId { get { throw null; } set { } }
+ public string Group { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class AddUserToGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public AddUserToGroup() { }
+ public string Group { get { throw null; } set { } }
+ public string UserId { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class CloseClientConnection : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public CloseClientConnection() { }
+ public string ConnectionId { get { throw null; } set { } }
+ public string Reason { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class ConnectionContext
+ {
+ public ConnectionContext() { }
+ public string ConnectionId { get { throw null; } }
+ public string EventName { get { throw null; } }
+ public Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubEventType EventType { get { throw null; } }
+ public System.Collections.Generic.Dictionary Headers { get { throw null; } }
+ public string Hub { get { throw null; } }
+ public string Signature { get { throw null; } }
+ public string UserId { get { throw null; } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class ConnectResponse : Microsoft.Azure.WebJobs.Extensions.WebPubSub.ServiceResponse
+ {
+ public ConnectResponse() { }
+ [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)]
+ public string[] Groups { get { throw null; } set { } }
+ [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)]
+ public string[] Roles { get { throw null; } set { } }
+ [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)]
+ public string Subprotocol { get { throw null; } set { } }
+ [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)]
+ public string UserId { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class ErrorResponse : Microsoft.Azure.WebJobs.Extensions.WebPubSub.ServiceResponse
+ {
+ [Newtonsoft.Json.JsonConstructorAttribute]
+ public ErrorResponse() { }
+ public ErrorResponse(Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubErrorCode code, string message = null) { }
+ [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Always)]
+ public Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubErrorCode Code { get { throw null; } set { } }
+ [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)]
+ public string ErrorMessage { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class GrantGroupPermission : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public GrantGroupPermission() { }
+ public string ConnectionId { get { throw null; } set { } }
+ public Azure.Messaging.WebPubSub.WebPubSubPermission Permission { get { throw null; } set { } }
+ public string TargetName { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
+ public enum MessageDataType
+ {
+ [System.Runtime.Serialization.EnumMemberAttribute(Value="binary")]
+ Binary = 0,
+ [System.Runtime.Serialization.EnumMemberAttribute(Value="json")]
+ Json = 1,
+ [System.Runtime.Serialization.EnumMemberAttribute(Value="text")]
+ Text = 2,
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class MessageResponse : Microsoft.Azure.WebJobs.Extensions.WebPubSub.ServiceResponse
+ {
+ public MessageResponse() { }
+ public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } }
+ [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Always)]
+ public System.BinaryData Message { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class RemoveConnectionFromGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public RemoveConnectionFromGroup() { }
+ public string ConnectionId { get { throw null; } set { } }
+ public string Group { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class RemoveUserFromAllGroups : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public RemoveUserFromAllGroups() { }
+ public string UserId { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class RemoveUserFromGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public RemoveUserFromGroup() { }
+ public string Group { get { throw null; } set { } }
+ public string UserId { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class RevokeGroupPermission : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public RevokeGroupPermission() { }
+ public string ConnectionId { get { throw null; } set { } }
+ public Azure.Messaging.WebPubSub.WebPubSubPermission Permission { get { throw null; } set { } }
+ public string TargetName { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class SendToAll : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public SendToAll() { }
+ public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } }
+ public string[] Excluded { get { throw null; } set { } }
+ public System.BinaryData Message { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class SendToConnection : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public SendToConnection() { }
+ public string ConnectionId { get { throw null; } set { } }
+ public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } }
+ public System.BinaryData Message { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class SendToGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public SendToGroup() { }
+ public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } }
+ public string[] Excluded { get { throw null; } set { } }
+ public string Group { get { throw null; } set { } }
+ public System.BinaryData Message { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class SendToUser : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation
+ {
+ public SendToUser() { }
+ public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } }
+ public System.BinaryData Message { get { throw null; } set { } }
+ public string UserId { get { throw null; } set { } }
+ }
+ public abstract partial class ServiceResponse
+ {
+ protected ServiceResponse() { }
+ }
+ [Microsoft.Azure.WebJobs.Description.BindingAttribute]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Parameter | System.AttributeTargets.ReturnValue)]
+ public partial class WebPubSubAttribute : System.Attribute
+ {
+ public WebPubSubAttribute() { }
+ [Microsoft.Azure.WebJobs.Description.ConnectionStringAttribute]
+ public string ConnectionStringSetting { get { throw null; } set { } }
+ [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
+ public string Hub { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public partial class WebPubSubConnection
+ {
+ public WebPubSubConnection(System.Uri url) { }
+ public string AccessToken { get { throw null; } }
+ public string BaseUrl { get { throw null; } }
+ public string Url { get { throw null; } }
+ }
+ [Microsoft.Azure.WebJobs.Description.BindingAttribute]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Parameter | System.AttributeTargets.ReturnValue)]
+ public partial class WebPubSubConnectionAttribute : System.Attribute
+ {
+ public WebPubSubConnectionAttribute() { }
+ [Microsoft.Azure.WebJobs.Description.ConnectionStringAttribute]
+ public string ConnectionStringSetting { get { throw null; } set { } }
+ [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
+ public string Hub { get { throw null; } set { } }
+ [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
+ public string UserId { get { throw null; } set { } }
+ }
+ [Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
+ public enum WebPubSubErrorCode
+ {
+ [System.Runtime.Serialization.EnumMemberAttribute(Value="unauthorized")]
+ Unauthorized = 0,
+ [System.Runtime.Serialization.EnumMemberAttribute(Value="userError")]
+ UserError = 1,
+ [System.Runtime.Serialization.EnumMemberAttribute(Value="serverError")]
+ ServerError = 2,
+ }
+ [System.Text.Json.Serialization.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
+ public enum WebPubSubEventType
+ {
+ [System.Runtime.Serialization.EnumMemberAttribute(Value="system")]
+ System = 0,
+ [System.Runtime.Serialization.EnumMemberAttribute(Value="user")]
+ User = 1,
+ }
+ public static partial class WebPubSubJobsBuilderExtensions
+ {
+ public static Microsoft.Azure.WebJobs.IWebJobsBuilder AddWebPubSub(this Microsoft.Azure.WebJobs.IWebJobsBuilder builder) { throw null; }
+ }
+ [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))]
+ public abstract partial class WebPubSubOperation
+ {
+ protected WebPubSubOperation() { }
+ public string OperationKind { get { throw null; } set { } }
+ }
+ public partial class WebPubSubOptions : Microsoft.Azure.WebJobs.Hosting.IOptionsFormatter
+ {
+ public WebPubSubOptions() { }
+ public string Hub { get { throw null; } set { } }
+ public string Format() { throw null; }
+ }
+ [Microsoft.Azure.WebJobs.Description.BindingAttribute(TriggerHandlesReturnValue=true)]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Parameter)]
+ public partial class WebPubSubTriggerAttribute : System.Attribute
+ {
+ public WebPubSubTriggerAttribute(Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubEventType eventType, string eventName) { }
+ public WebPubSubTriggerAttribute(string hub, Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubEventType eventType, string eventName) { }
+ [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
+ [System.ComponentModel.DataAnnotations.RequiredAttribute]
+ public string EventName { get { throw null; } }
+ [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
+ public Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubEventType EventType { get { throw null; } }
+ [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
+ public string Hub { get { throw null; } }
+ }
+ public partial class WebPubSubWebJobsStartup : Microsoft.Azure.WebJobs.Hosting.IWebJobsStartup
+ {
+ public WebPubSubWebJobsStartup() { }
+ public void Configure(Microsoft.Azure.WebJobs.IWebJobsBuilder builder) { }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs
new file mode 100644
index 000000000000..708d0e8c923c
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+
+using Microsoft.Extensions.Primitives;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class ConnectionContext
+ {
+ ///
+ /// The type of the message.
+ ///
+ public WebPubSubEventType EventType { get; internal set; }
+
+ ///
+ /// The event name of the message.
+ ///
+ public string EventName { get; internal set; }
+
+ ///
+ /// The hub which the message belongs to.
+ ///
+ public string Hub { get; internal set; }
+
+ ///
+ /// The connection-id of the client which send the message.
+ ///
+ public string ConnectionId { get; internal set; }
+
+ ///
+ /// The user identity of the client which send the message.
+ ///
+ public string UserId { get; internal set; }
+
+ ///
+ /// The signature for validation
+ ///
+ public string Signature { get; internal set; }
+
+ ///
+ /// The headers of request.
+ ///
+ public Dictionary Headers { get; internal set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageDataType.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageDataType.cs
new file mode 100644
index 000000000000..680fdfffe1cb
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageDataType.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum MessageDataType
+ {
+ [EnumMember(Value = "binary")]
+ Binary,
+ [EnumMember(Value = "json")]
+ Json,
+ [EnumMember(Value = "text")]
+ Text
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddConnectionToGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddConnectionToGroup.cs
new file mode 100644
index 000000000000..de465f53f9a0
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddConnectionToGroup.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class AddConnectionToGroup : WebPubSubOperation
+ {
+ public string ConnectionId { get; set; }
+
+ public string Group { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddUserToGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddUserToGroup.cs
new file mode 100644
index 000000000000..98d6d58e65b9
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddUserToGroup.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class AddUserToGroup : WebPubSubOperation
+ {
+ public string UserId { get; set; }
+
+ public string Group { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs
new file mode 100644
index 000000000000..66a6d41722b9
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal static class BinaryDataExtensions
+ {
+ public static object Convert(this BinaryData message, Type targetType)
+ {
+ if (targetType == typeof(JObject))
+ {
+ return JObject.FromObject(message.ToArray());
+ }
+
+ if (targetType == typeof(Stream))
+ {
+ return message.ToStream();
+ }
+
+ if (targetType == typeof(byte[]))
+ {
+ return message.ToArray();
+ }
+
+ if (targetType == typeof(string))
+ {
+ return message.ToString();
+ }
+
+ if (targetType == typeof(BinaryData))
+ {
+ return message;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs
new file mode 100644
index 000000000000..e482070658c0
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal class BinaryDataJsonConverter : JsonConverter
+ {
+ public override BinaryData ReadJson(JsonReader reader, Type objectType, BinaryData existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.String)
+ {
+ return BinaryData.FromString(serializer.Deserialize(reader));
+ }
+
+ var value = JToken.Load(reader);
+
+ if (TryLoadBinary(value, out var bytes))
+ {
+ return BinaryData.FromBytes(bytes);
+ }
+ // string JObject
+ return BinaryData.FromString(value.ToString());
+ }
+
+ public override void WriteJson(JsonWriter writer, BinaryData value, JsonSerializer serializer)
+ {
+ serializer.Serialize(writer, value.ToString());
+ }
+
+ private static bool TryLoadBinary(JToken input, out byte[] output)
+ {
+ if (input["type"] != null)
+ {
+ var target = input.ToObject();
+ output = target.Data;
+ return true;
+ }
+ output = null;
+ return false;
+ }
+
+ private sealed class ArrayBuffer
+ {
+ public string Type { get; set; }
+ public byte[] Data { get; set; }
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/CloseClientConnection.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/CloseClientConnection.cs
new file mode 100644
index 000000000000..25582768a5f3
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/CloseClientConnection.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class CloseClientConnection : WebPubSubOperation
+ {
+ public string ConnectionId { get; set; }
+
+ public string Reason { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs
new file mode 100644
index 000000000000..44c5bb85b243
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Azure.Messaging.WebPubSub;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class GrantGroupPermission : WebPubSubOperation
+ {
+ public string ConnectionId { get; set; }
+
+ public WebPubSubPermission Permission { get; set; }
+
+ public string TargetName { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveConnectionFromGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveConnectionFromGroup.cs
new file mode 100644
index 000000000000..8f7d00bf10b1
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveConnectionFromGroup.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class RemoveConnectionFromGroup : WebPubSubOperation
+ {
+ public string ConnectionId { get; set; }
+
+ public string Group { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromAllGroups.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromAllGroups.cs
new file mode 100644
index 000000000000..2695fb8b14f8
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromAllGroups.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class RemoveUserFromAllGroups : WebPubSubOperation
+ {
+ public string UserId { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromGroup.cs
new file mode 100644
index 000000000000..1eadb87f8be0
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromGroup.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class RemoveUserFromGroup : WebPubSubOperation
+ {
+ public string UserId { get; set; }
+
+ public string Group { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs
new file mode 100644
index 000000000000..a50b1a0f53e7
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Azure.Messaging.WebPubSub;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class RevokeGroupPermission : WebPubSubOperation
+ {
+ public string ConnectionId { get; set; }
+
+ public WebPubSubPermission Permission { get; set; }
+
+ public string TargetName { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs
new file mode 100644
index 000000000000..b4bfc7b900f8
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class SendToAll : WebPubSubOperation
+ {
+ public BinaryData Message { get; set; }
+
+ public MessageDataType DataType { get; set; } = MessageDataType.Binary;
+
+ public string[] Excluded { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs
new file mode 100644
index 000000000000..db8624af7401
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class SendToConnection : WebPubSubOperation
+ {
+ public string ConnectionId { get; set; }
+
+ public BinaryData Message { get; set; }
+
+ public MessageDataType DataType { get; set; } = MessageDataType.Binary;
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs
new file mode 100644
index 000000000000..37c0f6c4edab
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class SendToGroup : WebPubSubOperation
+ {
+ public string Group { get; set; }
+
+ public BinaryData Message { get; set; }
+
+ public MessageDataType DataType { get; set; } = MessageDataType.Binary;
+
+ public string[] Excluded { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs
new file mode 100644
index 000000000000..ca37f6bdfbf9
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class SendToUser : WebPubSubOperation
+ {
+ public string UserId { get; set; }
+
+ public BinaryData Message { get; set; }
+
+ public MessageDataType DataType { get; set; } = MessageDataType.Binary;
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs
new file mode 100644
index 000000000000..70d99b17fe0b
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public abstract class WebPubSubOperation
+ {
+ public string OperationKind
+ {
+ get
+ {
+ return GetType().Name;
+ }
+ set
+ {
+ // used in type-less for deserialize.
+ _ = value;
+ }
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ConnectResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ConnectResponse.cs
new file mode 100644
index 000000000000..769f2dd3b3f2
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ConnectResponse.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class ConnectResponse : ServiceResponse
+ {
+ [JsonProperty(Required = Required.Default)]
+ public string UserId { get; set; }
+
+ [JsonProperty(Required = Required.Default)]
+ public string[] Groups { get; set; }
+
+ [JsonProperty(Required = Required.Default)]
+ public string Subprotocol { get; set; }
+
+ [JsonProperty(Required = Required.Default)]
+ public string[] Roles { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ErrorResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ErrorResponse.cs
new file mode 100644
index 000000000000..1cdb5ed3cef9
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ErrorResponse.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class ErrorResponse : ServiceResponse
+ {
+ [JsonProperty(Required = Required.Always)]
+ public WebPubSubErrorCode Code { get; set; }
+
+ [JsonProperty(Required = Required.Default)]
+ public string ErrorMessage { get; set; }
+
+ public ErrorResponse(WebPubSubErrorCode code, string message = null)
+ {
+ Code = code;
+ ErrorMessage = message;
+ }
+
+ [JsonConstructor]
+ public ErrorResponse()
+ {
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/MessageResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/MessageResponse.cs
new file mode 100644
index 000000000000..04662a2c53b4
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/MessageResponse.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using System;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class MessageResponse : ServiceResponse
+ {
+ [JsonProperty(Required = Required.Always)]
+ [JsonConverter(typeof(BinaryDataJsonConverter))]
+ public BinaryData Message { get; set; }
+
+ public MessageDataType DataType { get; set; } = MessageDataType.Text;
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ServiceResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ServiceResponse.cs
new file mode 100644
index 000000000000..a51147948f50
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ServiceResponse.cs
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ public abstract class ServiceResponse
+ {
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/WebPubSubErrorCode.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/WebPubSubErrorCode.cs
new file mode 100644
index 000000000000..c1051d6d155b
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/WebPubSubErrorCode.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum WebPubSubErrorCode
+ {
+ [EnumMember(Value = "unauthorized")]
+ Unauthorized,
+ [EnumMember(Value = "userError")]
+ UserError,
+ [EnumMember(Value = "serverError")]
+ ServerError
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs
new file mode 100644
index 000000000000..7242477bbdb6
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Reflection;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal class WebPubSubAsyncCollector : IAsyncCollector
+ {
+ private readonly IWebPubSubService _service;
+
+ internal WebPubSubAsyncCollector(IWebPubSubService service)
+ {
+ _service = service;
+ }
+
+ public async Task AddAsync(WebPubSubOperation item, CancellationToken cancellationToken = default)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ try
+ {
+ var method = typeof(IWebPubSubService).GetMethod(item.OperationKind.ToString(),
+ BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+
+ var task = (Task)method.Invoke(_service, new object[] { item });
+
+ await task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"Not supported operation: {item.OperationKind}, exception: {ex}");
+ }
+ }
+
+ public Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs
new file mode 100644
index 000000000000..e7e1f0633388
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Web;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
+ public class WebPubSubConnection
+ {
+ public WebPubSubConnection(Uri url)
+ {
+ Url = url.ToString();
+ BaseUrl = $"{url.Scheme}://{url.Authority}{url.AbsolutePath}";
+ AccessToken = HttpUtility.ParseQueryString(url.Query)["access_token"];
+ }
+
+ public string BaseUrl { get;}
+
+ public string Url { get;}
+
+ public string AccessToken { get;}
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEventType.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEventType.cs
new file mode 100644
index 000000000000..74b61a045f2f
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEventType.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+using Newtonsoft.Json.Converters;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum WebPubSubEventType
+ {
+ [EnumMember(Value = "system")]
+ System,
+ [EnumMember(Value = "user")]
+ User,
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs
new file mode 100644
index 000000000000..541fe080ddc7
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs
@@ -0,0 +1,249 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Azure.WebJobs.Description;
+using Microsoft.Azure.WebJobs.Host.Config;
+using Microsoft.Azure.WebJobs.Logging;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [Extension("WebPubSub", "webpubsub")]
+ internal class WebPubSubConfigProvider : IExtensionConfigProvider, IAsyncConverter
+ {
+ private readonly IConfiguration _configuration;
+ private readonly INameResolver _nameResolver;
+ private readonly ILogger _logger;
+ private readonly WebPubSubOptions _options;
+ private readonly IWebPubSubTriggerDispatcher _dispatcher;
+
+ public WebPubSubConfigProvider(
+ IOptions options,
+ INameResolver nameResolver,
+ ILoggerFactory loggerFactory,
+ IConfiguration configuration)
+ {
+ _options = options.Value;
+ _logger = loggerFactory.CreateLogger(LogCategories.CreateTriggerCategory("WebPubSub"));
+ _nameResolver = nameResolver;
+ _configuration = configuration;
+ _dispatcher = new WebPubSubTriggerDispatcher(_logger);
+ }
+
+ public void Initialize(ExtensionConfigContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (string.IsNullOrEmpty(_options.ConnectionString))
+ {
+ _options.ConnectionString = _nameResolver.Resolve(Constants.WebPubSubConnectionStringName);
+ AddSettings(_options.ConnectionString);
+ }
+
+ if (string.IsNullOrEmpty(_options.Hub))
+ {
+ _options.Hub = _nameResolver.Resolve(Constants.HubNameStringName);
+ }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ var url = context.GetWebhookHandler();
+#pragma warning restore CS0618 // Type or member is obsolete
+ _logger.LogInformation($"Registered Web PubSub negotiate Endpoint = {url?.GetLeftPart(UriPartial.Path)}");
+
+ // register JsonConverters
+ RegisterJsonConverter();
+
+ // bindings
+ context
+ .AddConverter(JObject.FromObject)
+ .AddConverter(ConvertToWebPubSubOperation)
+ .AddConverter(ConvertToWebPubSubOperationArray);
+
+ // Trigger binding
+ context.AddBindingRule()
+ .BindToTrigger(new WebPubSubTriggerBindingProvider(_dispatcher, _options));
+
+ var webpubsubConnectionAttributeRule = context.AddBindingRule();
+ webpubsubConnectionAttributeRule.AddValidator(ValidateWebPubSubConnectionAttributeBinding);
+ webpubsubConnectionAttributeRule.BindToInput(GetClientConnection);
+
+ var webPubSubAttributeRule = context.AddBindingRule();
+ webPubSubAttributeRule.AddValidator(ValidateWebPubSubAttributeBinding);
+ webPubSubAttributeRule.BindToCollector(CreateCollector);
+
+ _logger.LogInformation("Azure Web PubSub binding initialized");
+ }
+
+ public Task ConvertAsync(HttpRequestMessage input, CancellationToken cancellationToken)
+ {
+ return _dispatcher.ExecuteAsync(input, _options.AllowedHosts, _options.AccessKeys, cancellationToken);
+ }
+
+ private void ValidateWebPubSubConnectionAttributeBinding(WebPubSubConnectionAttribute attribute, Type type)
+ {
+ ValidateConnectionString(
+ attribute.ConnectionStringSetting,
+ $"{nameof(WebPubSubConnectionAttribute)}.{nameof(WebPubSubConnectionAttribute.ConnectionStringSetting)}");
+ }
+
+ private void ValidateWebPubSubAttributeBinding(WebPubSubAttribute attribute, Type type)
+ {
+ ValidateConnectionString(
+ attribute.ConnectionStringSetting,
+ $"{nameof(WebPubSubAttribute)}.{nameof(WebPubSubAttribute.ConnectionStringSetting)}");
+ }
+
+ internal WebPubSubService GetService(WebPubSubAttribute attribute)
+ {
+ var connectionString = Utilities.FirstOrDefault(attribute.ConnectionStringSetting, _options.ConnectionString);
+ var hubName = Utilities.FirstOrDefault(attribute.Hub, _options.Hub);
+ return new WebPubSubService(connectionString, hubName);
+ }
+
+ private IAsyncCollector CreateCollector(WebPubSubAttribute attribute)
+ {
+ return new WebPubSubAsyncCollector(GetService(attribute));
+ }
+
+ private WebPubSubConnection GetClientConnection(WebPubSubConnectionAttribute attribute)
+ {
+ var hub = Utilities.FirstOrDefault(attribute.Hub, _options.Hub);
+ var service = new WebPubSubService(attribute.ConnectionStringSetting, hub);
+ return service.GetClientConnection(attribute.UserId);
+ }
+
+ private void ValidateConnectionString(string attributeConnectionString, string attributeConnectionStringName)
+ {
+ AddSettings(attributeConnectionString);
+ var connectionString = Utilities.FirstOrDefault(attributeConnectionString, _options.ConnectionString);
+
+ if (string.IsNullOrEmpty(connectionString))
+ {
+ throw new InvalidOperationException($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or directly in code via {nameof(WebPubSubOptions)}.{nameof(WebPubSubOptions.ConnectionString)} or {attributeConnectionStringName}.");
+ }
+ }
+
+ private void AddSettings(string connectionString)
+ {
+ if (!string.IsNullOrEmpty(connectionString))
+ {
+ var item = new ServiceConfigParser(connectionString);
+ _options.AllowedHosts.Add(item.Endpoint.Host);
+ _options.AccessKeys.Add(item.AccessKey);
+ }
+ }
+
+ internal static void RegisterJsonConverter()
+ {
+ JsonConvert.DefaultSettings = () => new JsonSerializerSettings
+ {
+ Converters = new List
+ {
+ new StringEnumConverter(),
+ new BinaryDataJsonConverter()
+ }
+ };
+ }
+
+ internal static WebPubSubOperation ConvertToWebPubSubOperation(JObject input)
+ {
+ if (input.TryGetValue("operationKind", StringComparison.OrdinalIgnoreCase, out var kind))
+ {
+ if (kind.ToString().Equals(nameof(SendToAll), StringComparison.OrdinalIgnoreCase))
+ {
+ CheckDataType(input);
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(SendToConnection), StringComparison.OrdinalIgnoreCase))
+ {
+ CheckDataType(input);
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(SendToUser), StringComparison.OrdinalIgnoreCase))
+ {
+ CheckDataType(input);
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(SendToGroup), StringComparison.OrdinalIgnoreCase))
+ {
+ CheckDataType(input);
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(AddUserToGroup), StringComparison.OrdinalIgnoreCase))
+ {
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(RemoveUserFromGroup), StringComparison.OrdinalIgnoreCase))
+ {
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(RemoveUserFromAllGroups), StringComparison.OrdinalIgnoreCase))
+ {
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(AddConnectionToGroup), StringComparison.OrdinalIgnoreCase))
+ {
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(RemoveConnectionFromGroup), StringComparison.OrdinalIgnoreCase))
+ {
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(CloseClientConnection), StringComparison.OrdinalIgnoreCase))
+ {
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(GrantGroupPermission), StringComparison.OrdinalIgnoreCase))
+ {
+ return input.ToObject();
+ }
+ else if (kind.ToString().Equals(nameof(RevokeGroupPermission), StringComparison.OrdinalIgnoreCase))
+ {
+ return input.ToObject();
+ }
+ }
+ return input.ToObject();
+ }
+
+ internal static WebPubSubOperation[] ConvertToWebPubSubOperationArray(JArray input)
+ {
+ var result = new List();
+ foreach (var item in input)
+ {
+ result.Add(ConvertToWebPubSubOperation((JObject)item));
+ }
+ return result.ToArray();
+ }
+
+ // Binary data accepts ArrayBuffer only.
+ private static void CheckDataType(JObject input)
+ {
+ if (input.TryGetValue("dataType", StringComparison.OrdinalIgnoreCase, out var value))
+ {
+ var dataType = value.ToObject();
+
+ input.TryGetValue("message", StringComparison.OrdinalIgnoreCase, out var message);
+
+ if (dataType == MessageDataType.Binary &&
+ !(message["type"] != null && message["type"].ToString().Equals("Buffer", StringComparison.OrdinalIgnoreCase)))
+ {
+ throw new ArgumentException("MessageDataType is binary, please use ArrayBuffer as message type.");
+ }
+ }
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs
new file mode 100644
index 000000000000..56635b0f83bc
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ public static class WebPubSubJobsBuilderExtensions
+ {
+ public static IWebJobsBuilder AddWebPubSub(this IWebJobsBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddExtension()
+ .ConfigureOptions(ApplyConfiguration);
+ return builder;
+ }
+
+ private static void ApplyConfiguration(IConfiguration config, WebPubSubOptions options)
+ {
+ if (config == null)
+ {
+ return;
+ }
+
+ config.Bind(options);
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs
new file mode 100644
index 000000000000..bf77105d9e97
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using Microsoft.Azure.WebJobs.Hosting;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ public class WebPubSubOptions : IOptionsFormatter
+ {
+ public string Hub { get; set; }
+
+ internal string ConnectionString { get; set; }
+
+ internal HashSet AllowedHosts { get; set; } = new HashSet();
+
+ internal HashSet AccessKeys { get; set; } = new HashSet();
+
+ ///
+ /// Formats the options as JSON objects for display.
+ ///
+ /// Options formatted as JSON.
+ public string Format()
+ {
+ // Not expose ConnectionString in logging.
+ JObject options = new JObject
+ {
+ { nameof(Hub), Hub }
+ };
+
+ return options.ToString(Formatting.Indented);
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs
new file mode 100644
index 000000000000..45492ece15c0
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal static class Constants
+ {
+ // WebPubSubOptions can be set by customers.
+ public const string WebPubSubConnectionStringName = "WebPubSubConnectionString";
+ public const string HubNameStringName = "WebPubSubHub";
+
+ public static class ContentTypes
+ {
+ public const string JsonContentType = "application/json";
+ public const string BinaryContentType = "application/octet-stream";
+ public const string PlainTextContentType = "text/plain";
+ }
+
+ public static class Events
+ {
+ public const string ConnectEvent = "connect";
+ public const string ConnectedEvent = "connected";
+ public const string MessageEvent = "message";
+ public const string DisconnectedEvent = "disconnected";
+ }
+
+ public static class Headers
+ {
+ public static class CloudEvents
+ {
+ private const string Prefix = "ce-";
+ public const string Signature = Prefix + "signature";
+ public const string Hub = Prefix + "hub";
+ public const string ConnectionId = Prefix + "connectionId";
+ public const string Id = Prefix + "id";
+ public const string Time = Prefix + "time";
+ public const string SpecVersion = Prefix + "specversion";
+ public const string Type = Prefix + "type";
+ public const string Source = Prefix + "source";
+ public const string EventName = Prefix + "eventName";
+ public const string UserId = Prefix + "userId";
+
+ public const string TypeSystemPrefix = "azure.webpubsub.sys.";
+ public const string TypeUserPrefix = "azure.webpubsub.user.";
+ }
+
+ public const string WebHookRequestOrigin = "WebHook-Request-Origin";
+ public const string WebHookAllowedOrigin = "WebHook-Allowed-Origin";
+ }
+
+ public class ErrorMessages
+ {
+ public const string NotSupportedDataType = "Message only supports text, binary, json. Current value is: ";
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj
new file mode 100644
index 000000000000..41afb714b008
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj
@@ -0,0 +1,18 @@
+
+
+
+ $(RequiredTargetFrameworks)
+ Microsoft.Azure.WebJobs.Extensions.WebPubSub
+ This Azure Functions extension for Web PubSub
+ 1.0.0-beta.2
+ $(NoWarn);AZC0001;CS1591;SA1636;CA1056
+ true
+
+
+
+
+
+
+
+
+
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000000..d60aea4c5502
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
\ No newline at end of file
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs
new file mode 100644
index 000000000000..2e6ef67bec4f
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal interface IWebPubSubService
+ {
+ Task SendToAll(SendToAll webPubSubEvent);
+
+ Task CloseClientConnection(CloseClientConnection webPubSubEvent);
+
+ Task SendToConnection(SendToConnection webPubSubEvent);
+
+ Task SendToGroup(SendToGroup webPubSubEvent);
+
+ Task AddConnectionToGroup(AddConnectionToGroup webPubSubEvent);
+
+ Task RemoveConnectionFromGroup(RemoveConnectionFromGroup webPubSubEvent);
+
+ Task SendToUser(SendToUser webPubSubEvent);
+
+ Task AddUserToGroup(AddUserToGroup webPubSubEvent);
+
+ Task RemoveUserFromGroup(RemoveUserFromGroup webPubSubEvent);
+
+ Task RemoveUserFromAllGroups(RemoveUserFromAllGroups webPubSubEvent);
+
+ Task GrantGroupPermission(GrantGroupPermission webPubSubEvent);
+
+ Task RevokeGroupPermission(RevokeGroupPermission webPubSubEvent);
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ClientCertificateInfo.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ClientCertificateInfo.cs
new file mode 100644
index 000000000000..969c808ab268
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ClientCertificateInfo.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal sealed class ClientCertificateInfo
+ {
+ [JsonProperty("thumbprint")]
+ public string Thumbprint { get; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventRequest.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventRequest.cs
new file mode 100644
index 000000000000..b40233b6c2bc
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventRequest.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal sealed class ConnectEventRequest
+ {
+ [JsonProperty("claims")]
+ public IDictionary Claims { get; set; }
+
+ [JsonProperty("query")]
+ public IDictionary Query { get; set; }
+
+ [JsonProperty("subprotocols")]
+ public string[] Subprotocols { get; set; }
+
+ [JsonProperty("clientCertificates")]
+ public ClientCertificateInfo[] ClientCertificates { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventResponse.cs
new file mode 100644
index 000000000000..036545abd572
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventResponse.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal sealed class ConnectEventResponse
+ {
+ [JsonProperty("subprotocol")]
+ public string Subprotocol { get; set; }
+
+ [JsonProperty("roles")]
+ public string[] Roles { get; set; }
+
+ [JsonProperty("userId")]
+ public string UserId { get; set; }
+
+ [JsonProperty("groups")]
+ public string[] Groups { get; set; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/DisconnectEventRequest.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/DisconnectEventRequest.cs
new file mode 100644
index 000000000000..14c88faa3d5e
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/DisconnectEventRequest.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal sealed class DisconnectEventRequest
+ {
+ [JsonProperty("reason")]
+ public string Reason { get; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs
new file mode 100644
index 000000000000..b80bdb04315d
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal enum RequestType
+ {
+ Ignored,
+ Connect,
+ Disconnect,
+ User
+ }
+}
\ No newline at end of file
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs
new file mode 100644
index 000000000000..981c3de311ac
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal class ServiceConfigParser
+ {
+ private static readonly char[] _valueSeparator = new char[] { '=' };
+ private const char _partSeparator = ';';
+
+ public Uri Endpoint { get; }
+
+ public string AccessKey { get; }
+
+ public string Version { get; }
+
+ public int Port { get; }
+
+ public ServiceConfigParser(string connectionString)
+ {
+ var settings = ParseConnectionString(connectionString);
+
+ Endpoint = settings.ContainsKey("endpoint") ?
+ new Uri(settings["endpoint"]) :
+ throw new ArgumentException(nameof(Endpoint));
+ AccessKey = settings.ContainsKey("accesskey") ?
+ settings["accesskey"] :
+ throw new ArgumentException(nameof(AccessKey));
+
+ Version = settings.ContainsKey("version") ? settings["version"] : null;
+ Port = settings.ContainsKey("port") ? int.Parse(settings["port"], CultureInfo.InvariantCulture) : 80;
+ }
+
+ private static Dictionary ParseConnectionString(string connectionString)
+ {
+ if (string.IsNullOrEmpty(connectionString))
+ {
+ throw new ArgumentException("Web PubSub Service connection string is empty");
+ }
+
+ var setting = new Dictionary();
+ var items = connectionString.Split(_partSeparator);
+
+ try
+ {
+ setting = items.Where(x => x.Length > 0).ToDictionary(x => x.Split(_valueSeparator, 2)[0], y => y.Split(_valueSeparator, 2)[1], StringComparer.InvariantCultureIgnoreCase);
+ }
+ catch (Exception)
+ {
+ throw new ArgumentException($"Invalid Web PubSub connection string, please check.");
+ }
+ return setting;
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs
new file mode 100644
index 000000000000..3a290bb6b208
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs
@@ -0,0 +1,106 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading.Tasks;
+
+using Azure.Core;
+using Azure.Messaging.WebPubSub;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal class WebPubSubService : IWebPubSubService
+ {
+ private readonly WebPubSubServiceClient _client;
+ private readonly ServiceConfigParser _serviceConfig;
+
+ public WebPubSubService(string connectionString, string hub)
+ {
+ _serviceConfig = new ServiceConfigParser(connectionString);
+ _client = new WebPubSubServiceClient(connectionString, hub);
+ }
+
+ internal WebPubSubConnection GetClientConnection(string userId = null, string[] roles = null)
+ {
+ var url = _client.GetClientAccessUri(userId, roles);
+
+ #region TODO: Remove after SDK fix. Work-around to support http.
+ if (!_serviceConfig.Endpoint.Scheme.StartsWith("https", StringComparison.OrdinalIgnoreCase))
+ {
+ var replaced = url.AbsoluteUri.Replace("wss", "ws");
+ url = new Uri(replaced);
+ }
+ #endregion
+
+ return new WebPubSubConnection(url);
+ }
+
+ public async Task AddConnectionToGroup(AddConnectionToGroup webPubSubEvent)
+ {
+ await _client.AddConnectionToGroupAsync(webPubSubEvent.Group, webPubSubEvent.ConnectionId).ConfigureAwait(false);
+ }
+
+ public async Task AddUserToGroup(AddUserToGroup webPubSubEvent)
+ {
+ await _client.AddUserToGroupAsync(webPubSubEvent.Group, webPubSubEvent.UserId).ConfigureAwait(false);
+ }
+
+ public async Task CloseClientConnection(CloseClientConnection webPubSubEvent)
+ {
+ await _client.CloseClientConnectionAsync(webPubSubEvent.ConnectionId, webPubSubEvent.Reason).ConfigureAwait(false);
+ }
+
+ public async Task GrantGroupPermission(GrantGroupPermission webPubSubEvent)
+ {
+ await _client.GrantPermissionAsync(webPubSubEvent.Permission, webPubSubEvent.ConnectionId).ConfigureAwait(false);
+ }
+
+ public async Task RemoveConnectionFromGroup(RemoveConnectionFromGroup webPubSubEvent)
+ {
+ await _client.RemoveConnectionFromGroupAsync(webPubSubEvent.Group, webPubSubEvent.ConnectionId).ConfigureAwait(false);
+ }
+
+ public async Task RemoveUserFromAllGroups(RemoveUserFromAllGroups webPubSubEvent)
+ {
+ await _client.RemoveUserFromAllGroupsAsync(webPubSubEvent.UserId).ConfigureAwait(false);
+ }
+
+ public async Task RemoveUserFromGroup(RemoveUserFromGroup webPubSubEvent)
+ {
+ await _client.RemoveUserFromGroupAsync(webPubSubEvent.Group, webPubSubEvent.UserId).ConfigureAwait(false);
+ }
+
+ public async Task RevokeGroupPermission(RevokeGroupPermission webPubSubEvent)
+ {
+ await _client.RevokePermissionAsync(webPubSubEvent.Permission, webPubSubEvent.ConnectionId).ConfigureAwait(false);
+ }
+
+ public async Task SendToAll(SendToAll webPubSubEvent)
+ {
+ var content = RequestContent.Create(webPubSubEvent.Message);
+ var contentType = Utilities.GetContentType(webPubSubEvent.DataType);
+ await _client.SendToAllAsync(content, contentType, webPubSubEvent.Excluded).ConfigureAwait(false);
+ }
+
+ public async Task SendToConnection(SendToConnection webPubSubEvent)
+ {
+ var content = RequestContent.Create(webPubSubEvent.Message);
+ var contentType = Utilities.GetContentType(webPubSubEvent.DataType);
+ await _client.SendToConnectionAsync(webPubSubEvent.ConnectionId, content, contentType).ConfigureAwait(false);
+ }
+
+ public async Task SendToGroup(SendToGroup webPubSubEvent)
+ {
+ var content = RequestContent.Create(webPubSubEvent.Message);
+ var contentType = Utilities.GetContentType(webPubSubEvent.DataType);
+ await _client.SendToGroupAsync(webPubSubEvent.Group, content, contentType, webPubSubEvent.Excluded).ConfigureAwait(false);
+ }
+
+ public async Task SendToUser(SendToUser webPubSubEvent)
+ {
+ var content = RequestContent.Create(webPubSubEvent.Message);
+ var contentType = Utilities.GetContentType(webPubSubEvent.DataType);
+ await _client.SendToUserAsync(webPubSubEvent.UserId, content, contentType).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/IWebPubSubTriggerDispatcher.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/IWebPubSubTriggerDispatcher.cs
new file mode 100644
index 000000000000..e728b4ef3751
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/IWebPubSubTriggerDispatcher.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal interface IWebPubSubTriggerDispatcher
+ {
+ void AddListener(string key, WebPubSubListener listener);
+
+ Task ExecuteAsync(HttpRequestMessage req, HashSet allowedHosts, HashSet AccessTokens, CancellationToken token = default);
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs
new file mode 100644
index 000000000000..04ae6431079e
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Azure.WebJobs.Host.Executors;
+using Microsoft.Azure.WebJobs.Host.Listeners;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal class WebPubSubListener : IListener
+ {
+ public ITriggeredFunctionExecutor Executor { get; private set; }
+
+ private readonly string _listenerKey;
+ private readonly IWebPubSubTriggerDispatcher _dispatcher;
+
+ public WebPubSubListener(ITriggeredFunctionExecutor executor, string listenerKey, IWebPubSubTriggerDispatcher dispatcher)
+ {
+ _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
+ _listenerKey = listenerKey ?? throw new ArgumentNullException(nameof(listenerKey));
+ Executor = executor ?? throw new ArgumentNullException(nameof(executor));
+ }
+
+ public void Cancel()
+ {
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ _dispatcher.AddListener(_listenerKey, this);
+ return Task.CompletedTask;
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs
new file mode 100644
index 000000000000..9f4b03a2fb8a
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.ComponentModel.DataAnnotations;
+
+using Microsoft.Azure.WebJobs.Description;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ [AttributeUsage(AttributeTargets.Parameter)]
+#pragma warning disable CS0618 // Type or member is obsolete
+ [Binding(TriggerHandlesReturnValue = true)]
+#pragma warning restore CS0618 // Type or member is obsolete
+ public class WebPubSubTriggerAttribute : Attribute
+ {
+ ///
+ /// Used to map to method name automatically
+ ///
+ ///
+ ///
+ ///
+ public WebPubSubTriggerAttribute(string hub, WebPubSubEventType eventType, string eventName)
+ {
+ Hub = hub;
+ EventName = eventName;
+ EventType = eventType;
+ }
+
+ public WebPubSubTriggerAttribute(WebPubSubEventType eventType, string eventName)
+ : this ("", eventType, eventName)
+ {
+ }
+
+ ///
+ /// The hub of request.
+ ///
+ [AutoResolve]
+ public string Hub { get; }
+
+ ///
+ /// The event of the request
+ ///
+ [Required]
+ [AutoResolve]
+ public string EventName { get; }
+
+ ///
+ /// The event type, allowed value is system or user
+ ///
+ [AutoResolve]
+ public WebPubSubEventType EventType { get; }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs
new file mode 100644
index 000000000000..a89a1d6ea4a3
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs
@@ -0,0 +1,245 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Azure.WebJobs.Host.Bindings;
+using Microsoft.Azure.WebJobs.Host.Listeners;
+using Microsoft.Azure.WebJobs.Host.Protocols;
+using Microsoft.Azure.WebJobs.Host.Triggers;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal class WebPubSubTriggerBinding : ITriggerBinding
+ {
+ private readonly ParameterInfo _parameterInfo;
+ private readonly WebPubSubTriggerAttribute _attribute;
+ private readonly IWebPubSubTriggerDispatcher _dispatcher;
+ private readonly WebPubSubOptions _options;
+
+ public WebPubSubTriggerBinding(ParameterInfo parameterInfo, WebPubSubTriggerAttribute attribute, WebPubSubOptions options, IWebPubSubTriggerDispatcher dispatcher)
+ {
+ _parameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
+ _attribute = attribute ?? throw new ArgumentNullException(nameof(attribute));
+ _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+
+ BindingDataContract = CreateBindingContract(parameterInfo);
+ }
+
+ public Type TriggerValueType => typeof(WebPubSubTriggerEvent);
+
+ public IReadOnlyDictionary BindingDataContract { get; }
+
+ public Task BindAsync(object value, ValueBindingContext context)
+ {
+ var bindingData = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ if (value is WebPubSubTriggerEvent triggerEvent)
+ {
+ AddBindingData(bindingData, triggerEvent);
+
+ return Task.FromResult(new TriggerData(new WebPubSubTriggerValueProvider(_parameterInfo, triggerEvent), bindingData)
+ {
+ ReturnValueProvider = new TriggerReturnValueProvider(triggerEvent.TaskCompletionSource),
+ });
+ }
+
+ return Task.FromResult(null);
+ }
+
+ public Task CreateListenerAsync(ListenerFactoryContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Get listener key from attributes.
+ var hub = Utilities.FirstOrDefault(_attribute.Hub, _options.Hub);
+ if (string.IsNullOrEmpty(hub))
+ {
+ throw new ArgumentException("Hub name should be configured in either attribute or appsettings.");
+ }
+ var attributeName = $"{hub}.{_attribute.EventType}.{_attribute.EventName}";
+ var listernerKey = attributeName;
+
+ return Task.FromResult(new WebPubSubListener(context.Executor, listernerKey, _dispatcher));
+ }
+
+ public ParameterDescriptor ToParameterDescriptor()
+ {
+ return new ParameterDescriptor
+ {
+ Name = _parameterInfo.Name,
+ };
+ }
+
+ private static void AddBindingData(Dictionary bindingData, WebPubSubTriggerEvent triggerEvent)
+ {
+ bindingData.Add(nameof(triggerEvent.ConnectionContext), triggerEvent.ConnectionContext);
+ bindingData.Add(nameof(triggerEvent.Message), triggerEvent.Message);
+ bindingData.Add(nameof(triggerEvent.DataType), triggerEvent.DataType);
+ bindingData.Add(nameof(triggerEvent.Claims), triggerEvent.Claims);
+ bindingData.Add(nameof(triggerEvent.Query), triggerEvent.Query);
+ bindingData.Add(nameof(triggerEvent.Reason), triggerEvent.Reason);
+ bindingData.Add(nameof(triggerEvent.Subprotocols), triggerEvent.Subprotocols);
+ bindingData.Add(nameof(triggerEvent.ClientCertificates), triggerEvent.ClientCertificates);
+ }
+
+ ///
+ /// Defined what other bindings can use and return value.
+ ///
+ private static IReadOnlyDictionary CreateBindingContract(ParameterInfo parameterInfo)
+ {
+ var contract = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ { "$return", typeof(object).MakeByRefType() },
+ };
+
+ contract.Add(parameterInfo.Name, parameterInfo.ParameterType);
+ SafeAddContract(() => contract.Add("ConnectionContext", parameterInfo.ParameterType));
+ SafeAddContract(() => contract.Add("Message", typeof(BinaryData)));
+ SafeAddContract(() => contract.Add("DataType", typeof(MessageDataType)));
+ SafeAddContract(() => contract.Add("Claims", typeof(IDictionary)));
+ SafeAddContract(() => contract.Add("Query", typeof(IDictionary)));
+ SafeAddContract(() => contract.Add("Reason", typeof(string)));
+ SafeAddContract(() => contract.Add("Subprotocols", typeof(string[])));
+ SafeAddContract(() => contract.Add("ClientCertificates", typeof(ClientCertificateInfo[])));
+
+ return contract;
+ }
+
+ private static void SafeAddContract(Action addValue)
+ {
+ try
+ {
+ addValue();
+ }
+ catch
+ {
+ // ignore dup
+ }
+ }
+
+ ///
+ /// A provider that responsible for providing value in various type to be bond to function method parameter.
+ ///
+ internal class WebPubSubTriggerValueProvider : IValueBinder
+ {
+ private readonly ParameterInfo _parameter;
+ private readonly WebPubSubTriggerEvent _triggerEvent;
+
+ public WebPubSubTriggerValueProvider(ParameterInfo parameter, WebPubSubTriggerEvent triggerEvent)
+ {
+ _parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
+ _triggerEvent = triggerEvent ?? throw new ArgumentNullException(nameof(triggerEvent));
+ }
+
+ public Task