CasCap.Signalizr.Client 1.3.0

Prefix Reserved
dotnet add package CasCap.Signalizr.Client --version 1.3.0
                    
NuGet\Install-Package CasCap.Signalizr.Client -Version 1.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CasCap.Signalizr.Client" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CasCap.Signalizr.Client" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="CasCap.Signalizr.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CasCap.Signalizr.Client --version 1.3.0
                    
#r "nuget: CasCap.Signalizr.Client, 1.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CasCap.Signalizr.Client@1.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CasCap.Signalizr.Client&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=CasCap.Signalizr.Client&version=1.3.0
                    
Install as a Cake Tool

CasCap.Signalizr.Client

Client for the signalizr gateway: send, react, show typing and run polls on a named group over REST, and subscribe to inbound messages over gRPC from .NET 10/11.

Installation

dotnet add package CasCap.Signalizr.Client

Use a package version compatible with your gateway; the client and gateway share the repository version.

Configuration Examples

Minimal local gateway configuration; the addresses default to these loopback endpoints, but the subscriber name must be set explicitly:

{
  "CasCap": {
    "SignalizrClientConfig": {
      "BaseAddress": "http://localhost:8080",
      "GrpcAddress": "http://localhost:5001",
      "SubscriberName": "example-consumer"
    }
  }
}

For a gateway reachable at a service address, set both endpoints explicitly:

{
  "CasCap": {
    "SignalizrClientConfig": {
      "BaseAddress": "http://gateway.example.com",
      "GrpcAddress": "http://gateway.example.com:5001",
      "SubscriberName": "example-consumer"
    }
  }
}

Register with the application's existing configuration and resolve ISignalizrClient where needed:

using CasCap.Signalizr.Client;
using Microsoft.Extensions.DependencyInjection;

builder.Services.AddSignalizrClient(builder.Configuration);

Two addresses because the gateway serves REST and gRPC on separate ports: a plaintext endpoint cannot negotiate protocols without TLS, so one port answers HTTP/1.1 and another HTTP/2.

SubscriberName is the durable cursor key, not a process-instance identifier. Configure one name per logical consumer and keep it stable across restarts. Do not append Environment.MachineName, a pod name, process ID, or generated GUID: each new value creates a new cursor that begins at the current tail, losing replay from the previous identity. Only one live stream may own a durable name; use a stable replica identity only when each replica intentionally needs its own copy and cursor.

Send

var timestamp = await client.SendAsync("My Test Group Name", "deployment finished", cancellationToken);

Binary attachments use signal-cli-compatible data URIs and are MIME-agnostic:

string[] attachments =
[
  $"data:audio/ogg;filename=reply.ogg;base64,{Convert.ToBase64String(audioBytes)}"
];
var timestamp = await client.SendAsync(
  "My Test Group Name",
  "voice reply",
  attachments,
  cancellationToken);

Groups are addressed by exact Signal group display name, preserving case and spaces. The client URL-encodes names in the groupName query parameter. The gateway owns the Signal account, so a caller never supplies a group id or sender number. A 404 on sending means the group is not configured or the Gateway role is absent; GetGroupsAsync lists configured names and throws SignalizrRoleNotEnabledException when the send surface is absent. An uncertain send must not be retried automatically: it may already have reached Signal.

Interact

Reactions, typing indicators and polls use the same group names:

// React to a delivered message; the gateway resolves its sender and timestamp.
await client.SetReactionAsync(message, "👀", cancellationToken);

// Show typing while work runs. The gateway keeps it alive and clears it if the stop never comes.
await client.StartTypingAsync("My Test Group Name", cancellationToken);
await client.StopTypingAsync("My Test Group Name", cancellationToken);

// React to a message this gateway sent by omitting the author.
var sent = await client.SendAsync("My Test Group Name", "done", cancellationToken);
await client.SetReactionAsync("My Test Group Name", "✅",
  long.Parse(sent, System.Globalization.CultureInfo.InvariantCulture),
  cancellationToken: cancellationToken);

var pollId = await client.CreatePollAsync("My Test Group Name", "Deploy now?", ["Yes", "No"],
  cancellationToken: cancellationToken);
await client.ClosePollAsync("My Test Group Name", pollId, cancellationToken);

A vote arrives on the subscription as a SignalizrMessage whose PollVote names the poll and the selected answer indexes.

Reactions and typing indicators are feedback about work, not the work itself. The Try* extension methods report an unreachable gateway or a client timeout instead of throwing, so a missed indicator cannot stop a consumer. Cancellation you requested still propagates:

await client.TrySetReactionAsync("My Test Group Name", "⏳", message.Timestamp, message.Sender,
  onFailure: ex => logger.LogWarning(ex, "reaction not shown"), cancellationToken: cancellationToken);
