diff --git a/README.md b/README.md index caf1dc71d..2105de7ba 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,35 @@ Find out more: > > | Package | Use it for | > |---------|------------| -> | `Ably.PubSub.Device` | End-user device applications (desktop, mobile, Unity, MAUI, browser-adjacent clients) — *added in the next PR in this stack* | -> | `Ably.PubSub.Server` | Server-side and backend applications — *added in the next PR in this stack* | +> | `Ably.PubSub.Device` | End-user device applications (desktop, mobile, Unity, MAUI, browser-adjacent clients) | +> | `Ably.PubSub.Server` | Server-side and backend applications (ASP.NET, Azure hosts, workers, console apps) | > | `Ably.PubSub.Core` | Internal implementation shared by the two packages above. Not intended for direct use; you receive it transitively | > +> Install the package for the side your code runs on, and create clients through that package's factory methods — **they are the supported entry points**. `Ably.PubSub.Core` is internal: a client constructed directly from `AblyRealtime` or `AblyRest` is not classified as device-side or server-side, which Ably's platform behaviour and billing depend on. +> +> ```sh +> dotnet add package Ably.PubSub.Server +> ``` +> +> ```csharp +> using IO.Ably.PubSub.Server; +> +> var realtime = PubSubServer.CreateRealtimeClient(""); +> var http = PubSubServer.CreateHttpClient(""); +> ``` +> +> ```sh +> dotnet add package Ably.PubSub.Device +> ``` +> +> ```csharp +> using IO.Ably.PubSub.Device; +> +> var realtime = PubSubDevice.CreateClient(""); +> ``` +> +> Each factory also takes a `ClientOptions` or an `Action`. The returned clients are the ordinary `AblyRealtime` and `AblyRest`, so the whole of the `IO.Ably` API remains available — including device-side connectionless operations such as message history, presence reads and token requests, which is why the device package has one door and no separate HTTP factory. +> > The compiled assembly is now `Ably.PubSub.Core.dll`. The code namespace is unchanged: `using IO.Ably;` and every public type name stay as they are for now. > > Today's [`ably.io`](https://www.nuget.org/packages/ably.io) 1.x package is unaffected and continues from a 1.x maintenance branch for a year after 2.0 becomes generally available; it is never published from this branch again. The same applies to `ably.io.push.android` and `ably.io.push.ios`, whose Xamarin-era projects are not part of the 2.0 set (see [PushNotifications.md](./PushNotifications.md)). @@ -30,8 +55,6 @@ Find out more: > Never reference `ably.io` and `Ably.PubSub.*` from the same project: they share the `IO.Ably` namespace, so mixing them is a compile error by design. > > This also applies **transitively**. NuGet dedupes only by package ID, so a graph that pulls both `ably.io` 1.x (often via a library dependency) and any `Ably.PubSub.*` package loads *both* assemblies, and every `IO.Ably.*` type then exists twice: you get compile error CS0433 where your own code names those types, and runtime type-identity failures (`InvalidCastException`-class) where a library exposes `IO.Ably` types across its API. There is no type-forwarding between the packages. Detect it with `dotnet nuget why ably.io`; if a dependency genuinely forces both, isolate them with an [`extern alias`](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/extern-alias) — note `` applies only to a **direct** `PackageReference`, so first promote `ably.io` to a direct reference of the affected project, then add `ablyLegacy` to it and `extern alias ablyLegacy;` in the consuming file — otherwise treat a both-packages graph as unsupported and migrate the transitive dependency off `ably.io`. -> -> The Installation and Usage sections below still describe the 1.x `ably.io` package; they are replaced with `Ably.PubSub.Device`/`Ably.PubSub.Server` instructions before 2.0 general availability. --- @@ -62,18 +85,18 @@ Everything you need to get started with Ably: ## Installation -The SDK is available as a [nuget package](https://www.nuget.org/packages/ably.io/). To get started with your project, install the package from the Package Manager Console or the .NET CLI. +Install the package for the side your application runs on. Server-side and backend applications use `Ably.PubSub.Server`; end-user device applications (desktop, mobile, Unity, MAUI) use `Ably.PubSub.Device`. Both bring in `Ably.PubSub.Core` transitively — never install `Ably.PubSub.Core` directly. -Package Manager Console: +Server-side (.NET CLI): ```shell -PM> Install-Package ably.io +dotnet add package Ably.PubSub.Server ``` -.NET CLI in your project directory: +Device-side (.NET CLI): ```shell -dotnet add package ably.io +dotnet add package Ably.PubSub.Device ``` ### MAUI configuration @@ -95,8 +118,11 @@ Add the following to your `.csproj` file to prevent trimming of the Ably assembl The following code connects to Ably's realtime messaging service, subscribes to a channel to receive messages, and publishes a test message to that same channel: ```csharp -// Initialize Ably Realtime client -var realtime = new AblyRealtime("your-ably-api-key"); +// Initialize an Ably Realtime client through the server-side door +using IO.Ably.PubSub.Server; + +var realtime = PubSubServer.CreateRealtimeClient("your-ably-api-key"); +// Device-side applications use: PubSubDevice.CreateClient("your-ably-api-key"); // Wait for connection to be established realtime.Connection.On(ConnectionEvent.Connected, args => @@ -175,5 +201,5 @@ var websocketOptions = new MsWebSocketOptions options.TransportFactory = new MsWebSocketTransport.TransportFactory(websocketOptions); -var realtime = new AblyRealtime(options); +var realtime = PubSubServer.CreateRealtimeClient(options); ``` diff --git a/cake-build/helpers/tools.cake b/cake-build/helpers/tools.cake index 4241f4042..92db1d4a4 100644 --- a/cake-build/helpers/tools.cake +++ b/cake-build/helpers/tools.cake @@ -11,7 +11,7 @@ public class ILRepackHelper _context = context; } - public void MergeDLLs(FilePath primaryDll, FilePath[] dllsToMerge, FilePath outputDll) + public void MergeDLLs(FilePath primaryDll, FilePath[] dllsToMerge, FilePath outputDll, FilePath internalizeExcludeFile = null) { if (!_context.FileExists(primaryDll)) { @@ -56,7 +56,9 @@ public class ILRepackHelper // Build ILRepack arguments - explicitly merge only the DLLs we specify var args = new ProcessArgumentBuilder() .Append("/targetplatform:v4") - .Append("/internalize") + .Append(internalizeExcludeFile != null && _context.FileExists(internalizeExcludeFile) + ? $"/internalize:\"{internalizeExcludeFile.FullPath}\"" + : "/internalize") // /lib: Specifies where ILRepack should search for referenced assemblies when loading the primary DLL. // This is needed because Mono.Cecil (used by ILRepack) must resolve all type references while reading // the assembly metadata, even before the merge begins. Without this, it fails to resolve types from @@ -66,6 +68,23 @@ public class ILRepackHelper .Append($"/keyfile:\"{rootDir.CombineWithFilePath("IO.Ably.snk").FullPath}\"") .Append("/parallel") .Append($"/out:\"{outputDll.FullPath}\""); + + // When ILRepack runs under Mono (macOS/Linux), the netstandard2.0 inputs' 'netstandard' + // facade lives in Mono's 4.5/Facades directory, which /targetplatform:v4 does not search; + // add it as an extra /lib so Mono.Cecil can resolve it. No-op on Windows (paths absent). + var monoFacadesCandidates = new[] + { + new DirectoryPath("/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5/Facades"), + new DirectoryPath("/usr/lib/mono/4.5/Facades") + }; + foreach (var facades in monoFacadesCandidates) + { + if (_context.DirectoryExists(facades)) + { + args.Append($"/lib:\"{facades.FullPath}\""); + break; + } + } // Add all DLL paths explicitly (primary + merging DLLs) foreach (var dllPath in dllPaths) diff --git a/cake-build/tasks/build.cake b/cake-build/tasks/build.cake index d4b554da6..435edd971 100644 --- a/cake-build/tasks/build.cake +++ b/cake-build/tasks/build.cake @@ -81,7 +81,7 @@ Task("_Build_Ably_Unity_Dll") .Description("Create merged Unity DLL with all dependencies") .Does(() => { - Information("Merging Unity dependencies into Ably.PubSub.Core.dll..."); + Information("Merging Unity dependencies and the Ably.PubSub.Device door into Ably.PubSub.Device.dll..."); var netStandard20BinPath = paths.Src .Combine("Ably.PubSub.Core") @@ -108,8 +108,19 @@ Task("_Build_Ably_Unity_Dll") throw new Exception($"Newtonsoft.Json.dll not found at: {newtonsoftDll}"); } + var deviceDoorDll = paths.Src + .Combine("Ably.PubSub.Device") + .Combine("bin/Release/netstandard2.0") + .CombineWithFilePath("Ably.PubSub.Device.dll"); + + if (!FileExists(deviceDoorDll)) + { + throw new Exception($"Device door DLL not found: {deviceDoorDll}. Please build the Ably.PubSub.Device project first."); + } + var dllsToMerge = new[] { + deviceDoorDll, netStandard20BinPath.CombineWithFilePath("IO.Ably.DeltaCodec.dll"), netStandard20BinPath.CombineWithFilePath("System.Runtime.CompilerServices.Unsafe.dll"), netStandard20BinPath.CombineWithFilePath("System.Threading.Channels.dll"), @@ -118,7 +129,10 @@ Task("_Build_Ably_Unity_Dll") }; var unityOutputPath = paths.Root.Combine("unity/Assets/Ably/Plugins"); - var outputDll = unityOutputPath.CombineWithFilePath("Ably.PubSub.Core.dll"); + // The merged Unity plugin is named after the public device door package; the + // primary input stays Ably.PubSub.Core.dll (its public API survives the merge) + // and the real Ably.PubSub.Device.dll is one of the merged, exclude-protected inputs. + var outputDll = unityOutputPath.CombineWithFilePath("Ably.PubSub.Device.dll"); // Delete existing output DLL if it exists if (FileExists(outputDll)) @@ -127,8 +141,9 @@ Task("_Build_Ably_Unity_Dll") Information($"Deleted existing DLL: {outputDll}"); } - // Merge all dependencies into primary DLL in one go - ilRepackHelper.MergeDLLs(primaryDll, dllsToMerge, outputDll); + // Merge all dependencies into primary DLL in one go, keeping the device door namespace public + var internalizeExclude = paths.Root.Combine("cake-build").CombineWithFilePath("unity-internalize-exclude.txt"); + ilRepackHelper.MergeDLLs(primaryDll, dllsToMerge, outputDll, internalizeExclude); Information($"✓ Unity DLL created at: {outputDll}"); }); diff --git a/cake-build/tasks/package.cake b/cake-build/tasks/package.cake index a9c18afcf..54e17ac9a 100644 --- a/cake-build/tasks/package.cake +++ b/cake-build/tasks/package.cake @@ -35,11 +35,13 @@ Task("_Package_Create_NuGet") Information($"Creating NuGet packages version {version}..."); // The lockstep package set. Every package here is built from this repository - // and released at the same version. Stack PR 2 adds the door packages - // (ably.pubsub.device.nuspec, ably.pubsub.server.nuspec) to this list. + // and released at the same version, and both door packages pin the core exactly. + // Packed core-first so the order matches the publish order stack PR 3 adds. var nuspecFiles = new[] { - "nuget/ably.pubsub.core.nuspec" + "nuget/ably.pubsub.core.nuspec", + "nuget/ably.pubsub.device.nuspec", + "nuget/ably.pubsub.server.nuspec" }; var nugetSettings = new NuGetPackSettings @@ -133,7 +135,7 @@ Task("_Package_Unity") /////////////////////////////////////////////////////////////////////////////// Task("Package") - .Description("Create the NuGet packages (Ably.PubSub.Core)") + .Description("Create the NuGet packages (Ably.PubSub.Core, Ably.PubSub.Device, Ably.PubSub.Server)") .IsDependentOn("_Package_Create_NuGet"); Task("UnityPackage") diff --git a/cake-build/unity-internalize-exclude.txt b/cake-build/unity-internalize-exclude.txt new file mode 100644 index 000000000..45af4c34f --- /dev/null +++ b/cake-build/unity-internalize-exclude.txt @@ -0,0 +1 @@ +^IO\.Ably\.PubSub\.Device\..* diff --git a/examples/NotificationsPublisher/NotificationsPublisher.csproj b/examples/NotificationsPublisher/NotificationsPublisher.csproj index 8a93d385f..a2a9bb85d 100644 --- a/examples/NotificationsPublisher/NotificationsPublisher.csproj +++ b/examples/NotificationsPublisher/NotificationsPublisher.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/examples/NotificationsPublisher/Program.cs b/examples/NotificationsPublisher/Program.cs index 981fae502..56bb4a36d 100644 --- a/examples/NotificationsPublisher/Program.cs +++ b/examples/NotificationsPublisher/Program.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text; using IO.Ably; +using IO.Ably.PubSub.Server; using IO.Ably.Types; using Newtonsoft.Json.Linq; using Terminal.Gui; @@ -284,7 +285,7 @@ void InitialiseAbly(string key) LogLevel = LogLevel.Warning, AutoConnect = false }; - Ably = new AblyRealtime(options); + Ably = PubSubServer.CreateRealtimeClient(options); } var key = GetCurrentKey(); diff --git a/nuget/ably.pubsub.device.nuspec b/nuget/ably.pubsub.device.nuspec new file mode 100644 index 000000000..d6a8e2e18 --- /dev/null +++ b/nuget/ably.pubsub.device.nuspec @@ -0,0 +1,50 @@ + + + + Ably.PubSub.Device + $version$ + Ably.PubSub.Device + Martin Georgiev, Sachin Shinde, Yavor Ivanov, Jack Rutherford, Tom Kirby-Green + Ably Real-time Ltd + Apache-2.0 + https://github.com/ably/ably-dotnet + icon.png + README.md + false + The supported entry point for Ably Pub/Sub in an end-user device application - a mobile or desktop app, a Unity game, a set-top box or any other client the end user holds. Create clients through PubSubDevice.CreateClient(...) in the IO.Ably.PubSub.Device namespace; it accepts an API key, an Ably token, a ClientOptions or an Action<ClientOptions>, and returns the ordinary AblyRealtime so the whole of the IO.Ably API remains available, including device-side connectionless operations such as message history, presence reads and token requests. Creating a client any other way - including directly from the Ably.PubSub.Core implementation package this depends on - does not classify it as device-side, which Ably's platform behaviour and billing depend on. Install Ably.PubSub.Server instead in a server-side application. See https://www.ably.com for more info. + https://github.com/ably/ably-dotnet/releases + ©2026 Ably Real-time Ltd + ably realtime messaging websocket pubsub presence device mobile dotnet csharp maui unity netstandard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nuget/ably.pubsub.server.nuspec b/nuget/ably.pubsub.server.nuspec new file mode 100644 index 000000000..34417e59a --- /dev/null +++ b/nuget/ably.pubsub.server.nuspec @@ -0,0 +1,56 @@ + + + + Ably.PubSub.Server + $version$ + Ably.PubSub.Server + Martin Georgiev, Sachin Shinde, Yavor Ivanov, Jack Rutherford, Tom Kirby-Green + Ably Real-time Ltd + Apache-2.0 + https://github.com/ably/ably-dotnet + icon.png + README.md + false + The supported entry point for Ably Pub/Sub in a server-side application - an ASP.NET or Azure host, a worker, a console app or any other backend the end user does not hold. Create clients through PubSubServer.CreateRealtimeClient(...) and PubSubServer.CreateHttpClient(...) in the IO.Ably.PubSub.Server namespace; each accepts an API key, an Ably token, a ClientOptions or an Action<ClientOptions>, and returns the ordinary AblyRealtime or AblyRest so the whole of the IO.Ably API remains available. Creating a client any other way - including directly from the Ably.PubSub.Core implementation package this depends on - does not classify it as server-side, which Ably's platform behaviour and billing depend on. Install Ably.PubSub.Device instead in an end-user device application. See https://www.ably.com for more info. + https://github.com/ably/ably-dotnet/releases + ©2026 Ably Real-time Ltd + ably realtime messaging websocket pubsub presence server backend dotnet csharp aspnet netstandard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ably.PubSub.Device/Ably.PubSub.Device.csproj b/src/Ably.PubSub.Device/Ably.PubSub.Device.csproj new file mode 100644 index 000000000..3bdcfeff3 --- /dev/null +++ b/src/Ably.PubSub.Device/Ably.PubSub.Device.csproj @@ -0,0 +1,61 @@ + + + + + netstandard2.0;net6.0;net7.0 + ..\IO.Ably.ruleset + Debug;Release;package + false + true + Ably.PubSub.Device + IO.Ably.PubSub.Device + + + + netstandard2.0 + + + + + true + ..\..\IO.Ably.snk + + + + bin\package + + + + true + + + + true + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/src/Ably.PubSub.Device/PubSubDevice.cs b/src/Ably.PubSub.Device/PubSubDevice.cs new file mode 100644 index 000000000..d12f1d312 --- /dev/null +++ b/src/Ably.PubSub.Device/PubSubDevice.cs @@ -0,0 +1,70 @@ +using System; +using IO.Ably.PubSub.Internal; + +namespace IO.Ably.PubSub.Device +{ + /// + /// Entry point for Ably Pub/Sub applications that run on an end-user device - a mobile or + /// desktop app, a Unity game, a set-top box or any other client the end user holds. + /// + /// This factory is the only supported way to create a client from this package. The + /// Ably.PubSub.Core package that carries the implementation is an internal dependency: a + /// client constructed directly from or is + /// not classified as device-side, which Ably's platform behaviour and billing depend on. + /// + /// + /// There is one door by design, and it returns the ordinary , so + /// the whole of the IO.Ably API remains available. Device-side connectionless + /// operations - message history, presence reads, token requests and + /// Request/RequestV2 - are available on that client through its + /// and channel APIs, so per PDR-091 there is + /// deliberately no device HTTP door. + /// + /// + /// The API-key overload is kept on purpose: per PDR-091, device-side API keys stay allowed + /// at launch and enforcement is server-side. Token authentication remains the recommended + /// scheme for anything the end user holds. + /// + /// + public static class PubSubDevice + { + /// + /// Creates a realtime client for an end-user device from an API key or an Ably token. + /// + /// + /// A valid Ably API key (of the form APP_ID.KEY_ID:KEY_SECRET) or an Ably token. + /// The core applies the same colon rule its own constructors use to tell them apart. + /// + /// A connected-on-demand realtime client. + public static AblyRealtime CreateClient(string keyOrToken) + { + return CreateClient(new ClientOptions(keyOrToken)); + } + + /// + /// Creates a realtime client for an end-user device from a set of client options. + /// + /// The client options. + /// A connected-on-demand realtime client. + /// Thrown when is null. + public static AblyRealtime CreateClient(ClientOptions options) + { + // The core constructor is [Obsolete] to steer consumers to the doors; this door is + // the sanctioned caller, constructing the client from the side-stamped copy. +#pragma warning disable CS0618 + return new AblyRealtime(Side.WithSideAgent(options, Side.DeviceAgentIdentifier)); +#pragma warning restore CS0618 + } + + /// + /// Creates a realtime client for an end-user device, configured by the supplied action. + /// + /// Action that populates the client options. + /// A connected-on-demand realtime client. + /// Thrown when is null. + public static AblyRealtime CreateClient(Action configure) + { + return CreateClient(Side.Configure(configure)); + } + } +} diff --git a/src/Ably.PubSub.NetFramework.sln b/src/Ably.PubSub.NetFramework.sln index 48a9db1b7..538bff444 100644 --- a/src/Ably.PubSub.NetFramework.sln +++ b/src/Ably.PubSub.NetFramework.sln @@ -32,14 +32,9 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Ably.PubSub.Tests.Shared", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IO.Ably.DeltaCodec", "..\lib\delta-codec\IO.Ably.DeltaCodec\IO.Ably.DeltaCodec.csproj", "{EEAD196C-8D90-4402-9EAC-273F0DF5EFCB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Server.NETFramework", "Ably.PubSub.Server.NETFramework\Ably.PubSub.Server.NETFramework.csproj", "{7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - Ably.PubSub.Tests.Shared\Ably.PubSub.Tests.Shared.projitems*{1609f12c-8216-4e7c-ade0-240bfe160242}*SharedItemsImports = 13 - Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{2d265650-b1ec-4f8d-b043-a2e3dcc23fd8}*SharedItemsImports = 4 - Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{3553e1b2-8f1f-4e83-9524-08e702c14fcf}*SharedItemsImports = 13 - Ably.PubSub.Tests.Shared\Ably.PubSub.Tests.Shared.projitems*{567feef7-41af-42f2-ad34-546a278355b1}*SharedItemsImports = 4 - Ably.PubSub.Shared.MsgPack\Ably.PubSub.Shared.MsgPack.projitems*{eebf3249-3ca3-4078-a188-f8b916c3f4c2}*SharedItemsImports = 13 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution AppStore|Any CPU = AppStore|Any CPU AppStore|ARM = AppStore|ARM @@ -266,6 +261,70 @@ Global {EEAD196C-8D90-4402-9EAC-273F0DF5EFCB}.Release|x64.Build.0 = Release|Any CPU {EEAD196C-8D90-4402-9EAC-273F0DF5EFCB}.Release|x86.ActiveCfg = Release|Any CPU {EEAD196C-8D90-4402-9EAC-273F0DF5EFCB}.Release|x86.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.AppStore|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.AppStore|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.AppStore|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.AppStore|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.AppStore|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.CI_Release|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.CI_Release|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.CI_Release|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.CI_Release|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.CI_Release|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.CI_Release|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.CI_Release|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.CI_Release|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug 4.0|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug 4.0|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug 4.0|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug 4.0|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug 4.0|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug 4.0|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug 4.0|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug 4.0|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package_netstandard|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package_netstandard|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package_netstandard|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package_netstandard|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package_netstandard|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package_netstandard|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package_netstandard|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package_netstandard|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.package|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release 4.0|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release 4.0|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release 4.0|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release 4.0|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release 4.0|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release 4.0|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release 4.0|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release 4.0|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|Any CPU.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|ARM.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|ARM.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x64.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x64.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x86.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -277,8 +336,16 @@ Global {EEBF3249-3CA3-4078-A188-F8B916C3F4C2} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} {1609F12C-8216-4E7C-ADE0-240BFE160242} = {C2F43DB7-AAA0-4687-9897-5D686EC91245} {EEAD196C-8D90-4402-9EAC-273F0DF5EFCB} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F69D4156-FC22-4B8E-AD72-2A7323D42CC4} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Ably.PubSub.Tests.Shared\Ably.PubSub.Tests.Shared.projitems*{1609f12c-8216-4e7c-ade0-240bfe160242}*SharedItemsImports = 13 + Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{2d265650-b1ec-4f8d-b043-a2e3dcc23fd8}*SharedItemsImports = 4 + Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{3553e1b2-8f1f-4e83-9524-08e702c14fcf}*SharedItemsImports = 13 + Ably.PubSub.Tests.Shared\Ably.PubSub.Tests.Shared.projitems*{567feef7-41af-42f2-ad34-546a278355b1}*SharedItemsImports = 4 + Ably.PubSub.Shared.MsgPack\Ably.PubSub.Shared.MsgPack.projitems*{eebf3249-3ca3-4078-a188-f8b916c3f4c2}*SharedItemsImports = 13 + EndGlobalSection EndGlobal diff --git a/src/Ably.PubSub.NetStandard.sln b/src/Ably.PubSub.NetStandard.sln index 4d31bce31..d77ab7eb8 100644 --- a/src/Ably.PubSub.NetStandard.sln +++ b/src/Ably.PubSub.NetStandard.sln @@ -30,12 +30,11 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Ably.PubSub.Tests.Shared", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IO.Ably.DeltaCodec", "..\lib\delta-codec\IO.Ably.DeltaCodec\IO.Ably.DeltaCodec.csproj", "{D8CAACFA-FE6E-46BC-9B23-8AA3B2055125}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Device", "Ably.PubSub.Device\Ably.PubSub.Device.csproj", "{ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Server", "Ably.PubSub.Server\Ably.PubSub.Server.csproj", "{882D22FB-68CB-4EA7-AE62-B79F7165854D}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - Ably.PubSub.Tests.Shared\Ably.PubSub.Tests.Shared.projitems*{1609f12c-8216-4e7c-ade0-240bfe160242}*SharedItemsImports = 13 - Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{3553e1b2-8f1f-4e83-9524-08e702c14fcf}*SharedItemsImports = 13 - Ably.PubSub.Shared.MsgPack\Ably.PubSub.Shared.MsgPack.projitems*{eebf3249-3ca3-4078-a188-f8b916c3f4c2}*SharedItemsImports = 13 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution AppStore|Any CPU = AppStore|Any CPU AppStore|ARM = AppStore|ARM @@ -262,6 +261,134 @@ Global {D8CAACFA-FE6E-46BC-9B23-8AA3B2055125}.Release|x64.Build.0 = Release|Any CPU {D8CAACFA-FE6E-46BC-9B23-8AA3B2055125}.Release|x86.ActiveCfg = Release|Any CPU {D8CAACFA-FE6E-46BC-9B23-8AA3B2055125}.Release|x86.Build.0 = Release|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.AppStore|ARM.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.AppStore|x64.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.AppStore|x64.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.AppStore|x86.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.AppStore|x86.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.CI_Release|Any CPU.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.CI_Release|Any CPU.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.CI_Release|ARM.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.CI_Release|ARM.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.CI_Release|x64.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.CI_Release|x64.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.CI_Release|x86.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.CI_Release|x86.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug 4.0|Any CPU.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug 4.0|Any CPU.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug 4.0|ARM.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug 4.0|ARM.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug 4.0|x64.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug 4.0|x64.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug 4.0|x86.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug 4.0|x86.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug|ARM.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug|x64.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug|x64.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug|x86.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Debug|x86.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package_netstandard|Any CPU.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package_netstandard|Any CPU.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package_netstandard|ARM.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package_netstandard|ARM.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package_netstandard|x64.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package_netstandard|x64.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package_netstandard|x86.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package_netstandard|x86.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package|Any CPU.ActiveCfg = package|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package|Any CPU.Build.0 = package|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package|ARM.ActiveCfg = package|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package|ARM.Build.0 = package|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package|x64.ActiveCfg = package|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package|x64.Build.0 = package|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package|x86.ActiveCfg = package|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.package|x86.Build.0 = package|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release 4.0|Any CPU.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release 4.0|Any CPU.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release 4.0|ARM.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release 4.0|ARM.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release 4.0|x64.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release 4.0|x64.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release 4.0|x86.ActiveCfg = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release 4.0|x86.Build.0 = Debug|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release|Any CPU.Build.0 = Release|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release|ARM.ActiveCfg = Release|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release|ARM.Build.0 = Release|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release|x64.ActiveCfg = Release|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release|x64.Build.0 = Release|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release|x86.ActiveCfg = Release|Any CPU + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B}.Release|x86.Build.0 = Release|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.AppStore|ARM.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.AppStore|x64.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.AppStore|x64.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.AppStore|x86.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.AppStore|x86.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.CI_Release|Any CPU.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.CI_Release|Any CPU.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.CI_Release|ARM.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.CI_Release|ARM.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.CI_Release|x64.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.CI_Release|x64.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.CI_Release|x86.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.CI_Release|x86.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug 4.0|Any CPU.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug 4.0|Any CPU.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug 4.0|ARM.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug 4.0|ARM.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug 4.0|x64.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug 4.0|x64.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug 4.0|x86.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug 4.0|x86.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug|ARM.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug|x64.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug|x64.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug|x86.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Debug|x86.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package_netstandard|Any CPU.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package_netstandard|Any CPU.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package_netstandard|ARM.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package_netstandard|ARM.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package_netstandard|x64.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package_netstandard|x64.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package_netstandard|x86.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package_netstandard|x86.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package|Any CPU.ActiveCfg = package|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package|Any CPU.Build.0 = package|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package|ARM.ActiveCfg = package|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package|ARM.Build.0 = package|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package|x64.ActiveCfg = package|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package|x64.Build.0 = package|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package|x86.ActiveCfg = package|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.package|x86.Build.0 = package|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release 4.0|Any CPU.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release 4.0|Any CPU.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release 4.0|ARM.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release 4.0|ARM.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release 4.0|x64.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release 4.0|x64.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release 4.0|x86.ActiveCfg = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release 4.0|x86.Build.0 = Debug|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release|Any CPU.Build.0 = Release|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release|ARM.ActiveCfg = Release|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release|ARM.Build.0 = Release|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release|x64.ActiveCfg = Release|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release|x64.Build.0 = Release|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release|x86.ActiveCfg = Release|Any CPU + {882D22FB-68CB-4EA7-AE62-B79F7165854D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -273,8 +400,15 @@ Global {4805A9DA-3412-4BE8-8B95-5A0EFDA1AC01} = {C2F43DB7-AAA0-4687-9897-5D686EC91245} {1609F12C-8216-4E7C-ADE0-240BFE160242} = {C2F43DB7-AAA0-4687-9897-5D686EC91245} {D8CAACFA-FE6E-46BC-9B23-8AA3B2055125} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {ADD94C3B-7F88-4D5E-8C64-9FC84DC1DC0B} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {882D22FB-68CB-4EA7-AE62-B79F7165854D} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F69D4156-FC22-4B8E-AD72-2A7323D42CC4} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Ably.PubSub.Tests.Shared\Ably.PubSub.Tests.Shared.projitems*{1609f12c-8216-4e7c-ade0-240bfe160242}*SharedItemsImports = 13 + Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{3553e1b2-8f1f-4e83-9524-08e702c14fcf}*SharedItemsImports = 13 + Ably.PubSub.Shared.MsgPack\Ably.PubSub.Shared.MsgPack.projitems*{eebf3249-3ca3-4078-a188-f8b916c3f4c2}*SharedItemsImports = 13 + EndGlobalSection EndGlobal diff --git a/src/Ably.PubSub.Package.sln b/src/Ably.PubSub.Package.sln index 1a0336f87..a2f4a0c60 100644 --- a/src/Ably.PubSub.Package.sln +++ b/src/Ably.PubSub.Package.sln @@ -25,13 +25,13 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Ably.PubSub.Shared.MsgPack" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IO.Ably.DeltaCodec", "..\lib\delta-codec\IO.Ably.DeltaCodec\IO.Ably.DeltaCodec.csproj", "{55FD7D98-585B-4304-9B3F-3F518DB5CBFF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Device", "Ably.PubSub.Device\Ably.PubSub.Device.csproj", "{EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Server", "Ably.PubSub.Server\Ably.PubSub.Server.csproj", "{181C023D-975C-430A-BB80-A0BC884D415C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Server.NETFramework", "Ably.PubSub.Server.NETFramework\Ably.PubSub.Server.NETFramework.csproj", "{7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{2d265650-b1ec-4f8d-b043-a2e3dcc23fd8}*SharedItemsImports = 4 - Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{3180aa9d-9800-4eb2-87f0-ada47e2fff3d}*SharedItemsImports = 4 - Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{3553e1b2-8f1f-4e83-9524-08e702c14fcf}*SharedItemsImports = 13 - Ably.PubSub.Shared.MsgPack\Ably.PubSub.Shared.MsgPack.projitems*{eebf3249-3ca3-4078-a188-f8b916c3f4c2}*SharedItemsImports = 13 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM @@ -91,6 +91,54 @@ Global {55FD7D98-585B-4304-9B3F-3F518DB5CBFF}.Release|x64.Build.0 = Release|Any CPU {55FD7D98-585B-4304-9B3F-3F518DB5CBFF}.Release|x86.ActiveCfg = Release|Any CPU {55FD7D98-585B-4304-9B3F-3F518DB5CBFF}.Release|x86.Build.0 = Release|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Debug|ARM.ActiveCfg = Debug|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Debug|ARM.Build.0 = Debug|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Debug|x64.Build.0 = Debug|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Debug|x86.Build.0 = Debug|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Release|Any CPU.Build.0 = Release|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Release|ARM.ActiveCfg = Release|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Release|ARM.Build.0 = Release|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Release|x64.ActiveCfg = Release|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Release|x64.Build.0 = Release|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Release|x86.ActiveCfg = Release|Any CPU + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1}.Release|x86.Build.0 = Release|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Debug|ARM.Build.0 = Debug|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Debug|x64.ActiveCfg = Debug|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Debug|x64.Build.0 = Debug|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Debug|x86.ActiveCfg = Debug|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Debug|x86.Build.0 = Debug|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Release|Any CPU.Build.0 = Release|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Release|ARM.ActiveCfg = Release|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Release|ARM.Build.0 = Release|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Release|x64.ActiveCfg = Release|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Release|x64.Build.0 = Release|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Release|x86.ActiveCfg = Release|Any CPU + {181C023D-975C-430A-BB80-A0BC884D415C}.Release|x86.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|Any CPU.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|ARM.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|ARM.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x64.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x64.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x86.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -101,8 +149,17 @@ Global {3553E1B2-8F1F-4E83-9524-08E702C14FCF} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} {EEBF3249-3CA3-4078-A188-F8B916C3F4C2} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} {55FD7D98-585B-4304-9B3F-3F518DB5CBFF} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {EB66BFC4-CF1D-4FA0-9618-C04FF34A02C1} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {181C023D-975C-430A-BB80-A0BC884D415C} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F69D4156-FC22-4B8E-AD72-2A7323D42CC4} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{2d265650-b1ec-4f8d-b043-a2e3dcc23fd8}*SharedItemsImports = 4 + Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{3553e1b2-8f1f-4e83-9524-08e702c14fcf}*SharedItemsImports = 13 + Ably.PubSub.Shared\Ably.PubSub.Shared.projitems*{48ea337f-ae7b-4001-bd8c-da1668fc6cbd}*SharedItemsImports = 5 + Ably.PubSub.Shared.MsgPack\Ably.PubSub.Shared.MsgPack.projitems*{eebf3249-3ca3-4078-a188-f8b916c3f4c2}*SharedItemsImports = 13 + EndGlobalSection EndGlobal diff --git a/src/Ably.PubSub.Server.NETFramework/Ably.PubSub.Server.NETFramework.csproj b/src/Ably.PubSub.Server.NETFramework/Ably.PubSub.Server.NETFramework.csproj new file mode 100644 index 000000000..7c90e6716 --- /dev/null +++ b/src/Ably.PubSub.Server.NETFramework/Ably.PubSub.Server.NETFramework.csproj @@ -0,0 +1,99 @@ + + + + + + + Debug + AnyCPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31} + Library + Properties + IO.Ably.PubSub.Server + Ably.PubSub.Server + v4.6.2 + 512 + + + + + + true + full + false + bin\Debug\ + TRACE;DEBUG + prompt + 4 + ..\IO.Ably.ruleset + true + bin\Debug\Ably.PubSub.Server.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + ..\IO.Ably.ruleset + 4 + bin\Release\Ably.PubSub.Server.xml + + + bin\package\ + bin\package\Ably.PubSub.Server.xml + + + + true + ..\..\IO.Ably.snk + + + + + + + + + + + + + Properties\CommonAssemblyInfo.cs + + + Side.cs + + + PubSubServer.cs + + + + + + + + + + + + + + + {2d265650-b1ec-4f8d-b043-a2e3dcc23fd8} + Ably.PubSub.Core.NETFramework + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + diff --git a/src/Ably.PubSub.Server.NETFramework/Properties/AssemblyInfo.cs b/src/Ably.PubSub.Server.NETFramework/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b36bc208b --- /dev/null +++ b/src/Ably.PubSub.Server.NETFramework/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Ably.PubSub.Server")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyDescription("Server-side entry point for Ably Pub/Sub")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7b3c1f42-9d5e-4a87-b1c6-2e4f8a9d0b31")] + +[assembly: ComVisible(false)] diff --git a/src/Ably.PubSub.Server.NETFramework/packages.config b/src/Ably.PubSub.Server.NETFramework/packages.config new file mode 100644 index 000000000..8cf75130b --- /dev/null +++ b/src/Ably.PubSub.Server.NETFramework/packages.config @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Ably.PubSub.Server/Ably.PubSub.Server.csproj b/src/Ably.PubSub.Server/Ably.PubSub.Server.csproj new file mode 100644 index 000000000..f1669d01a --- /dev/null +++ b/src/Ably.PubSub.Server/Ably.PubSub.Server.csproj @@ -0,0 +1,60 @@ + + + + + netstandard2.0;net6.0;net7.0 + ..\IO.Ably.ruleset + Debug;Release;package + false + true + Ably.PubSub.Server + IO.Ably.PubSub.Server + + + + netstandard2.0 + + + + + true + ..\..\IO.Ably.snk + + + + bin\package + + + + true + + + + true + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/src/Ably.PubSub.Server/PubSubServer.cs b/src/Ably.PubSub.Server/PubSubServer.cs new file mode 100644 index 000000000..beed7412b --- /dev/null +++ b/src/Ably.PubSub.Server/PubSubServer.cs @@ -0,0 +1,103 @@ +using System; +using IO.Ably.PubSub.Internal; + +namespace IO.Ably.PubSub.Server +{ + /// + /// Entry point for Ably Pub/Sub applications that run on a server - an ASP.NET or Azure host, + /// a worker, a console app or any other backend the end user does not hold. + /// + /// These factory methods are the only supported way to create a client from this package. + /// The Ably.PubSub.Core package that carries the implementation is an internal dependency: a + /// client constructed directly from or is + /// not classified as server-side, which Ably's platform behaviour and billing depend on. + /// + /// + /// The returned clients are the ordinary and + /// , so the whole of the IO.Ably API remains available; + /// installing this package rather than Ably.PubSub.Device states where the code runs. What + /// the doors add is an agent entry declaring the server side, and per PDR-091 that entry is + /// what earns the monthly-active-user exemption on API-key authentication. + /// + /// + public static class PubSubServer + { + /// + /// Creates a server-side realtime client from an API key or an Ably token. + /// + /// + /// A valid Ably API key (of the form APP_ID.KEY_ID:KEY_SECRET) or an Ably token. + /// The core applies the same colon rule its own constructors use to tell them apart. + /// + /// A connected-on-demand realtime client. + public static AblyRealtime CreateRealtimeClient(string keyOrToken) + { + return CreateRealtimeClient(new ClientOptions(keyOrToken)); + } + + /// + /// Creates a server-side realtime client from a set of client options. + /// + /// The client options. + /// A connected-on-demand realtime client. + /// Thrown when is null. + public static AblyRealtime CreateRealtimeClient(ClientOptions options) + { + // The core constructor is [Obsolete] to steer consumers to the doors; this door is + // the sanctioned caller, constructing the client from the side-stamped copy. +#pragma warning disable CS0618 + return new AblyRealtime(Side.WithSideAgent(options, Side.ServerAgentIdentifier)); +#pragma warning restore CS0618 + } + + /// + /// Creates a server-side realtime client, configured by the supplied action. + /// + /// Action that populates the client options. + /// A connected-on-demand realtime client. + /// Thrown when is null. + public static AblyRealtime CreateRealtimeClient(Action configure) + { + return CreateRealtimeClient(Side.Configure(configure)); + } + + /// + /// Creates a server-side HTTP (REST) client from an API key or an Ably token. + /// + /// + /// A valid Ably API key (of the form APP_ID.KEY_ID:KEY_SECRET) or an Ably token. + /// The core applies the same colon rule its own constructors use to tell them apart. + /// + /// An HTTP client. + public static AblyRest CreateHttpClient(string keyOrToken) + { + return CreateHttpClient(new ClientOptions(keyOrToken)); + } + + /// + /// Creates a server-side HTTP (REST) client from a set of client options. + /// + /// The client options. + /// An HTTP client. + /// Thrown when is null. + public static AblyRest CreateHttpClient(ClientOptions options) + { + // The core constructor is [Obsolete] to steer consumers to the doors; this door is + // the sanctioned caller, constructing the client from the side-stamped copy. +#pragma warning disable CS0618 + return new AblyRest(Side.WithSideAgent(options, Side.ServerAgentIdentifier)); +#pragma warning restore CS0618 + } + + /// + /// Creates a server-side HTTP (REST) client, configured by the supplied action. + /// + /// Action that populates the client options. + /// An HTTP client. + /// Thrown when is null. + public static AblyRest CreateHttpClient(Action configure) + { + return CreateHttpClient(Side.Configure(configure)); + } + } +} diff --git a/src/Ably.PubSub.Shared/AblyRealtime.cs b/src/Ably.PubSub.Shared/AblyRealtime.cs index 58ff6b9db..6bb85a440 100644 --- a/src/Ably.PubSub.Shared/AblyRealtime.cs +++ b/src/Ably.PubSub.Shared/AblyRealtime.cs @@ -38,6 +38,7 @@ public class AblyRealtime : IRealtimeClient, IDisposable /// client will be rejected once MAU-based pricing is live. /// /// String key (obtained from application dashboard). + [Obsolete("Construct via PubSubDevice.CreateClient or PubSubServer.CreateRealtimeClient; a directly-constructed client is unclassified and will be rejected once MAU-based pricing is live.", error: false)] public AblyRealtime(string key) : this(new ClientOptions(key)) { @@ -55,6 +56,7 @@ public AblyRealtime(string key) /// client will be rejected once MAU-based pricing is live. /// /// . + [Obsolete("Construct via PubSubDevice.CreateClient or PubSubServer.CreateRealtimeClient; a directly-constructed client is unclassified and will be rejected once MAU-based pricing is live.", error: false)] public AblyRealtime(ClientOptions options) : this(options, CreateRestFunc, IoC.MobileDevice) { diff --git a/src/Ably.PubSub.Shared/AblyRest.cs b/src/Ably.PubSub.Shared/AblyRest.cs index e36247a63..29d977a3a 100644 --- a/src/Ably.PubSub.Shared/AblyRest.cs +++ b/src/Ably.PubSub.Shared/AblyRest.cs @@ -29,6 +29,7 @@ public sealed class AblyRest : IRestClient /// client will be rejected once MAU-based pricing is live. /// /// Full api key. + [Obsolete("Construct via PubSubServer.CreateHttpClient; a directly-constructed client is unclassified and will be rejected once MAU-based pricing is live.", error: false)] public AblyRest(string apiKey) : this(new ClientOptions(apiKey)) { @@ -52,6 +53,7 @@ public AblyRest(string apiKey) /// client will be rejected once MAU-based pricing is live. /// /// Action delegate which receives a empty options object. + [Obsolete("Construct via PubSubServer.CreateHttpClient; a directly-constructed client is unclassified and will be rejected once MAU-based pricing is live.", error: false)] public AblyRest(Action init) { Options = new ClientOptions(); @@ -70,6 +72,7 @@ public AblyRest(Action init) /// client will be rejected once MAU-based pricing is live. /// /// instance of clientOptions. + [Obsolete("Construct via PubSubServer.CreateHttpClient; a directly-constructed client is unclassified and will be rejected once MAU-based pricing is live.", error: false)] public AblyRest(ClientOptions clientOptions) : this(clientOptions, IoC.MobileDevice) { diff --git a/src/Ably.PubSub.Shared/Agent.cs b/src/Ably.PubSub.Shared/Agent.cs index 147e9c20a..166058cce 100644 --- a/src/Ably.PubSub.Shared/Agent.cs +++ b/src/Ably.PubSub.Shared/Agent.cs @@ -34,7 +34,19 @@ public static class OS } internal const string AblyAgentHeader = "Ably-Agent"; - private static readonly string AblySdkIdentifier = $"ably-dotnet/{Defaults.LibraryVersion}"; // RSC7d1 + + // RSC7d1. The family identifier for this SDK. + // + // It flips from `ably-dotnet` to `ably-pubsub-dotnet` at the Pub/Sub package split + // (PDR-091b), and the flip is the point: the identifier alone then partitions legacy + // traffic (`ably-dotnet/*`, still emitted by the 1.x maintenance branch, which keeps + // the old identifier) from new-package traffic (`ably-pubsub-dotnet/*`), without + // needing to correlate versions. Registered in ably-common + // (https://github.com/ably/ably-common/pull/361) as a versioned `sdk` entry. + // + // The per-side flags (`ably-pubsub-device` / `ably-pubsub-server`) are stamped by the + // door packages, not here — see src/Ably.PubSub.Side/Side.cs. + private static readonly string AblySdkIdentifier = $"ably-pubsub-dotnet/{Defaults.LibraryVersion}"; private static readonly Lazy _dotnetRuntimeIdentifier = new Lazy(() => GetDotnetRuntimeIdentifier()); diff --git a/src/Ably.PubSub.Shared/ClientOptions.cs b/src/Ably.PubSub.Shared/ClientOptions.cs index b93a57e4f..6cd77993c 100644 --- a/src/Ably.PubSub.Shared/ClientOptions.cs +++ b/src/Ably.PubSub.Shared/ClientOptions.cs @@ -454,5 +454,93 @@ public ClientOptions(string key) : base(key) { } + + /// + /// Creates a member-wise copy of these options. Value-typed members are copied by value; + /// the mutable dictionary members (, , + /// and ) are copied into new + /// dictionaries so the copy can be mutated without affecting the original; every other + /// reference-typed member (delegates, , + /// , , , + /// , and ) + /// is shared by reference, matching how the core's own constructors retain them. + /// + /// This is the copy the door packages (Ably.PubSub.Device / Ably.PubSub.Server) stamp their + /// side agent onto, so that stamping a client never mutates the caller's own options instance. + /// It lives here rather than in the door helper because , + /// and are write-only from outside the + /// class and can only be carried across from inside it. + /// + /// + /// A copy of these options. + public ClientOptions Clone() + { + var copy = new ClientOptions + { + // AuthOptions members. + AuthCallback = AuthCallback, + AuthUrl = AuthUrl, + AuthMethod = AuthMethod, + Key = Key, + Token = Token, + TokenDetails = TokenDetails, + AuthHeaders = AuthHeaders == null ? null : new Dictionary(AuthHeaders), + AuthParams = AuthParams == null ? null : new Dictionary(AuthParams), + QueryTime = QueryTime, + UseTokenAuth = UseTokenAuth, + + // ClientOptions members. + AutoConnect = AutoConnect, + ClientId = ClientId, + DefaultTokenParams = DefaultTokenParams, + QueueMessages = QueueMessages, + EchoMessages = EchoMessages, + Recover = Recover, + LogLevel = LogLevel, + LogHandler = LogHandler, + Port = Port, + Tls = Tls, + TlsPort = TlsPort, + UseBinaryProtocol = UseBinaryProtocol, + DisconnectedRetryTimeout = DisconnectedRetryTimeout, + SuspendedRetryTimeout = SuspendedRetryTimeout, + ChannelRetryTimeout = ChannelRetryTimeout, + HttpOpenTimeout = HttpOpenTimeout, + HttpRequestTimeout = HttpRequestTimeout, + FallbackRetryTimeout = FallbackRetryTimeout, + HttpMaxRetryCount = HttpMaxRetryCount, + HttpMaxRetryDuration = HttpMaxRetryDuration, + ChannelDefaults = ChannelDefaults, + Environment = Environment, + TransportFactory = TransportFactory, + IdempotentRestPublishing = IdempotentRestPublishing, + TransportParams = TransportParams == null ? null : new Dictionary(TransportParams), + CustomContext = CustomContext, + AutomaticNetworkStateMonitoring = AutomaticNetworkStateMonitoring, + HeartbeatMonitorDelay = HeartbeatMonitorDelay, + AddRequestIds = AddRequestIds, + PushAdminFullWait = PushAdminFullWait, + Agents = Agents == null ? null : new Dictionary(Agents), + HttpClient = HttpClient, + + // Internal state the core reads. + Logger = Logger, + SkipInternetCheck = SkipInternetCheck, + RealtimeRequestTimeout = RealtimeRequestTimeout, + }; + + // Write-only / private-field members, only reachable from inside the class. + copy._restHost = _restHost; + copy._realtimeHost = _realtimeHost; + copy._fallbackHosts = _fallbackHosts; + copy._nowFunc = _nowFunc; + +#pragma warning disable 618 + copy.FallbackHostsUseDefault = FallbackHostsUseDefault; + copy.CaptureCurrentSynchronizationContext = CaptureCurrentSynchronizationContext; +#pragma warning restore 618 + + return copy; + } } } diff --git a/src/Ably.PubSub.Side/Side.cs b/src/Ably.PubSub.Side/Side.cs new file mode 100644 index 000000000..d55de5fed --- /dev/null +++ b/src/Ably.PubSub.Side/Side.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; + +namespace IO.Ably.PubSub.Internal +{ + /// + /// Private helper shared by Ably.PubSub.Device and Ably.PubSub.Server. It is compiled into + /// each door assembly by a <Compile Include> item rather than published, so the two + /// packages can share this code without a third NuGet package existing for it to live in. + /// This is the .NET analogue of ably-js's packages/shared/side.ts and ably-java's + /// shared/.../Side.java. + /// + /// PDR-091 keeps Ably.PubSub.Core itself as the shared core, so nothing here may grow into a + /// general abstraction over the core: it exists only to stamp the side a package declares. + /// + /// + internal static class Side + { + // The `-device` / `-server` suffix on both identifiers below is load-bearing, not + // cosmetic. On API-key auth the realtime system grants the MAU server exemption by + // matching an agent entry ending in `-server`, and an identifier that is not yet in the + // ably-common registry is classified by that suffix alone. Renaming either without + // preserving its suffix silently reclassifies every client the package constructs. + // + // Both live here rather than in the package that uses each, so the naming scheme can be + // changed in one place. + + /// + /// The agent identifier declaring the device side, sent by Ably.PubSub.Device. + /// + internal const string DeviceAgentIdentifier = "ably-pubsub-device"; + + /// + /// The agent identifier declaring the server side, sent by Ably.PubSub.Server. + /// + /// This is the entry that earns the MAU exemption on API-key auth, so its -server + /// suffix is the one with billing consequences. + /// + /// + internal const string ServerAgentIdentifier = "ably-pubsub-server"; + + /// + /// Returns a copy of the caller's options carrying the agent entry that declares this + /// package's side. + /// + /// The core stores the options object it is given by reference, so stamping the + /// side onto the caller's own instance would leak the flag into any other client built + /// from the same options — both doors reused on one options object, or a plain + /// new AblyRealtime(options). To prevent that, the options are copied via + /// and the side is stamped onto the copy; the caller's + /// instance and its Agents dictionary are left untouched. The copy lives in the core + /// (not here) because RestHost/RealtimeHost/FallbackHosts are + /// write-only from outside the class and cannot be carried across by a copy located here. + /// + /// + /// The caller's Agents entries are preserved alongside the side stamp, so an SDK + /// layered on top of this package keeps its attribution. The side stamp is applied last + /// and so wins a collision on its own identifier: which side the package declares is the + /// package's to state, not the caller's to redefine. + /// + /// + /// The stamp is deliberately versionless — a bare ably-pubsub-server token + /// rather than ably-pubsub-server/2.0.0. A version on the flag would say + /// version-of-what: the flag is a cross-SDK statement about where the code runs, the + /// door package is released in lockstep with the core, whose version the family + /// identifier (ably-pubsub-dotnet/<version>) already carries, and the + /// ably-common registry models the flags like browser, with + /// versioned: false. See ably-js#2297. Agent.AddAgentIdentifier in the core + /// already emits a bare token for a null or empty version, so a null value here is all + /// that is needed. + /// + /// + /// The options the caller passed to the door. + /// The side-declaring agent identifier to stamp. + /// A copy of the options, with the side stamped into its Agents dictionary. + /// Thrown when is null. + internal static ClientOptions WithSideAgent(ClientOptions options, string identifier) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + // Copy before stamping: the core keeps the options object by reference, so mutating the + // caller's instance would let this side flag leak into another client built from the + // same options. Clone() already gives Agents its own dictionary. + var copy = options.Clone(); + + if (copy.Agents == null) + { + copy.Agents = new Dictionary(); + } + + // Applied last, so the side wins a collision on its own key. Null value => bare + // token, per the versionless-flag reasoning above. + copy.Agents[identifier] = null; + + return copy; + } + + /// + /// Builds a fresh configured by the supplied action. The core + /// clients have no such constructor overload, so the doors provide one uniformly. + /// + /// Action that populates the new options. + /// The populated options. + /// Thrown when is null. + internal static ClientOptions Configure(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var options = new ClientOptions(); + configure(options); + + return options; + } + } +} diff --git a/src/Ably.PubSub.Tests.DotNET/Ably.PubSub.Tests.DotNET.csproj b/src/Ably.PubSub.Tests.DotNET/Ably.PubSub.Tests.DotNET.csproj index 87c98ee2f..3bb109682 100644 --- a/src/Ably.PubSub.Tests.DotNET/Ably.PubSub.Tests.DotNET.csproj +++ b/src/Ably.PubSub.Tests.DotNET/Ably.PubSub.Tests.DotNET.csproj @@ -4,10 +4,12 @@ net6.0;net7.0 ..\IO.Ably.Tests.ruleset 8 + + $(NoWarn);618 - 1701;1702;SA0001;NU1701 + 1701;1702;SA0001;NU1701;618 @@ -48,6 +50,11 @@ + + + diff --git a/src/Ably.PubSub.Tests.DotNET/PubSub/PubSubDoorSandboxSpecs.cs b/src/Ably.PubSub.Tests.DotNET/PubSub/PubSubDoorSandboxSpecs.cs new file mode 100644 index 000000000..2caf992ac --- /dev/null +++ b/src/Ably.PubSub.Tests.DotNET/PubSub/PubSubDoorSandboxSpecs.cs @@ -0,0 +1,85 @@ +using System; +using System.Threading.Tasks; +using FluentAssertions; +using IO.Ably.PubSub.Device; +using IO.Ably.PubSub.Server; +using IO.Ably.Realtime; +using IO.Ably.Tests; +using IO.Ably.Tests.Infrastructure; +using Xunit; +using Xunit.Abstractions; + +namespace IO.Ably.Tests.PubSub +{ + /// + /// Dual-construction-mode conformance: a client built through each door must actually work + /// against the Ably sandbox, not just carry the right agent over the fake transport. One + /// realtime publish/subscribe round-trip per realtime door, and one REST call for the HTTP + /// door. Tagged type=integration so it runs in the existing Test.NetStandard.Integration legs. + /// + [Collection("SandBox Connection")] + [Trait("type", "integration")] + public class PubSubDoorSandboxSpecs : SandboxSpecs + { + public PubSubDoorSandboxSpecs(AblySandboxFixture fixture, ITestOutputHelper output) + : base(fixture, output) + { + } + + [Fact] + [Trait("spec", "RSC7d")] + public async Task ServerRealtimeDoor_ConnectsAndRoundTripsAMessage() + { + var client = PubSubServer.CreateRealtimeClient(await DoorOptions()); + + await RoundTripAMessage(client); + } + + [Fact] + [Trait("spec", "RSC7d")] + public async Task DeviceDoor_ConnectsAndRoundTripsAMessage() + { + var client = PubSubDevice.CreateClient(await DoorOptions()); + + await RoundTripAMessage(client); + } + + [Fact] + [Trait("spec", "RSC7d")] + public async Task ServerHttpDoor_CompletesARestCall() + { + var client = PubSubServer.CreateHttpClient(await DoorOptions()); + + var serverTime = await client.TimeAsync(); + + serverTime.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMinutes(15)); + } + + private static async Task DoorOptions() + { + var settings = await AblySandboxFixture.GetSettings(); + var options = settings.CreateDefaultOptions(); + options.TransportFactory = new TestTransportFactory(); + return options; + } + + private async Task RoundTripAMessage(AblyRealtime client) + { + using (client) + { + await client.WaitForState(ConnectionState.Connected); + + var channel = client.Channels.Get("pubsub-door-smoke".AddRandomSuffix()); + Message received = null; + channel.Subscribe(message => received = message); + await channel.AttachAsync(); + + var result = await channel.PublishAsync(new Message("greeting", "hello")); + result.IsSuccess.Should().BeTrue(); + + await new ConditionalAwaiter(() => received != null); + received.Data.Should().Be("hello"); + } + } + } +} diff --git a/src/Ably.PubSub.Tests.DotNET/PubSub/PubSubPackageSpecs.cs b/src/Ably.PubSub.Tests.DotNET/PubSub/PubSubPackageSpecs.cs new file mode 100644 index 000000000..983d649a1 --- /dev/null +++ b/src/Ably.PubSub.Tests.DotNET/PubSub/PubSubPackageSpecs.cs @@ -0,0 +1,414 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using IO.Ably.PubSub.Device; +using IO.Ably.PubSub.Server; +using IO.Ably.Tests; +using IO.Ably.Tests.Infrastructure; +using IO.Ably.Tests.Realtime; +using Xunit; +using Xunit.Abstractions; + +namespace IO.Ably.Tests.PubSub +{ + /// + /// The agent contract of the two door packages. These assertions are what Ably's billing + /// reads: the realtime system grants the MAU server exemption on API-key auth by matching an + /// agent entry ending in `-server`, so a rename, a stray version suffix or a lost stamp must + /// fail here loudly rather than surface as a billing discrepancy. + /// + /// The identifiers are written out as literals on purpose - asserting against + /// `Side.ServerAgentIdentifier` would make a rename of the constant pass silently. + /// + /// Every spec either disables AutoConnect or supplies the fake transport factory, so no unit + /// test here opens a network connection. The one gap that follows from that is noted on + /// StringOverloadSpecs below. + /// + [Trait("spec", "RSC7d")] + public class PubSubPackageSpecs : AblySpecs + { + private const string DeviceFlag = "ably-pubsub-device"; + private const string ServerFlag = "ably-pubsub-server"; + private const string FamilyPrefix = "ably-pubsub-dotnet/"; + + public PubSubPackageSpecs(ITestOutputHelper output) + : base(output) + { + } + + private static string FamilyIdentifier => FamilyPrefix + Defaults.LibraryVersion; + + [Fact] + public async Task ServerHttpClient_SendsBareServerFlagAndFamilyIdentifier() + { + var agentValues = await CaptureHttpAgentTokens( + handler => PubSubServer.CreateHttpClient(options => UseFakeHttp(options, handler))); + + AssertServerSide(agentValues); + } + + [Fact] + public async Task ServerRealtimeClient_SendsBareServerFlagAndFamilyIdentifier() + { + var agentValues = await CaptureRealtimeAgentTokens( + factory => PubSubServer.CreateRealtimeClient(o => UseFakeTransport(o, factory))); + + AssertServerSide(agentValues); + } + + [Fact] + public async Task DeviceClient_SendsBareDeviceFlagOverHttp() + { + var agentValues = await CaptureHttpAgentTokens( + handler => PubSubDevice.CreateClient(options => UseFakeHttp(options, handler)).RestClient); + + AssertDeviceSide(agentValues); + } + + [Fact] + public async Task DeviceClient_SendsBareDeviceFlagOnTheRealtimeConnection() + { + var agentValues = await CaptureRealtimeAgentTokens( + factory => PubSubDevice.CreateClient(o => UseFakeTransport(o, factory))); + + AssertDeviceSide(agentValues); + } + + [Fact] + public async Task CoreConstructedRestClient_SendsNeitherSideFlag() + { + var agentValues = await CaptureHttpAgentTokens(handler => + { + var options = new ClientOptions(); + UseFakeHttp(options, handler); + return new AblyRest(options); + }); + + AssertNoSideFlag(agentValues); + } + + [Fact] + public async Task CoreConstructedRealtimeClient_SendsNeitherSideFlagOnTheConnection() + { + var agentValues = await CaptureRealtimeAgentTokens(factory => + { + var options = new ClientOptions(); + UseFakeTransport(options, factory); + return new AblyRealtime(options); + }); + + AssertNoSideFlag(agentValues); + } + + [Fact] + public async Task CallerSuppliedAgents_ArePreservedAlongsideTheFlag() + { + var agentValues = await CaptureHttpAgentTokens(handler => + PubSubServer.CreateHttpClient(options => + { + UseFakeHttp(options, handler); + options.Agents = new Dictionary { { "chat-dotnet", "1.0.0" } }; + })); + + agentValues.Should().Contain("chat-dotnet/1.0.0"); + AssertServerSide(agentValues); + } + + [Fact] + public async Task CallerEntryUnderTheSidesOwnKey_IsOverriddenWithTheBareFlag() + { + // Which side the package declares is the package's to state, not the caller's to + // redefine, so the stamp is applied last and wins. + var agentValues = await CaptureHttpAgentTokens(handler => + PubSubServer.CreateHttpClient(options => + { + UseFakeHttp(options, handler); + options.Agents = new Dictionary { { ServerFlag, "9.9.9" } }; + })); + + AssertServerSide(agentValues); + } + + [Fact] + public void ServerHttpClient_WithNeitherKeyNorToken_FailsExactlyLikeTheCore() + { + // Deliberately no key, token, authUrl or authCallback. + Action viaDoor = () => PubSubServer.CreateHttpClient(options => { }); + Action viaCore = () => _ = new AblyRest(new ClientOptions()); + + // The door neither requires nor injects auth: with no credentials it fails + // with exactly the core's own 40106 (and had it injected any credential, + // it would not throw at all). + var doorError = viaDoor.Should().Throw().Which.ErrorInfo; + var coreError = viaCore.Should().Throw().Which.ErrorInfo; + + doorError.Code.Should().Be(40106); + coreError.Code.Should().Be(40106); + doorError.Message.Should().Be(coreError.Message); + } + + [Fact] + public void CallersOwnOptionsInstance_IsNotMutated() + { + var callerAgents = new Dictionary { { "chat-dotnet", "1.0.0" } }; + var options = new ClientOptions(ValidKey) { AutoConnect = false, Agents = callerAgents }; + + using (PubSubServer.CreateRealtimeClient(options)) + { + // The caller's dictionary instance is untouched. + callerAgents.Should().HaveCount(1); + callerAgents.Should().NotContainKey(ServerFlag); + callerAgents.Should().ContainKey("chat-dotnet"); + + // The caller's options instance is untouched: the door stamps a copy, so the + // original still points at the caller's own dictionary and carries no side flag. + options.Agents.Should().BeSameAs(callerAgents); + options.Agents.Should().NotContainKey(ServerFlag); + } + } + + [Fact] + public void ReusingOneOptionsInstanceAcrossDoors_DoesNotAccumulateOrLeakSideFlags() + { + var options = new ClientOptions(ValidKey) { AutoConnect = false, SkipInternetCheck = true }; + + using (var server = PubSubServer.CreateRealtimeClient(options)) + using (var device = PubSubDevice.CreateClient(options)) + { + // Each client consumes its own stamped copy, never the caller's instance. + server.Options.Should().NotBeSameAs(options); + device.Options.Should().NotBeSameAs(options); + + server.Options.Agents.Should().ContainKey(ServerFlag); + server.Options.Agents.Should().NotContainKey(DeviceFlag); + + device.Options.Agents.Should().ContainKey(DeviceFlag); + device.Options.Agents.Should().NotContainKey(ServerFlag); + + // The caller's own options carries neither flag - no cross-contamination. + options.Agents.Should().BeNull(); + } + } + + [Fact] + public void EveryDoor_RejectsNullOptions() + { + Assert.Throws(() => PubSubServer.CreateRealtimeClient((ClientOptions)null)); + Assert.Throws(() => PubSubServer.CreateHttpClient((ClientOptions)null)); + Assert.Throws(() => PubSubDevice.CreateClient((ClientOptions)null)); + } + + [Fact] + public void EveryDoor_RejectsANullConfigureAction() + { + Assert.Throws(() => PubSubServer.CreateRealtimeClient((Action)null)); + Assert.Throws(() => PubSubServer.CreateHttpClient((Action)null)); + Assert.Throws(() => PubSubDevice.CreateClient((Action)null)); + } + + [Fact] + public void OptionsOverloads_ReturnTheConcreteCoreTypes() + { + using (var realtime = PubSubServer.CreateRealtimeClient(NoConnectOptions())) + { + realtime.Should().BeOfType(); + } + + PubSubServer.CreateHttpClient(NoConnectOptions()).Should().BeOfType(); + + using (var device = PubSubDevice.CreateClient(NoConnectOptions())) + { + device.Should().BeOfType(); + } + } + + [Fact] + public void ActionOverloads_ReturnTheConcreteCoreTypes() + { + using (var realtime = PubSubServer.CreateRealtimeClient(o => NoConnect(o))) + { + realtime.Should().BeOfType(); + } + + PubSubServer.CreateHttpClient(o => NoConnect(o)).Should().BeOfType(); + + using (var device = PubSubDevice.CreateClient(o => NoConnect(o))) + { + device.Should().BeOfType(); + } + } + + /// + /// The string overload of every door hands the string to `new ClientOptions(keyOrToken)`, + /// which applies the core's own colon rule (AuthOptions: an Ably API key is + /// `APP_ID.KEY_ID:KEY_SECRET` and always contains a colon; a token never does), and then + /// takes the same stamping path as the options overload. + /// + /// Only the HTTP door is exercised here. `CreateRealtimeClient(string)` and + /// `CreateClient(string)` cannot set AutoConnect, so constructing one in a unit test + /// would open a real websocket - the flakiness noted on the superseded PR #1330. They + /// share this exact code path, and the realtime stamping itself is covered above through + /// the action overload with a fake transport. + /// + public class StringOverloadSpecs + { + [Fact] + public void AKeyLikeString_IsTreatedAsAnApiKeyAndStillStamped() + { + var client = PubSubServer.CreateHttpClient("appId.keyId:secret"); + + client.Options.Key.Should().Be("appId.keyId:secret"); + client.Options.Token.Should().BeNullOrEmpty(); + client.Options.Agents.Should().ContainKey(ServerFlag); + client.Options.Agents[ServerFlag].Should().BeNullOrEmpty(); + } + + [Fact] + public void ATokenLikeString_IsTreatedAsATokenAndStillStamped() + { + var client = PubSubServer.CreateHttpClient("a_token_with_no_colon"); + + client.Options.Token.Should().Be("a_token_with_no_colon"); + client.Options.Key.Should().BeNullOrEmpty(); + client.Options.Agents.Should().ContainKey(ServerFlag); + client.Options.Agents[ServerFlag].Should().BeNullOrEmpty(); + } + } + + private static ClientOptions NoConnectOptions() + { + var options = new ClientOptions(ValidKey); + NoConnect(options); + return options; + } + + private static void NoConnect(ClientOptions options) + { + options.Key = ValidKey; + options.AutoConnect = false; + options.SkipInternetCheck = true; + } + + private static void UseFakeHttp(ClientOptions options, FakeHttpMessageHandler handler) + { + NoConnect(options); + options.UseBinaryProtocol = false; + options.HttpClient = new HttpClient(handler); + } + + private static void UseFakeTransport(ClientOptions options, FakeTransportFactory factory) + { + options.Key = ValidKey; + options.SkipInternetCheck = true; + options.TransportFactory = factory; + } + + /// + /// Drives a real request through the door-created REST client and returns the + /// `Ably-Agent` header the core actually put on the wire, split into tokens. + /// + private static async Task CaptureHttpAgentTokens(Func createClient) + { + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("[1500000000000]"), + }; + response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var handler = new FakeHttpMessageHandler(response); + var client = createClient(handler); + + await client.TimeAsync(); + + handler.LastRequest.Should().NotBeNull(); + var values = handler.LastRequest.Headers.GetValues(Agent.AblyAgentHeader).ToArray(); + values.Should().HaveCount(1); + + return values[0].Split(' '); + } + + /// + /// Constructs a door-created realtime client over the fake transport and returns the + /// agent tokens from the connection query parameters. + /// + /// Note the query key: `TransportParams.GetParams()` sends the agent under + /// `Ably-Agent`, while spec RTN2g names the parameter `agent`. Whether that is a latent + /// bug is a separate question (plan step 14); this spec asserts on whatever key the core + /// currently uses so it does not pre-empt the answer. + /// + private static async Task CaptureRealtimeAgentTokens(Func createClient) + { + var factory = new FakeTransportFactory(); + using (createClient(factory)) + { + // The connection is established off the calling thread, so wait for the factory + // to be asked for a transport rather than assuming it already has been. + using (var awaiter = new ConditionalAwaiter( + () => factory.LastCreatedTransport != null, + () => "the fake transport factory was never asked for a transport")) + { + await awaiter; + } + + var transport = factory.LastCreatedTransport; + transport.Should().NotBeNull("the fake transport factory should have been used instead of a real websocket"); + + var parameters = transport.Parameters.GetParams(); + parameters.Should().ContainKey(Agent.AblyAgentHeader); + + return parameters[Agent.AblyAgentHeader].Split(' '); + } + } + + private static void AssertServicedByThisSdk(string[] agentValues) + { + agentValues.Should().Contain( + FamilyIdentifier, + $"the family identifier must be the versioned '{FamilyPrefix}' token registered in ably-common"); + } + + private static void AssertServerSide(string[] agentValues) + { + AssertServicedByThisSdk(agentValues); + + agentValues.Should().Contain( + ServerFlag, + "the server flag must be a bare token - the '-server' suffix is what earns the MAU exemption on API-key auth"); + agentValues.Should().NotContain( + t => t.StartsWith(ServerFlag + "/", StringComparison.Ordinal), + "the side flag is deliberately versionless (ably-common models it with versioned: false)"); + agentValues.Should().NotContain( + t => t.StartsWith(DeviceFlag, StringComparison.Ordinal), + "a server client must never declare the device side"); + } + + private static void AssertDeviceSide(string[] agentValues) + { + AssertServicedByThisSdk(agentValues); + + agentValues.Should().Contain(DeviceFlag, "the device flag must be a bare token"); + agentValues.Should().NotContain( + t => t.StartsWith(DeviceFlag + "/", StringComparison.Ordinal), + "the side flag is deliberately versionless (ably-common models it with versioned: false)"); + agentValues.Should().NotContain( + t => t.StartsWith(ServerFlag, StringComparison.Ordinal), + "a device client must never declare the server side, which would earn it the MAU exemption"); + } + + private static void AssertNoSideFlag(string[] agentValues) + { + AssertServicedByThisSdk(agentValues); + + agentValues.Should().NotContain( + t => t.StartsWith(ServerFlag, StringComparison.Ordinal), + "a client built directly from the core is unclassified and must never declare the server side"); + agentValues.Should().NotContain( + t => t.StartsWith(DeviceFlag, StringComparison.Ordinal), + "a client built directly from the core is unclassified and must never declare the device side"); + } + } +} diff --git a/src/Ably.PubSub.Tests.NETFramework/Ably.PubSub.Tests.NETFramework.csproj b/src/Ably.PubSub.Tests.NETFramework/Ably.PubSub.Tests.NETFramework.csproj index 2c0588464..d27cf6312 100644 --- a/src/Ably.PubSub.Tests.NETFramework/Ably.PubSub.Tests.NETFramework.csproj +++ b/src/Ably.PubSub.Tests.NETFramework/Ably.PubSub.Tests.NETFramework.csproj @@ -13,6 +13,8 @@ 512 8 + + $(NoWarn);618 true diff --git a/src/Ably.PubSub.Tests.Shared/Realtime/ConnectionSpecs/ConnectionParameterSpecs.cs b/src/Ably.PubSub.Tests.Shared/Realtime/ConnectionSpecs/ConnectionParameterSpecs.cs index 740708b2a..54d265f88 100644 --- a/src/Ably.PubSub.Tests.Shared/Realtime/ConnectionSpecs/ConnectionParameterSpecs.cs +++ b/src/Ably.PubSub.Tests.Shared/Realtime/ConnectionSpecs/ConnectionParameterSpecs.cs @@ -129,7 +129,7 @@ public async Task ShouldSetTransportAblyAgentHeader() var agentValues = LastCreatedTransport.Parameters.GetParams()[Agent.AblyAgentHeader].Split(' '); var keys = new List() { - "ably-dotnet/", + "ably-pubsub-dotnet/", Agent.DotnetRuntimeIdentifier, Agent.OsIdentifier }; @@ -160,7 +160,7 @@ public async Task ShouldSetTransportCustomAblyAgentHeader() var agentValues = LastCreatedTransport.Parameters.GetParams()[Agent.AblyAgentHeader].Split(' '); var keys = new List() { - "ably-dotnet/", + "ably-pubsub-dotnet/", Agent.DotnetRuntimeIdentifier, Agent.OsIdentifier, "agent1", diff --git a/src/Ably.PubSub.Tests.Shared/Rest/AblyHttpClientSpecs.cs b/src/Ably.PubSub.Tests.Shared/Rest/AblyHttpClientSpecs.cs index a611be51a..45bb32e32 100644 --- a/src/Ably.PubSub.Tests.Shared/Rest/AblyHttpClientSpecs.cs +++ b/src/Ably.PubSub.Tests.Shared/Rest/AblyHttpClientSpecs.cs @@ -98,7 +98,7 @@ public async Task WhenCallingUrl_AddsDefaultAblyAgentHeader() var keys = new List() { - "ably-dotnet/", + "ably-pubsub-dotnet/", Agent.DotnetRuntimeIdentifier, Agent.OsIdentifier }; @@ -140,7 +140,7 @@ public async Task WhenCallingUrl_AddsCustomizedAblyAgentHeader() var keys = new List() { - "ably-dotnet/", + "ably-pubsub-dotnet/", Agent.DotnetRuntimeIdentifier, Agent.OsIdentifier, "agent1", diff --git a/src/Ably.PubSub.sln b/src/Ably.PubSub.sln index bd85e51bc..c27a93ad4 100644 --- a/src/Ably.PubSub.sln +++ b/src/Ably.PubSub.sln @@ -33,6 +33,12 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Ably.PubSub.Tests.Shared", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IO.Ably.DeltaCodec", "..\lib\delta-codec\IO.Ably.DeltaCodec\IO.Ably.DeltaCodec.csproj", "{412DEB5F-7936-4027-8BF8-977F23D465ED}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Device", "Ably.PubSub.Device\Ably.PubSub.Device.csproj", "{91E8141C-38F6-4CB2-A241-BDC34C470F73}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Server", "Ably.PubSub.Server\Ably.PubSub.Server.csproj", "{A92FE34C-1D98-4254-BE8C-BC403B053DB9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ably.PubSub.Server.NETFramework", "Ably.PubSub.Server.NETFramework\Ably.PubSub.Server.NETFramework.csproj", "{7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -125,6 +131,54 @@ Global {412DEB5F-7936-4027-8BF8-977F23D465ED}.Release|x64.Build.0 = Release|Any CPU {412DEB5F-7936-4027-8BF8-977F23D465ED}.Release|x86.ActiveCfg = Release|Any CPU {412DEB5F-7936-4027-8BF8-977F23D465ED}.Release|x86.Build.0 = Release|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Debug|ARM.ActiveCfg = Debug|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Debug|ARM.Build.0 = Debug|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Debug|x64.ActiveCfg = Debug|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Debug|x64.Build.0 = Debug|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Debug|x86.ActiveCfg = Debug|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Debug|x86.Build.0 = Debug|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Release|Any CPU.Build.0 = Release|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Release|ARM.ActiveCfg = Release|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Release|ARM.Build.0 = Release|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Release|x64.ActiveCfg = Release|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Release|x64.Build.0 = Release|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Release|x86.ActiveCfg = Release|Any CPU + {91E8141C-38F6-4CB2-A241-BDC34C470F73}.Release|x86.Build.0 = Release|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Debug|ARM.ActiveCfg = Debug|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Debug|ARM.Build.0 = Debug|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Debug|x64.ActiveCfg = Debug|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Debug|x64.Build.0 = Debug|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Debug|x86.ActiveCfg = Debug|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Debug|x86.Build.0 = Debug|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Release|Any CPU.Build.0 = Release|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Release|ARM.ActiveCfg = Release|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Release|ARM.Build.0 = Release|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Release|x64.ActiveCfg = Release|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Release|x64.Build.0 = Release|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Release|x86.ActiveCfg = Release|Any CPU + {A92FE34C-1D98-4254-BE8C-BC403B053DB9}.Release|x86.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|ARM.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|ARM.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x64.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x64.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x86.ActiveCfg = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Debug|x86.Build.0 = Debug|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|Any CPU.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|ARM.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|ARM.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x64.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x64.Build.0 = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x86.ActiveCfg = Release|Any CPU + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -138,6 +192,9 @@ Global {4805A9DA-3412-4BE8-8B95-5A0EFDA1AC01} = {C2F43DB7-AAA0-4687-9897-5D686EC91245} {1609F12C-8216-4E7C-ADE0-240BFE160242} = {C2F43DB7-AAA0-4687-9897-5D686EC91245} {412DEB5F-7936-4027-8BF8-977F23D465ED} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {91E8141C-38F6-4CB2-A241-BDC34C470F73} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {A92FE34C-1D98-4254-BE8C-BC403B053DB9} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} + {7B3C1F42-9D5E-4A87-B1C6-2E4F8A9D0B31} = {8A0F718E-2168-4CC7-8F7B-A65508B52B0F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F69D4156-FC22-4B8E-AD72-2A7323D42CC4} diff --git a/src/IO.Ably.ruleset b/src/IO.Ably.ruleset index aecc9528f..6b64cdad3 100644 --- a/src/IO.Ably.ruleset +++ b/src/IO.Ably.ruleset @@ -212,7 +212,6 @@ - diff --git a/unity/Assets/Ably/Examples/Dashboard/AblyMain.cs b/unity/Assets/Ably/Examples/Dashboard/AblyMain.cs index be9536ec7..3b0f6e929 100644 --- a/unity/Assets/Ably/Examples/Dashboard/AblyMain.cs +++ b/unity/Assets/Ably/Examples/Dashboard/AblyMain.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using IO.Ably; +using IO.Ably.PubSub.Device; using IO.Ably.Realtime; using UnityEngine; using UnityEngine.UI; @@ -54,7 +55,7 @@ private void InitializeAbly() CustomContext = SynchronizationContext.Current }; - _ably = new AblyRealtime(_clientOptions); + _ably = PubSubDevice.CreateClient(_clientOptions); _ably.Connection.On(args => { LogAndDisplay($"Connection State is {args.Current}"); diff --git a/unity/Assets/Ably/Plugins/Ably.PubSub.Device.dll b/unity/Assets/Ably/Plugins/Ably.PubSub.Device.dll new file mode 100644 index 000000000..2df3a14ef Binary files /dev/null and b/unity/Assets/Ably/Plugins/Ably.PubSub.Device.dll differ diff --git a/unity/Assets/Ably/Plugins/Ably.PubSub.Core.dll.meta b/unity/Assets/Ably/Plugins/Ably.PubSub.Device.dll.meta similarity index 100% rename from unity/Assets/Ably/Plugins/Ably.PubSub.Core.dll.meta rename to unity/Assets/Ably/Plugins/Ably.PubSub.Device.dll.meta diff --git a/unity/Assets/Ably/Plugins/link.xml b/unity/Assets/Ably/Plugins/link.xml index 39b1151c8..ab718682e 100644 --- a/unity/Assets/Ably/Plugins/link.xml +++ b/unity/Assets/Ably/Plugins/link.xml @@ -1,5 +1,5 @@ - + diff --git a/unity/Assets/Tests/AblySandbox/AblySandbox.asmdef b/unity/Assets/Tests/AblySandbox/AblySandbox.asmdef index 426714cbd..7aa3db832 100644 --- a/unity/Assets/Tests/AblySandbox/AblySandbox.asmdef +++ b/unity/Assets/Tests/AblySandbox/AblySandbox.asmdef @@ -8,7 +8,7 @@ "overrideReferences": true, "precompiledReferences": [ "nunit.framework.dll", - "Ably.PubSub.Core.dll" + "Ably.PubSub.Device.dll" ], "autoReferenced": false, "defineConstraints": [ diff --git a/unity/Assets/Tests/EditMode/AblyRealtimeSpecs.cs b/unity/Assets/Tests/EditMode/AblyRealtimeSpecs.cs index b3aa4d3b5..45c92dda1 100644 --- a/unity/Assets/Tests/EditMode/AblyRealtimeSpecs.cs +++ b/unity/Assets/Tests/EditMode/AblyRealtimeSpecs.cs @@ -227,7 +227,7 @@ public IEnumerator TestHttpUnityAgentHeader([ValueSource(nameof(_protocols))] Pr var keys = new List() { - "ably-dotnet/", + "ably-pubsub-dotnet/", Agent.DotnetRuntimeIdentifier, UnityHelper.UnityIdentifier, Agent.OsIdentifier diff --git a/unity/Assets/Tests/EditMode/EditMode.asmdef b/unity/Assets/Tests/EditMode/EditMode.asmdef index a9a72e2f2..60c0711e7 100644 --- a/unity/Assets/Tests/EditMode/EditMode.asmdef +++ b/unity/Assets/Tests/EditMode/EditMode.asmdef @@ -15,7 +15,7 @@ "overrideReferences": false, "precompiledReferences": [ "nunit.framework.dll", - "Ably.PubSub.Core.dll" + "Ably.PubSub.Device.dll" ], "autoReferenced": false, "defineConstraints": [ diff --git a/unity/Assets/Tests/PlayMode/AblyRealtimeSpecs.cs b/unity/Assets/Tests/PlayMode/AblyRealtimeSpecs.cs index b465901a3..87d5fc81a 100644 --- a/unity/Assets/Tests/PlayMode/AblyRealtimeSpecs.cs +++ b/unity/Assets/Tests/PlayMode/AblyRealtimeSpecs.cs @@ -227,7 +227,7 @@ public IEnumerator TestHttpUnityAgentHeader([ValueSource(nameof(_protocols))] Pr var keys = new List() { - "ably-dotnet/", + "ably-pubsub-dotnet/", Agent.DotnetRuntimeIdentifier, UnityHelper.UnityIdentifier, Agent.OsIdentifier diff --git a/unity/Assets/Tests/PlayMode/PlayMode.asmdef b/unity/Assets/Tests/PlayMode/PlayMode.asmdef index 89dbf3eb2..810c7cf2e 100644 --- a/unity/Assets/Tests/PlayMode/PlayMode.asmdef +++ b/unity/Assets/Tests/PlayMode/PlayMode.asmdef @@ -14,7 +14,7 @@ "overrideReferences": true, "precompiledReferences": [ "nunit.framework.dll", - "Ably.PubSub.Core.dll" + "Ably.PubSub.Device.dll" ], "autoReferenced": false, "defineConstraints": [ diff --git a/unity/CONTRIBUTING.md b/unity/CONTRIBUTING.md index e3bd7709c..8d5b44064 100644 --- a/unity/CONTRIBUTING.md +++ b/unity/CONTRIBUTING.md @@ -151,4 +151,4 @@ Unity.exe -batchmode -nographics -quit -projectPath 'C:\Users\${UserName}\UnityP ## Updating the Ably Unity Package - Currently, the latest Ably source code is imported as DLLs in Unity using [Unity Plug-ins](https://docs.unity3d.com/Manual/Plugins.html). - This is done as part of the [release process](../CONTRIBUTING.md/#release-process) by running `./unity-plugins-updater.sh 1.2.3` (linux/mac) / `.\unity-plugins-updater.cmd 1.2.3` (windows) at the root. -- This script is responsible for merging all dependencies into `Ably.PubSub.Core.dll` and copying it to `unity/Assets/Ably/Plugins/`. +- This script is responsible for merging all dependencies into `Ably.PubSub.Device.dll` (the core plus the device door and dependencies, merged) and copying it to `unity/Assets/Ably/Plugins/`. diff --git a/unity/README.md b/unity/README.md index d8034b733..366bbffe9 100644 --- a/unity/README.md +++ b/unity/README.md @@ -18,6 +18,7 @@ using System; using System.Threading; using IO.Ably; +using IO.Ably.PubSub.Device; using IO.Ably.Realtime; using UnityEngine; using UnityEngine.UI; @@ -48,7 +49,7 @@ namespace Example.ChatApp CustomContext = SynchronizationContext.Current }; - _ably = new AblyRealtime(_clientOptions); + _ably = PubSubDevice.CreateClient(_clientOptions); _ably.Connection.On(args => { Debug.Log($"Connection State is {args.Current}");