# Persocket: AI integration guide for JavaScript and TypeScript

Use this reference to add Persocket realtime features to an existing project.
Read the project’s own instructions, inspect its framework and authentication,
and follow its existing conventions. Adapt the examples to the requested feature.

## Start here

1. Create an application at https://ws-app.persova.co/dashboard/apps/new.
2. Use placeholders while coding. The developer adds their app ID, public key and
   secret to the appropriate environment variables locally or at deployment.
3. Connect a client, subscribe to a channel, and publish an event from the server.
4. Explain how to run the integration and verify it using the application’s live
   console. Do not claim an integration was tested unless it was actually tested.

Persocket console sign-in uses Google. Your own application’s end users continue
using your existing authentication; do not replace it to integrate messaging.

## Addresses and credentials

| Purpose | Address |
| --- | --- |
| Console and human-readable docs | https://ws-app.persova.co/docs |
| WebSocket connection | wss://ws.persova.co/socket?appKey=YOUR_APP_KEY |
| Publish an event | POST https://ws-api.persova.co/api/apps/YOUR_APP_ID/events |
| Authorize a private subscription | POST https://ws-api.persova.co/api/apps/YOUR_APP_ID/auth |
| .NET integration guide | https://ws-app.persova.co/AGENTS-dotnet.md |

Server-side environment variables:

```env
PERSOCKET_APP_ID=YOUR_APP_ID
PERSOCKET_APP_KEY=YOUR_APP_KEY
PERSOCKET_APP_SECRET=YOUR_APP_SECRET
```

Expose only the public app key to browser code using your framework’s public
configuration convention. Never put the app secret in a public-prefixed variable,
browser bundle, committed file, copied prompt or client-facing response.

The app ID and public key are different values. Publishing and signing requests
require `Authorization: Bearer YOUR_APP_SECRET`. Management endpoints are reserved
for the authenticated console; an app ID or user ID header grants no access.

## Browser client: native WebSocket

No SDK is required. This minimal example connects to a public channel:

```javascript
const channel = "notifications";
const socket = new WebSocket(
  "wss://ws.persova.co/socket?appKey=YOUR_APP_KEY"
);

socket.onopen = () => {
  socket.send(JSON.stringify({ action: "subscribe", channel }));
};

socket.onmessage = ({ data }) => {
  const packet = JSON.parse(data);

  if (packet.event === "subscription_error") {
    console.error("Subscription failed", packet.data);
    return;
  }
  if (packet.channel !== channel) return;
  if (packet.event === "pusher_internal:subscription_succeeded") {
    console.log("Listening on", channel);
    return;
  }
  if (packet.event) return;

  const event = JSON.parse(packet.data);
  console.log(event.name, event.data);
};

socket.onerror = () => console.error("WebSocket connection failed");
socket.onclose = () => console.log("Disconnected");

// Call when the component or page is disposed:
// socket.close();
```

For React, create the connection inside an effect and close it in the effect’s
cleanup. Handle malformed messages, display connection status, and implement
bounded reconnect backoff with resubscription for production use. Re-fetch durable
application state after reconnecting. Do not create connections during render.

## Publish from the server

Call this from your trusted server logic after authenticating the caller and
checking that they may perform the underlying action. Do not expose an endpoint
that accepts arbitrary channels and publishes without an authorization check.