await client.TryStartTypingAsync("My Test Group Name", cancellationToken: cancellationToken);
await client.TryStopTypingAsync("My Test Group Name", cancellationToken: cancellationToken);

At startup, WaitForGroupsAsync retries an unreachable gateway until it answers. It then throws SignalizrGroupNotConfiguredException if a required group is missing, and returns the optional groups it does not serve:

var missingOptional = await client.WaitForGroupsAsync(
  requiredGroups: ["My Test Group Name"],
  optionalGroups: ["My Test Monitor Group Name"],
  retryDelay: TimeSpan.FromSeconds(2),
  cancellationToken: cancellationToken);

The exception message omits the group names, which can carry private information.

SignalizrMessage.FromSelf marks messages the gateway's own account sent, so a consumer can ignore its own traffic without knowing the account. On an account linked to a person's phone the owner's messages are marked too; a consumer serving that owner must not drop them on this flag alone.

The account profile is shared by every group, so the client cannot change it. The gateway applies its configured profile name instead.

Subscribe

await foreach (var message in client.SubscribeAsync(cancellationToken))
{
    foreach (var attachment in message.Attachments)
    {
        var content = await client.GetAttachmentAsync(attachment.Id, cancellationToken);
        await ProcessAsync(attachment.ContentType, content, cancellationToken);
    }

    await HandleAsync(message, cancellationToken);
}

ProcessAsync and HandleAsync represent your application's processing; finish it before advancing the iterator.

Each message is acknowledged when the consumer asks for the next one, so an acknowledgement means "the previous message was processed" rather than "it arrived". A consumer that stops enumerating leaves the last message unacknowledged, which is correct: it may not have been processed. Closing the request stream ends the subscription and releases its live identity; a request-stream failure also terminates the response loop. Unacknowledged deliveries remain eligible for replay.

Subscriptions do not select or authorize a group on the server. Inspect GroupName before handling a delivery; it can be null for direct messages or messages outside the configured groups.

The stream carries attachment descriptors rather than binary content. GetAttachmentAsync downloads the raw bytes over REST. Content remains available until gateway retention removes the parent message, preserving replay and independent-subscriber semantics.

The gateway pauses delivery once too many messages are unacknowledged, and disconnects a subscriber that stops acknowledging altogether. Neither silently drops anything.

The stream is not resubscribed automatically. A caller that wants to survive a gateway restart must loop, which keeps the reconnect visible rather than hiding the gap in delivery it creates:

while (!cancellationToken.IsCancellationRequested)
{
    try
    {
        await foreach (var message in client.SubscribeAsync(cancellationToken))
            await HandleAsync(message, cancellationToken);
    }
    catch (Grpc.Core.RpcException ex) when (!cancellationToken.IsCancellationRequested)
    {
        logger.LogWarning(ex, "subscription ended, reconnecting");
        await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
    }
}

The gRPC transport limits connection establishment to 10 seconds. While a subscription is active, it sends an HTTP/2 keepalive ping after 60 seconds without incoming frames and closes the connection if the peer does not answer within another 30 seconds. This surfaces a half-open connection to the caller's reconnect loop without imposing a maximum subscription lifetime. The gateway or any HTTP/2 intermediary must support keepalive pings. These limits do not change REST send behavior or automatically replay sends.

Privacy

SignalizrMessage.Sender is a Signal identifier and Message is someone's content. Neither belongs in a log, a metric label or a trace attribute. REST and gRPC are not authenticated by this client or by the gateway. A subscription receives all configured groups; filtering locally is not an authorization boundary. Use a trusted network or secure transport boundary. MCP Basic credentials are unrelated to these client endpoints.

Testing

The companion CasCap.Signalizr.Client.Testing guide package provides FakeSignalizrClient. It is an in-memory implementation that feeds deliveries to a consumer and records every group operation, for test projects that need no gateway.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on CasCap.Signalizr.Client:

Package Downloads
CasCap.Comms

Signalizr communications pipeline: comms stream delivery, inbound messages, voice and the reply queue.

CasCap.Signalizr.Client.Testing

In-memory ISignalizrClient for testing signalizr consumers without a gateway: feeds inbound deliveries and records every group operation.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.0 76 11/14/2026
1.2.0 246 11/5/2026
1.1.0 162 11/1/2026
1.0.2 205 10/24/2026
1.0.1 165 10/21/2026
1.0.0 125 10/21/2026
0.1.2 172 10/4/2026
0.1.1 140 9/29/2026
0.1.0 155 9/27/2026
0.0.17 126 9/26/2026
0.0.16 116 9/26/2026
0.0.15 136 9/24/2026