@socialproof/myso-messaging-stack
v0.1.0
Published
Permissioned Messaging Groups
Readme
@socialproof/myso-messaging-stack
[!NOTE] The MySocial Messaging Stack tooling is currently in Beta.
The tooling is production-capable for many use cases, but developers should evaluate their own security, reliability, and operational requirements before deploying to production. .
TypeScript SDK for encrypted group messaging on MySocial, powered by MyData for end-to-end encryption.
Installation
npm install @socialproof/myso-messaging-stackArchitecture
The SDK is transport-agnostic. It handles encryption, decryption, key management, and on-chain
group operations — but delegates message delivery and storage to a pluggable RelayerTransport
interface. You can implement this interface to connect to any backend (HTTP server, WebSocket,
peer-to-peer, etc.).
We provide two reference implementations:
HTTPRelayerTransport— Built-in transport that works with the reference relayer. Ships with the SDK.FileStorageRecoveryTransport(example) — Read-only recovery adapter that fetches messages from File Storage storage via the Discovery Indexer. ImplementsRecoveryTransport. Not part of the SDK — seeexamples/recovery-transport/for a reference implementation.
Neither is required — you can build your own transport from scratch.
Quick Start
With the reference relayer (built-in HTTP transport)
The SDK uses MySo's client extension pattern. Chain $extend() to compose mysoGroups, a MyData
extension, and mysoMessagingStack:
import { MySoGrpcClient } from '@socialproof/myso/grpc';
import { mysoGroups } from '@socialproof/myso-groups';
import { mysoMessagingStack } from '@socialproof/myso-messaging-stack';
const client = new MySoGrpcClient({ network: 'testnet' })
.$extend(
mysoGroups({ witnessType: `${pkg}::messaging::Messaging` }),
mydata({ mydataKeyServers }), // from @socialproof/mydata
)
.$extend(
mysoMessagingStack({
encryption: { sessionKey: { ttlMin: 10, signer: keypair } },
relayer: { relayerUrl: 'https://relayer.example.com' },
}),
);
// Send a message
await client.messaging.sendMessage({
signer: keypair,
groupRef: { uuid: 'my-group' },
text: 'Hello, group!',
});
// Fetch and decrypt messages
const { messages } = await client.messaging.getMessages({
signer: keypair,
groupRef: { uuid: 'my-group' },
});With a custom transport
import { mysoMessagingStack } from '@socialproof/myso-messaging-stack';
import type { RelayerTransport } from '@socialproof/myso-messaging-stack';
class MyTransport implements RelayerTransport {
// Implement sendMessage, fetchMessages, subscribe, etc.
// Connect to whatever backend you want.
}
// Use { transport: ... } instead of { relayerUrl: ... }
const client = baseClient.$extend(
mysoMessagingStack({
encryption: { sessionKey: { ttlMin: 10, signer: keypair } },
relayer: { transport: new MyTransport() },
}),
);Recovery from File Storage
If your message backend persists messages to File Storage (as the reference relayer does), the SDK provides utilities to read them back directly — useful when the backend is unavailable and you need to restore conversation history.
SDK Utilities
fromFileStorageMessage(wire)— Converts a raw File Storage message (theserde_jsonwire format used by the reference relayer) to the SDK'sRelayerMessageformat. Handlesnumber[]toUint8Array, ISO 8601 to unix timestamps, field name mapping, and derivingisEdited/isDeleted.FileStorageMessageWire— TypeScript type for the raw JSON shape stored on File Storage.
import { fromFileStorageMessage } from '@socialproof/myso-messaging-stack';
import type { FileStorageMessageWire, RelayerMessage } from '@socialproof/myso-messaging-stack';
// Read a message blob/patch from File Storage (via aggregator, SDK, etc.)
const rawJson = await fetchFromFileStorage(blobId, patchId);
const wire: FileStorageMessageWire = JSON.parse(rawJson);
// Convert to the SDK's standard format — ready for decryption
const message: RelayerMessage = fromFileStorageMessage(wire);Building a Recovery Transport
To restore full conversation history from File Storage, implement RecoveryTransport (1 method:
recoverMessages) that:
- Queries an indexer for which File Storage blobs contain a group's messages
- Downloads message content from the File Storage aggregator
- Converts each message using
fromFileStorageMessage() - Returns them sorted by order
See examples/recovery-transport/ for a complete reference
implementation using the Discovery Indexer.
API Reference
Client Methods
| Method | Description |
| ----------------------- | ------------------------------------------------------------------------------- |
| sendMessage() | Encrypt and send a message to a group |
| getMessages() | Fetch and decrypt messages for a group |
| getMessage() | Fetch and decrypt a single message |
| editMessage() | Re-encrypt and update an existing message |
| deleteMessage() | Soft-delete a message |
| subscribe() | Group stream: decrypted messages, reactions, typing, presence |
| subscribeUserEvents() | Wallet stream: group activity, cross-device read state, group discovery |
| listReactions() | List reaction tallies (count + reactors) keyed by message order |
| addReaction() | Add the signer's emoji reaction to a message (idempotent) |
| removeReaction() | Remove the signer's emoji reaction from a message (idempotent) |
| getReadState() | Fetch and decrypt the wallet-scoped read-state blob |
| updateReadState() | Advance a read watermark (CAS-protected; merges + retries on conflict) |
| getUnreadCounts() | Exact unread counts for many groups — one batch request |
| sendTyping() | Broadcast the signer's typing start/stop (ephemeral, fire-and-forget) |
| getGroupPresence() | Presence snapshot for a group's members (live updates arrive via subscribe()) |
Reactions use canonical Unicode emoji strings (👍, ❤️, 👨👩👧👦, ...). Use the exported
emojiToStorage() helper to NFC-normalize input before comparing against stored entries.
Read-state synchronization is handled by the exported MessagingSyncManager (cache +
compare-and-set retries + cross-device invalidation via read_state.updated user-feed events);
ReadStateManager remains as a deprecated alias.
Transport Interface (RelayerTransport)
| Method | Description |
| ----------------------- | ------------------------------------------------------------------------------------- |
| sendMessage() | Send an encrypted message to the backend |
| fetchMessages() | Fetch paginated messages for a group |
| fetchMessage() | Fetch a single message by ID |
| updateMessage() | Update message content |
| deleteMessage() | Soft-delete a message |
| subscribe() | Stream group events (messages, reactions, typing, presence) |
| subscribeUserEvents() | Stream the wallet-scoped user feed (/v1/users/ws) |
| fetchUnreadCounts() | Batch exact unread counts (POST /v1/users/unread-counts) |
| listGroupReactions() | List per-message reaction entries |
| postGroupReaction() | Add/remove the signer's reaction (idempotent) |
| sendTyping() | Broadcast an ephemeral typing start/stop |
| getGroupPresence() | Presence snapshot for a group's members |
| putUserReadState() | CAS write — throws ReadStateConflictError with the current blob on version mismatch |
| disconnect() | Clean up transport resources |
Recovery Exports
| Export | Description |
| --------------------------- | ----------------------------------------------------------------------- |
| RecoveryTransport | Read-only interface for recovery adapters (1 method: recoverMessages) |
| fromFileStorageMessage() | Convert File Storage wire format to RelayerMessage |
| FileStorageMessageWire | Type for the raw File Storage JSON shape |
| FileStorageAttachmentWire | Type for the raw File Storage attachment shape |
License
Apache-2.0