```javascript
const response = await fetch(
  `https://ws-api.persova.co/api/apps/${process.env.PERSOCKET_APP_ID}/events`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.PERSOCKET_APP_SECRET}`,
    },
    body: JSON.stringify({
      channel: "notifications",
      name: "order.created",
      data: { orderId: "1042", message: "On its way!" },
    }),
  }
);
if (!response.ok) throw new Error("Event publish failed");
const result = await response.json();
if (response.status === 207) {
  console.error("Some channels failed", result.failed);
}
```

`channel` may be a string or a string array. Successful publishing returns
`{ "success": true, "channels": ["notifications"] }`. A 207 response also includes
`failed`. Subscribers receive `{ "channel": "notifications", "data": "..." }`,
where `data` is a JSON string containing `{ "name": "order.created", "data": {...} }`.

Events go to currently connected subscribers. They are not durable history.
The current publish endpoint does not implement sender exclusion with `socket_id`.

## Private and presence channels

| Name | Access |
| --- | --- |
| `notifications` | Public; anyone with the app key can subscribe |
| `private-user-42` | Requires a valid subscription signature |
| `presence-team-42` | Requires a signature and a user identity |

Use private channels for personal or restricted data. The `debug-` prefix is
reserved and cannot be subscribed to by clients.

The initial socket control message has `event: "pubby:connection_established"`.
Parse its JSON `data` field to get `socket_id`. These protocol names are retained
for compatibility; do not rename them in a native client implementation.

For a restricted channel, send `socket_id` and `channel_name` from the browser to
an authenticated endpoint in YOUR backend. That backend must determine the user
from their session and check access to the exact channel before calling Persocket:

```http
POST https://ws-api.persova.co/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 the resulting `auth` value to the browser. It subscribes with:

```javascript
socket.send(JSON.stringify({
  action: "subscribe",
  channel: "private-user-42",
  auth: authorization.auth,
}));
```

For presence, the server authorization request also includes `user_id` and optional
`user_info`, derived from the current user. Forward both returned `auth` and
`channel_data` unchanged in the subscribe command. The acknowledgement’s `data`
is an object containing `presence: { ids, hash, count }`. Membership updates use
`pubby:member_added` and `pubby:member_removed`. Both public events and presence
membership are isolated by application, including when channel names match.

Signatures can also be computed on your server using HMAC-SHA256:

- Private input: `socket_id:channel_name`
- Presence input: `socket_id:channel_name:channel_data`
- Output: `APP_KEY:lowercase_hex_signature`

Sign and send exactly the same serialized `channel_data` for presence.

## Existing JavaScript SDK projects

The existing package identifiers remain `@getpubby/sdk` and `@getpubby/sdk/server`.
Do not invent a `@persocket/sdk` package. Existing clients can explicitly configure
the Persocket hosts:

```typescript
import { Pubby as RealtimeClient } from "@getpubby/sdk";
import { PubbyServer as RealtimeServer } from "@getpubby/sdk/server";

// Browser module. authEndpoint belongs to YOUR application.
const client = new RealtimeClient("YOUR_APP_KEY", {
  wsHost: "wss://ws.persova.co",
  authEndpoint: "/api/realtime/auth",
});
const channel = client.subscribe("notifications");
channel.bind("order.created", data => console.log(data));
client.connect();

// Separate server-only module:
const server = new RealtimeServer({
  appId: process.env.PERSOCKET_APP_ID!,
  key: process.env.PERSOCKET_APP_KEY!,
  secret: process.env.PERSOCKET_APP_SECRET!,
  apiHost: "https://ws-api.persova.co",
});
await server.trigger("notifications", "order.created", { orderId: "1042" });
```

Never rely on the SDK’s old default hosts. Review the installed SDK’s connection
lifecycle when adding cleanup or retries. The deployed API does not implement
`/api/apps/:id/channels`; do not use SDK channel-info helpers against that route.
Server-side diagnostics are available at `https://ws.persova.co/channels?appKey=...`
and `/stats?appKey=...`, with the app’s Bearer secret. Do not expose these requests
to browser code because they require the secret.

## Verify your integration

- Confirm subscription acknowledgement before expecting delivery.
- Publish the same event name to the same channel and application.
- Verify browser data handling and component cleanup.
- For restricted data, check both successful access and rejection for other users.
- Try a network disconnect and confirm recovery without duplicate handlers.
- Use the console’s live event tool to diagnose delivery; historical analytics
  may be unavailable. The homepage playground is a local simulation.
