# Pubb: AI integration guide for .NET

Use this guide to add realtime messaging to an existing ASP.NET Core, Razor,
Blazor or .NET application. Inspect the project and use its existing dependency
injection, authentication, authorization and configuration conventions.

The shared protocol reference and browser examples are at:
https://pubb.io/AGENTS.md

## Start here

1. Create an app at https://app.pubb.io/dashboard/apps/new.
2. Use environment-variable placeholders while implementing. The developer adds
   credentials locally or through deployment secrets, not into prompts or source.
3. Publish from trusted .NET server code. Subscribe from the existing frontend
   with native WebSocket or its existing client SDK.
4. Explain how to run and verify the feature in the application’s live console.

The Pubb console uses Google sign-in. Integrating Pubb does not require
you to change your own application’s user authentication.

## Connection settings

| Setting | Value |
| --- | --- |
| API base | https://api.pubb.io |
| WebSocket URL | wss://ws.pubb.io/socket?appKey=YOUR_APP_KEY |
| App ID | Identifies the app in the events API URL |
| Public key | Safe for frontend connection configuration |
| App secret | Server-only Bearer token and signing secret |

Example server environment variables:

```env
Pubb__AppId=YOUR_APP_ID
Pubb__Key=YOUR_APP_KEY
Pubb__Secret=YOUR_APP_SECRET
```

Read these using `configuration["Pubb:AppId"]` and the corresponding keys.
Keep the secret out of appsettings files committed to source control, Razor
markup, Blazor WebAssembly configuration and browser requests.

## Publishing with HttpClient

No SDK is required. Register a named or typed client using the project’s existing
`IHttpClientFactory` pattern. Adapt this registration to your configuration code:

```csharp
using System.Net.Http.Headers;

builder.Services.AddHttpClient("Pubb", client =>
{
    client.BaseAddress = new Uri("https://api.pubb.io/");
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
        "Bearer",
        builder.Configuration["Pubb:Secret"]
            ?? throw new InvalidOperationException("Pubb secret is missing"));
});
```

From a server service or controller with injected `IHttpClientFactory` and
`IConfiguration`, publish after authenticating and authorizing the domain action:

```csharp
using System.Net;
using System.Net.Http.Json;

var client = httpClientFactory.CreateClient("Pubb");
var appId = configuration["Pubb:AppId"]
    ?? throw new InvalidOperationException("Pubb app ID is missing");

using var response = await client.PostAsJsonAsync(
    $"api/apps/{Uri.EscapeDataString(appId)}/events",
    new
    {
        channel = "notifications",
        name = "order.created",
        data = new { orderId = "1042", message = "On its way!" }
    },
    cancellationToken);

response.EnsureSuccessStatusCode();
if (response.StatusCode == HttpStatusCode.MultiStatus)
{
    // Inspect the response's "failed" channel list and handle partial delivery.
    var partialResult = await response.Content.ReadAsStringAsync(cancellationToken);
    logger.LogWarning("Some event deliveries failed: {Result}", partialResult);
}
```

`httpClientFactory`, `configuration`, `logger` and `cancellationToken` come from
your application’s service/controller. Do not create an unauthenticated broadcast
endpoint that lets callers choose arbitrary channels.

## Frontend subscription

Use the JavaScript native WebSocket example from the shared guide in your existing
frontend. Connect to `wss://ws.pubb.io/socket?appKey=YOUR_APP_KEY`, send
`{ "action": "subscribe", "channel": "notifications" }`, and wait for
`pusher_internal:subscription_succeeded` before expecting delivery.

Event envelopes contain a channel and a JSON-string `data` field. Parse that field
to read the event’s `name` and nested `data`. Private-channel authentication belongs
in your .NET backend, not in browser code or Blazor WebAssembly.

For a .NET native client, use `ClientWebSocket`, handle fragmented messages until
`EndOfMessage`, and implement cancellation, error handling, cleanup and reconnect
backoff. Do not assume one receive call contains a complete JSON message.

## Private and presence channels

- Public: `notifications`, subscribable with the public app key.
- Private: `private-user-42`, requires a subscription signature.
- Presence: `presence-team-42`, requires a signature and current user identity.
- The `debug-` prefix is reserved.

The client’s initial control message uses `pubb:connection_established`. Its
JSON-string `data` contains `socket_id`. Custom clients must use the same
`pubb:*` event names as the deployed socket server.

Your authenticated backend receives `socket_id` and `channel_name`, identifies the
current user from their session, and checks access to the exact channel. Only then
call Pubb from the server:

```http
POST https://api.pubb.io/api/apps/YOUR_APP_ID/auth
Authorization: Bearer YOUR_APP_SECRET
Content-Type: application/json

{
  "socket_id": "CLIENT_SOCKET_ID",
  "channel_name": "private-user-42"
}
```

Return `auth` to the client. It sends a subscribe command with `action`, `channel`
and `auth`. For presence, include a server-derived `user_id` and optional `user_info`
in the authorization request; pass the returned `channel_data` unchanged alongside
`auth` in the subscribe command. Use explicit snake_case JSON field names in your
.NET DTOs, for example with `[JsonPropertyName("socket_id")]`.

You can also sign locally using HMAC-SHA256 with the app secret:

- Private input: `socket_id:channel_name`
- Presence input: `socket_id:channel_name:channel_data`
- Auth output: `APP_KEY:lowercase_hex_signature`

Authorization must happen before either signing locally or requesting a signature.

## .NET SDK projects

This repository builds the `Pubb.Server` package and namespace. Use a project
reference during development; NuGet installation requires publishing the renamed
package first. The SDK defaults to `https://api.pubb.io`:

```csharp
using RealtimeServer = Pubb.Server.PubbServer;
using RealtimeOptions = Pubb.Server.PubbServerOptions;

builder.Services.AddSingleton(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    return new RealtimeServer(new RealtimeOptions
    {
        AppId = config["Pubb:AppId"]
            ?? throw new InvalidOperationException("Pubb app ID is missing"),
        Key = config["Pubb:Key"]
            ?? throw new InvalidOperationException("Pubb app key is missing"),
        Secret = config["Pubb:Secret"]
            ?? throw new InvalidOperationException("Pubb secret is missing"),
        ApiHost = "https://api.pubb.io"
    });
});
```

Use `TriggerAsync(channel, eventName, data)` from an authorized server action.
After checking the current user’s access, the SDK can create signatures with
`AuthenticatePrivateChannel(socketId, channelName)` or
`AuthenticatePresenceChannel(socketId, channelName, userId, userInfo)`.

The deployed API does not implement `/api/apps/:id/channels`, so do not use the
SDK channel-info methods against that route. The publish endpoint currently does
not implement sender exclusion through `SocketId`. Native HTTP callers can inspect
207 responses for partial delivery. Never expose the secret to work around an
integration error.

## Validation

- Confirm a real frontend subscription and delivery from .NET server publishing.
- Verify denied access for an unauthenticated or unauthorized user.
- Handle reconnects and dispose clients/components correctly.
- Keep event data serializable and match the app, channel and event names exactly.
- Events are delivered to connected subscribers; resync durable state from your
  own backend after reconnecting. Historical analytics may not be enabled.
- Test using an actual app’s live console. The homepage playground is a simulation.
