Compare commits

..
4 changed files with 364 additions and 487 deletions

View file

@ -2,117 +2,33 @@
title: Endpoints
---
All application messages arrive at the `Iota` through an authenticated MTP
connection. A sealed application operation uses the reserved `Relay`
communication type. Its outer sender is absent, and its outer receiver is
only the next-hop Iota or local client ID.
The Iota opens and verifies Relay metadata and content. It resolves the
claimed signer ID through trusted user key history; it never treats a public
key carried by the message as authoritative. The `sender_id` fields in the
examples below describe authenticated application data inside the protected
Relay, not an outer MTP sender added by Omikron.
The Iota ID is used by the MTP routing layer and is not an application signer
identity. Omikron does not rewrite a Relay's sender, emit `MessageOtherIota`,
or inspect the protected operation to choose a route.
## MTP Relay API
The Relay API uses the MTP `0.3.0` implementation and protocol version `3.0`.
MTP sends the frame as binary data; the shapes below describe its logical
fields for application integration.
### `Relay` frame
```text
CommunicationValue {
id: optional u32,
type: Relay,
sender: absent,
receiver: next-hop u64,
payload: encrypted relay metadata,
}
```
The clear `receiver` is the only routing identity. The payload is sealed from
Omikron and Omega. The `id` is an MTP correlation value and is not the
authenticated application message identity.
After the metadata recipient opens the payload, the authenticated metadata
contains:
```text
RelayVersion: unsigned integer
MessageId: string
FinalRecipientId: unsigned integer
CreatedAt: Unix epoch milliseconds
Content: encrypted signed application content
Metadata: optional application metadata
```
The signed content contains `MessageType` and the application `Content` value.
The content recipient set is separate from the metadata recipient set. Include
the destination Iota in the content recipient set when that Iota must apply
the operation; metadata-only participants forward the sealed content.
### Relay verification
The receiving Iota performs these checks before dispatching application data:
1. The outer receiver is the local Iota ID.
2. The outer sender is absent.
3. The claimed signer ID is read from decrypted metadata but remains untrusted.
4. Trusted signing-key history is resolved from local user state or the
authenticated Omega control plane.
5. The metadata signature and final recipient are verified with those keys.
6. `(signer_id, MessageId)` is inserted into durable replay storage before an
application side effect.
7. The content signature is checked against the same trusted signer keys.
An application `SenderId` field is checked against the authenticated signer;
it is not a substitute for Relay signature verification. `CreatedAt` can be
used for retention, but it is not part of the replay identity.
### Relay routing
The outer receiver changes when the Relay crosses a routing boundary. The
sealed payload and absent outer sender remain unchanged.
| Stage | Outer receiver | Operation |
| --- | --- | --- |
| Client to origin Iota | Origin Iota ID | Verify the local user's Relay and resolve the final recipient's hosting Iota. |
| Origin Iota to remote Iota | Destination Iota ID | Call `forward_relay_frame()` and send the unchanged sealed Relay through Omikron. |
| Destination Iota to client | Final user ID | Verify and apply content, then forward the original Relay to the local client. |
The Iota never creates `MessageOtherIota`, reconstructs `SetChatSecret`, or
adds the authenticated signer as an outer MTP sender. Failed delivery stores
the encoded sealed Relay for retry.
All messages to the `Iota` are either from the `Omikron` directly, from a `Client` or another `Iota` trying to message a `User` on the `Iota`.
All messages will be marked with a `sender_id` this will be added on the `Omikron`.
It is important to note that the `Iota ID` is not shared with others as it is what verifies the `Iota` users to Tensamin.
In the response the `sender_id` & `receiver_id` must be swapped as sender.
When a message comes from an iota without a `sender-id` that the iota has access to or is designated for the client (`33333333-3333-3333-3333-333333333333`) the message will not be send.
## Messages
### Messaging Lifecycle
The messaging system persists and verifies each protected operation before it
is delivered or forwarded:
The messaging system follows a strict derivation loop to ensure delivery and correct state synchronization:
1. The client sends a sealed `Relay` containing the application operation to
its associated Iota.
2. The Iota verifies the protected signer, final recipient, message ID, and
application content before applying the operation.
3. When another Iota must receive the operation, the Iota persists any
required delivery state and forwards the same sealed `Relay` through
Omikron. The outer receiver changes only when the next hop changes.
4. The recipient Iota verifies the Relay again, stores the operation, and
sends the resulting application update to its local client.
1. The client sends a `message_send` to its Iota.
2. The Iota sends a response `message_send` as confirmation back to the client.
3. The Iota sends a `message_other_iota` to the chat partner's Iota.
- **If it times out:**
- The initial Iota sends a `message_state` to the client with the state `"sending"`.
- **If successful:**
- The other Iota receives the `message_other_iota` and handles it.
- The other Iota informs its client with a `message_live`.
- **If the client doesn't answer:**
1. The other Iota will send a `message_state` to the initial Iota with the state `"sent"`.
2. The initial Iota will store the state and forward the `message_state` to its client.
### Message mutations
`Iota` stores each mutation on the sender and recipient replicas. `Content`
remains encrypted chat content. The sender is the authenticated signer from
the protected Relay and must own the original message for edits and deletion.
The request and update examples in this section show protected application
content, not the outer MTP `CommunicationValue`.
`Iota` stores each mutation on the sender and recipient replicas. `Content` remains encrypted chat content. The sender is taken from the authenticated MTP connection and must own the original message for edits and deletion.
#### `MessageEdit`
@ -159,10 +75,6 @@ Changes an existing message. `ChatPartnerId` and `SendTime` identify the message
Adds one reaction for the authenticated user. Repeating the same request does not create a duplicate row.
Iota stores at most ten distinct reaction strings per message. A user may use
an existing reaction string after the limit is reached. A new string at the
limit returns `ErrorInvalidData` and produces no live update.
##### `REQ (C2S):`
```json
@ -230,27 +142,6 @@ Removes the authenticated user's matching reaction.
`Accepted` is `true` for an add and `false` for a removal.
#### `MessageGet`
Fetches one visible message by `SendTime`. Include `ChatPartnerId` when the
request is a reply lookup. The successful response then includes `Offset`,
the message's absolute position in the same descending history order used by
`MessagesGet`.
```json
{
"type": "MessageGet",
"data": {
"ChatPartnerId": "<chat-partner-id>",
"SendTime": "<message-unix-milliseconds>"
}
}
```
Use the returned `Offset` as the starting position for a page that contains
the target. Requests without `ChatPartnerId` remain accepted for compatibility
but do not include an offset.
#### `MessageDeleteLive`
Deletes a message from both replicas. The existing `MessageDeleteLive` MTP type is used for both the authenticated delete request and the recipient update because no separate delete request type exists in the client type map.
@ -294,10 +185,8 @@ Deletes a message from both replicas. The existing `MessageDeleteLive` MTP type
2. The initial Iota will store the state and forward the `message_state` to its client.
### Client adds someone to their contacts
Resolve a contact name through the control plane before creating the protected
`AddConversation` operation. Omikron does not inspect or mutate this
operation. The JSON below represents the protected application content.
When adding via name, the omikron will intercept and fill the chat_partner_id.
The iota will never read, or handle the chat_partner_name.
##### `REQ:`
@ -308,7 +197,7 @@ operation. The JSON below represents the protected application content.
"type": "add_conversation",
"id": "<uuid>",
"data": {
"chat_partner_id": id,
"chat_partner_name": "name"
@ -331,33 +220,41 @@ operation. The JSON below represents the protected application content.
### Client messages someone
The following object is the protected application content passed to the MTP
Relay builder. It is not the outer frame and its `SenderId`, when present, is
checked against the authenticated Relay signer.
##### `CONTENT:`
##### `REQ:`
```json
{
"MessageType": "MessageSend",
"Content": {
"ReceiverId": "<user-id>",
"Content": "<markdown (encrypted)>",
"Files": [
"sender_id": "<user-id>",
"receiver_id": "<user-id>",
"type": "message_send",
"id": "<uuid>",
"data": {
"receiver_id": "<uuid>",
"content": "<markdown (encrypted)>",
"files": [
{
"Name": "<file-name>",
"Id": "<uuid>",
"Type": "[ image | image_top_right | file ]"
"name": "<cool name>",
"id": "<uuid>",
"type": "[ image | image_top_right | file ]"
}
]
}
}
```
The Relay metadata carries the authenticated `MessageId` and
`FinalRecipientId`. It also carries `CreatedAt` as Unix epoch milliseconds.
The application does not put the signer identity in the outer MTP sender
field.
##### `RES:`
```json
{
"sender_id": "<user-id>",
"receiver_id": "<user-id>",
"type": "message_send",
"id": "<uuid>",
"data": {}
}
```
### Client loads messages
@ -416,50 +313,49 @@ field.
`ClientConnected` and `MessagesGet` include `Edited: true` for messages with at least one edit and a `Reactions` array. Each reaction contains `Reaction` and `SenderId` fields. Clients that do not consume these fields continue to receive the existing message fields.
### Route a message to another Iota
### Send a message to other Iota
The client sends the same `Relay` frame to its origin Iota regardless of the
final recipient's hosting Iota.
##### `REQ:`
```text
Relay {
id: <optional-mtp-frame-id>,
sender: absent,
receiver: <origin-iota-id>,
payload: <sealed MessageSend operation>,
```json
{
"type": "message_send",
"id": "<message_id>",
"data": {
"receiver_id": "99999999-8888-7777-6666-555555555555",
"content": "Hello, how are you?"
}
}
```
The origin Iota resolves the final recipient through the authenticated control
plane and forwards the original sealed frame with `receiver` set to the
destination Iota. It does not create a second application message.
##### `RES:`
```text
Relay {
id: <same-mtp-frame-id>,
sender: absent,
receiver: <destination-iota-id>,
payload: <same sealed MessageSend operation>,
```json
{
"type": "message",
"id": "<message_id>",
"receiver": "<user_id>"
}
```
### Receive a live message from a sealed Relay
### Receive live message (`message_other_iota`)
##### `UPDATE:`
```text
Relay {
id: <same-mtp-frame-id>,
sender: absent,
receiver: <final-user-id>,
payload: <same sealed MessageSend operation>,
```json
{
"type": "message_live",
"id": "<message_id>",
"receiver": "<user_id>",
"data": {
"send_time": unixstamp,
"message": "<content>",
"sender_id": "99999999-8888-7777-6666-555555555555"
}
}
```
The destination Iota applies the authenticated operation before forwarding
the frame to the local client. The client receives the application update
through its authenticated MTP connection.
## Communities
### Client storing a community on their Iota

View file

@ -1,93 +1,73 @@
---
title: MTP Endpoints
lastUpdated: 2026-08-14
title: WSS Endpoints
---
`Omega` serves HTTPS API routes and authenticated MTP WebTransport sessions.
`Omikron` connects through the MTP WebTransport endpoint. The Omega service
uses MTP protocol version `0.3.0` and requires transport authentication before
dispatching application frames.
The `Omega` can be reached by HTTPS request from `Client` and `Iota`.
`Omikron` servers establish a Secure Websocket when booting.
This document covers the MTP connection between `Omikron` and `Omega` and the
control messages handled after authentication. HTTP routes are documented in
[`https.md`](./https.md).
This documentation is split into `Omikron` (WSS) and `HTTPS`.
## MTP Omikron Connection
## Omikron
### Authentication
### Identification
The MTP opening exchange negotiates protocol version `3.0` and completes the
configured cryptographic login or registration flow. Omega uses
`ForceAuthentication`, so an unauthenticated WebTransport session does not
reach the application dispatcher. The authenticated MTP client ID identifies
the `Omikron` connection.
Any message to the `Omega` will be ignored until Identification.
The MTP `Identification`, `Register`, challenge, and signed proof frames are
transport messages. They are not application-level JSON endpoints. After the
handshake, Omega validates the authenticated `Omikron` capability description
before accepting control messages or Relay frames.
### Relay Routing
`Relay` is the reserved MTP communication type for opaque routing between
`Omikron` instances. The logical shape below describes the MTP frame; MTP sends
the frame as binary data, not as JSON.
#### `Relay`
The outer `receiver` is the destination `Iota` next hop. The outer `sender`
field must be absent. The payload contains the sealed Relay metadata and
content and is not opened by Omega.
##### `REQ (Omikron → Omega):`
##### `REQ:`
```json
{
"type": "Relay",
"id": "<frame-id>",
"receiver": "<iota-id>",
"payload": "<opaque sealed relay payload>"
"type": "identification",
"data": {
"omikron": "<omikron-id>",
}
}
```
##### `FORWARD:`
Omega resolves the destination `Iota` through its primary `Omikron` route and
sends the Relay to that one authenticated connection. The outer receiver and
sealed payload remain unchanged. Omega does not decrypt Relay metadata or
content, resolve the original signer, rewrite the sender, or interpret the
inner application type.
Relay route failures are infrastructure failures. Omega rejects a frame with
an outer sender, a missing destination `Iota`, an unavailable primary route,
an unavailable target connection, or a route that points back to the source
`Omikron`. Omega does not turn these failures into user-authored Relay content.
### Control Messages
Control messages use the negotiated MTP type map after the authenticated
connection is established.
#### `PushNotification`
Sends a notification to a user. The receiver ID can be specified in the MTP
frame's `receiver` field or in the data payload.
##### `REQ (Omikron → Omega):`
##### `REQ & RES:`
```json
{
"type": "PushNotification",
"receiver_id": "<user-id>?",
"sender_id": "<user-id>"
"type": "challenge",
"data": {
"public_key": "<omegas public key>",
"challenge": "<random encrypted b64 string>"
}
}
```
##### `RES (Omega → Omikron):`
##### `REQ & RES:`
```json
{
"type": "PushNotification",
"id": "<frame-id>"
"type": "challenge_response",
"data": {
"challenge": "<decrypted b64 string>",
}
}
```
##### `RES:`
```json
{
"type": "identification_response",
"data": {
"accepted": boolean,
}
}
```
### Push Notification
Sends a push notification to a user. The receiver ID can be specified either in the `receiver` field of the message or in the data payload.
##### `REQ:`
```json
{
"type": "push_notification",
"data": {
"receiver_id": "<user-id>", // Optional, if not in receiver field
"sender_id": "<user-id>"
}
}
```

View file

@ -1,126 +1,22 @@
---
title: HTTPS Endpoints
lastUpdated: 2026-08-14
---
#### GET `/api/get/omikron/`
Get an Omikron to connect to.
#### GET `/api/get/omikron/<omikron-id>`
Get an Omikron to connect to, when needing a certain omikron.
`Omega` exposes discovery and profile data over HTTPS. All identifiers in the
routes below are positive decimal integers.
#### GET `/api/get/omikron`
Returns a randomly selected connected `Omikron`.
##### `RES:`
#### GET `/api/get/omikron/<user-id>`
Get an Omikron to connect to, when needing the Omikron a certain User / a certains user's Iota is connected to.
#### GET `/api/get/omikron/<iota-id>`
Get an Omikron to connect to, when needing the Omikron a certain Iota is connected to.
```json
{
"status": "success",
"id": "<omikron-id>",
"public_key": "<base64>",
"ip_address": "<ip-address>",
"port": "<port>"
"type": "omikron",
"data": {
"omikron_id": id,
"domain": "omikron.tensamin.net"
}
}
```
#### GET `/api/get/omikron/<id>`
Returns a connected `Omikron` by ID. If `<id>` identifies a connected `Iota`
or a user with an assigned `Iota`, the response identifies that account's
primary connected `Omikron`.
##### `RES:`
```json
{
"status": "success",
"id": "<omikron-id>",
"public_key": "<base64>",
"ip_address": "<ip-address>",
"port": "<port>"
}
```
#### GET `/api/get/connections`
Returns the live in-memory map of `Omikron` IDs to their connected `Iota` IDs
and hosted user IDs. The map is empty after an Omega restart until connected
`Omikron` instances publish their state.
##### `RES:`
```json
{
"status": "success",
"<omikron-id>": {
"<iota-id>": ["<user-id>", "..."]
}
}
```
#### GET `/api/get/iota/<id>`
Returns the public identity data for an `Iota`.
##### `RES:`
```json
{
"status": "success",
"iota_id": "<iota-id>",
"public_key": "<base64>"
}
```
#### GET `/api/get/user/<id>`
Returns a user's profile and assigned `Iota` ID.
##### `RES:`
```json
{
"status": "success",
"username": "<username>",
"public_key": "<base64>",
"user_id": "<user-id>",
"iota_id": "<iota-id>?",
"sub_level": "<integer>",
"sub_end": "<unix-milliseconds>",
"display": "<string>?",
"status_message": "<string>?",
"about": "<string>?",
"avatar": "<base64>?"
}
```
#### GET `/api/get/id/<username>`
Resolves a username to its public profile and identity IDs. The username is
limited to 15 characters after URL decoding.
##### `RES:`
```json
{
"status": "success",
"username": "<username>",
"public_key": "<base64>",
"user_id": "<user-id>",
"iota_id": "<iota-id>?",
"sub_level": "<integer>",
"sub_end": "<unix-milliseconds>"
}
```
#### GET `/api/get/public_key`
Returns Omega's public MTP key bundle in base64 form.
##### `RES:`
```json
{
"status": "success",
"public_key": "<base64>"
}
```
```

View file

@ -1,147 +1,252 @@
---
title: Endpoints
lastUpdated: 2026-08-14
---
Omikron exposes an MTP WebTransport endpoint through its HTTPS listener. It
does not expose the previous JSON-over-WebSocket challenge protocol. Native
and browser clients use the MTP client APIs described in the repository's
`mtp-docs` guides.
To use the `Omikrons` functions a Secure Websocket connection must be established.
Where to connect is provided by the `Omega` Server.
All entries are split into `Iota` & `Client`.
As the `Omikron` connects the `Iota` and `Client` by passing messages from one to the other all Communication from `Iota` to `Client` has to contain a `receiver_id`
The `Omikron` will add a `sender_id` to Messages from `Client` to the `Iota`.
Omikron and its peers use the MTP `0.3.0` implementation and the wire protocol
version `3.0`. The MTP opening exchange negotiates the version and completes
transport authentication before Omikron dispatches application frames.
The `receiver_id` and `sender_id` of the `Omikron` server are `22222222-2222-2222-2222-222222222222`.
## Connection identities
## Messages
The MTP connection description selects the Omikron connection class:
### Message mutation forwarding
| Description | Authentication | Connection | Relay access |
| --- | --- | --- | --- |
| `client` | Registered MTP authentication | User client | Yes |
| `iota` | Registered MTP authentication | Iota node | Yes |
| `anonymous` | Unauthenticated | Temporary anonymous client | No |
`Omikron` forwards `MessageEditLive`, `MessageReactionLive`, and `MessageDeleteLive` by their MTP receiver. The sender Iota persists the authenticated request before forwarding it. The recipient Iota persists the live update before Omikron sends it to the recipient client.
The `client` ID is the authenticated user ID. The `iota` ID is the
authenticated Iota ID. A connection with an unknown description is rejected by
the application dispatcher. MTP challenge and proof frames are transport
messages, not application endpoints.
The request types `MessageEdit`, `MessageReactionAdd`, and `MessageReactionRemove` stay on the sender Iota. `MessageDeleteLive` is both the delete request and the live update; the authenticated connection direction distinguishes the request from a forwarded update.
## MTP frame shape
> See also: `components/iota/endpoints.md` for message mutation payloads.
MTP sends binary `CommunicationValue` frames. The following is a logical shape
for documentation; it is not a JSON wire format.
## Identification
```text
CommunicationValue {
id: optional u32,
type: CommunicationType,
sender: optional u64,
receiver: optional u64,
payload: DataValue,
Any message to the `Omikron` will be ignored until Identification.
### Iota
##### `REQ:`
```json
{
"type": "identification",
"data": {
"iota_id": "<iota-id>",
}
}
```
`Relay` is the reserved opaque communication type. A Relay has no outer
`sender`, its outer `receiver` is the next-hop ID, and its payload contains
protected relay metadata and content. Omikron reads only the clear routing
fields. It never opens relay metadata or content, resolves the original
signer, or changes the sealed payload.
##### `REQ & RES:`
Tensamin user IDs and Iota IDs currently share the same numeric range. An
Iota-origin Relay whose receiver matches both a local Client identity and a
local Iota identity is rejected as ambiguous; Omikron never chooses a route
from a colliding numeric ID.
```json
{
"type": "challenge",
"data": {
"public_key": "<omikrons public key>",
"challenge": "<random encrypted b64 string>"
}
}
```
##### `REQ & RES:`
## Relay
```json
{
"type": "challenge_response",
"data": {
"challenge": "<decrypted b64 string>",
}
}
```
##### `RES:`
### Client to Iota
A registered Client may send a Relay only to the Iota currently associated
with that Client.
```text
Relay {
id: request or correlation ID,
sender: absent,
receiver: associated Iota ID,
payload: opaque sealed relay,
```json
{
"type": "identification_response",
"data": {
"accepted": boolean,
}
}
```
Omikron validates the Relay layout with MTP's `forward_relay_frame` and sends
the unchanged frame to the local Iota connection. A Client cannot address an
arbitrary Iota or Omikron through this path.
---
### Iota routing
### Client
An Iota may send a Relay to a locally connected Client, another locally
connected Iota, or Omega. Omikron chooses the destination from the clear outer
receiver and the authenticated connection that supplied the frame.
This is a two step process.
| Source connection | Destination | Omikron action |
| --- | --- | --- |
| Client | Its associated Iota | Send to that local Iota |
| Iota | Local Client | Send to matching client sessions |
| Iota | Local Iota | Send to that Iota |
| Iota | Remote Iota | Send to Omega |
| Omega | Local Iota | Send to that Iota |
| Anonymous client | Any destination | Reject |
Get a challenge:
Omega sends a Relay to one destination Omikron. Omikron accepts it only when
the destination Iota is connected locally. It never sends an unavailable
Omega Relay back to Omega or broadcasts it to other Omikrons.
#### `REQ:`
### Validation and errors
```json
{
"id": "<uuid>",
"type": "identification",
"data": {
"user_id": "<user-id>"
}
}
```
Omikron rejects a Relay when:
#### `RES:`
- the communication type is not the reserved `Relay` type;
- an outer sender is present;
- the next-hop receiver is missing;
- a Client addresses an Iota other than its associated Iota;
- an Iota routes to itself;
- the destination Iota or Client is unavailable; or
- the frame cannot be sent on the selected connection.
```json
{
"id": "<uuid>",
"type": "challenge",
"data": {
"challenge": "<base64 string>",
"public_key": "<base64 string>"
}
}
```
Routing failures are ordinary service error frames sent to the authenticated
source connection. The failure frame is not a Relay and does not claim to be
from the original user. Unknown non-Relay application messages are rejected;
Omikron does not use generic fallthrough forwarding.
Send back the solved challenge:
## Application messages
#### `REQ:`
Direct control messages remain available after MTP authentication. Examples
include `GetUserData`, `GetIotaData`, `ChangeUserData`, `ClientChanged`, and
registration control messages. These messages use the negotiated MTP type map
and may use the existing control-plane sender semantics.
```json
{
"id": "<uuid>",
"type": "challenge_response",
"data": {
"challenge": "<decrypted challenge>"
}
}
```
`MessageGet`, `MessagesGet`, and `GetChatSecret` are authenticated peer-control
messages. A Client sends them to its associated Iota through Omikron. Omikron
adds the authenticated client ID as the outer sender for the Iota request,
waits for the correlated response, and returns that response to the Client.
It does not read or store message contents.
#### `RES:`
Those semantics do not apply to `Relay`. Omikron never adds a user or Iota ID
to a Relay and never uses the outer sender as the original message identity.
```json
{
"id": "<uuid>",
"type": "identification_response",
"data": {}
}
```
Message delivery, edits, reactions, deletion, chat secrets, and other
application operations travel inside the sealed Relay. The recipient Iota
opens and verifies the Relay, checks the original signer against its trusted
public-key history, applies the operation, and persists or forwards the sealed
Relay when required.
## Ping & Pong
## Keepalive
A ping request comes from the Client or Iota and the Omikron returns a pong answer.
The ping request should contain the last Client or Iota ping and the pong answer will contain the last ping of the Omikron.****
MTP handles protocol Ping and Pong frames through the connection dispatcher.
Application handlers must not treat keepalive frames as Relay traffic. See the
repository's `mtp-docs/CONNECTIONS.md` and `mtp-docs/PROTOCOL-REFERENCE.md`
files.
### Iota
## Related API documentation
#### REQ:
- [Calling](./calling)
- [User management](./user-management)
- `mtp-docs/CONNECTOR.md`
- `mtp-docs/NATIVE-CLIENT.md`
- `mtp-docs/NATIVE-HOST-WEB-SERVER.md`
```json
{
"id": "<uuid>",
"type": "ping",
"data": {
"last_ping": long
}
}
```
#### RES:
```json
{
"id": "<uuid>",
"type": "pong",
"data": {
"user_ping": {
"<user-id>": long,
"<user-id>": long
}
}
}
```
### Client
#### `REQ:`
```json
{
"id": "<uuid>",
"type": "ping",
"data": {
"last_ping": long
}
}
```
#### `RES:`
```json
{
"id": "<uuid>",
"type": "pong",
"data": {
"last_ping": long
}
}
```
## User Online Status
### Set Client Status
The client can change their online status by sending a `client_changed` message.
Supported status values: `user_online`, `user_offline`, `user_dnd`, `user_idle`, `user_wc`, `user_invisible`.
When `user_invisible` is set, the user remains connected and can send/receive messages, but appears as `user_offline` to all other users.
#### `REQ` (Client → Omikron):
```json
{
"id": "<uuid>",
"type": "client_changed",
"data": {
"user_state": "<status>"
}
}
```
### Online Status Notifications
When a connected user changes their status, interested clients receive a notification:
#### `EVENT` (Omikron → Client):
```json
{
"type": "client_changed",
"data": {
"user_id": "<user-id>",
"user_state": "<status>"
}
}
```
### Online Status in User Data
When fetching another user's profile via `get_user_data`, their online status is included in the response. Invisible users are reported as `user_offline`.
#### `RES` (Omikron → Client, field in `get_user_data`):
```json
{
"id": "<uuid>",
"type": "get_user_data",
"data": {
"user_id": long,
"username": "<string>",
"display": "<string>",
"public_key": "<base64>",
"iota_id": long,
"online_status": "<status>",
"omikron_id": long,
"omikron_connections": [long, ...],
"sub_level": long,
"sub_end": long,
"about": "<string>?",
"avatar": "<base64>?",
"status": "<string>?"
}
}
```