TextRoom
janus.plugin.textroom is a text chat built on WebRTC data channels. Users join rooms, broadcast to a room, and whisper to one user, all over one PeerConnection that carries no audio and no video. You use it when an application needs real-time text next to Janus and you do not want another server for it.
Two kinds of request exist, and the difference matters:
- Over the Janus API you only do setup (
setup,ack) and room management (create,edit,destroy,list,exists,listparticipants,allowed,kick,announcement). The key isrequest. These answer at once. - Over the data channel you do everything, including
join,leaveandmessage. The key istextroom, and every message must carry atransactionstring that the reply copies back.
You cannot join a room or send a message through the Janus API.
Enable and configure
Build with the plugin-textroom feature. The plugin reads janus.plugin.textroom.jcfg:
general: {
json = "compact"
}
room-1234: {
description = "Demo Room"
secret = "adminpwd"
}json = "compact" makes the server send each data channel message as one line of JSON. Every key, including pin, history, is_private, post, admin_key and string_ids, is in the TextRoom config reference.
Permanent rooms rewrite the file
create, edit and destroy with "permanent": true write the room into janus.plugin.textroom.jcfg (the reply says "permanent": true). The file is regenerated, so comments in it are lost.
Try it
Room management over the Janus API
No PeerConnection is needed. The transcript runs list, exists, create (with a PIN, a secret and a history of 5 messages), edit, a wrong secret, allowed, kick, destroy, and two requests the plugin refuses.
// TextRoom requests that go through the Janus API: no PeerConnection needed.
// Here the request key is `request`; on a data channel it is `textroom` (see textroom-chat).
// Room 1234 comes from janus.plugin.textroom.jcfg (secret adminpwd).
// → POST /janus
{
"janus": "create",
"transaction": "t-create"
}
// ← 200 OK
{
"janus": "success",
"transaction": "t-create",
"data": {
"id": 4100001234567908
}
}
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.textroom",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100002469135799
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "list"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-1",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "success",
"list": [
{
"room": 1234,
"description": "Demo Room",
"pin_required": false,
"num_participants": 0,
"history": 0
}
]
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "exists",
"room": 1234
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-2",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "success",
"room": 1234,
"exists": true
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-3",
"body": {
"request": "exists",
"room": 99
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-3",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "success",
"room": 99,
"exists": false
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-4",
"body": {
"request": "listparticipants",
"room": 1234
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-4",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "success",
"room": 1234,
"participants": []
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-5",
"body": {
"request": "create",
"room": 5678,
"description": "Docs room",
"secret": "s3cret",
"pin": "1111",
"history": 5,
"is_private": false
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-5",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "created",
"room": 5678,
"permanent": false
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-6",
"body": {
"request": "create",
"room": 5678
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-6",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "error",
"error_code": 418,
"error": "Room 5678 already exists"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-7",
"body": {
"request": "list"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-7",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "success",
"list": [
{
"room": 1234,
"description": "Demo Room",
"pin_required": false,
"num_participants": 0,
"history": 0
},
{
"room": 5678,
"description": "Docs room",
"pin_required": true,
"num_participants": 0,
"history": 5
}
]
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-8",
"body": {
"request": "edit",
"room": 5678,
"secret": "s3cret",
"new_description": "Renamed room"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-8",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "edited",
"room": 5678,
"permanent": false
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-9",
"body": {
"request": "edit",
"room": 5678,
"secret": "wrong",
"new_description": "x"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-9",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "error",
"error_code": 419,
"error": "Unauthorized (wrong secret)"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-10",
"body": {
"request": "allowed",
"room": 5678,
"secret": "s3cret",
"action": "add",
"allowed": [
"token-a",
"token-b"
]
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-10",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "success",
"room": 5678,
"allowed": [
"token-b",
"token-a"
]
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-11",
"body": {
"request": "kick",
"room": 5678,
"secret": "s3cret",
"username": "nobody"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-11",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "error",
"error_code": 423,
"error": "No such user nobody in room 5678"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-12",
"body": {
"request": "destroy",
"room": 5678,
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-12",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "destroyed",
"room": 5678,
"permanent": false
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-13",
"body": {
"request": "destroy",
"room": 5678,
"secret": "s3cret"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-13",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "error",
"error_code": 417,
"error": "No such room (5678)"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-14",
"body": {
"request": "join",
"room": 1234,
"username": "alice"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-14",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "event",
"error_code": 415,
"error": "Unknown request 'join'"
}
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-15",
"body": {
"request": "bogus"
}
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-15",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "event",
"error_code": 415,
"error": "Unknown request 'bogus'"
}
}
}Note that the plugin-level answer sits in plugindata.data. A failed request is still "janus": "success" at the Janus level: read error_code in data. Some errors say "textroom": "error" and some say "textroom": "event" (an unknown request), as in the C plugin.
Chat over data channels
A browser follows the same steps as janus.js:
- Attach to
janus.plugin.textroomand send{"request": "setup"}. You get anack, then an event with"result": "ok"and a JSEP offer from the plugin. - Create the PeerConnection with a data channel called
JanusDataChannel, apply the offer, create the answer. - Send
{"request": "ack"}with the answer asjsep. When ICE and DTLS finish you get thewebrtcupevent and the data channel opens. - Send JSON text on the data channel.
The transcript below was recorded with two real PeerConnections in headless Chrome, alice and bob. Lines marked with a double arrow are frames the server pushed. The server sends compact JSON; the transcript pretty-prints it.
// Two users, alice and bob, each with a PeerConnection that has only a data channel.
// Room 1234 comes from janus.plugin.textroom.jcfg (secret adminpwd), json = compact.
// A double-arrow line is something the server pushed: a long-poll event or a data channel frame.
// → POST /janus
{
"janus": "create",
"transaction": "t-create"
}
// ← 200 OK
{
"janus": "success",
"transaction": "t-create",
"data": {
"id": 4100001234567908
}
}
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.textroom",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100002469135799
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "setup"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-1"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "event",
"session_id": 4100001234567908,
"transaction": "t-1",
"sender": 4100002469135799,
"plugindata": {
"plugin": "janus.plugin.textroom",
"data": {
"textroom": "event",
"result": "ok"
}
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 4100003703703690 1 IN IP4 192.0.2.10\r\ns=Janus TextRoom plugin\r\n…"
}
}
// → POST /janus/4100001234567908/4100002469135799
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "ack"
},
"jsep": {
"type": "answer",
"sdp": "v=0\r\no=- 6100000000001000000 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-2"
}
// ⇠ event, long poll GET /janus/<session>
{
"janus": "webrtcup",
"session_id": 4100001234567908,
"sender": 4100002469135799
}
// → POST /janus/4100001234567908
{
"janus": "attach",
"plugin": "janus.plugin.textroom",
"transaction": "t-attach"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4100001234567908,
"transaction": "t-attach",
"data": {
"id": 4100004938271581
}
}
// → POST /janus/4100001234567908/4100004938271581
{
"janus": "message",
"transaction": "t-1",
"body": {
"request": "setup"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-1"
}
// → POST /janus/4100001234567908/4100004938271581
{
"janus": "message",
"transaction": "t-2",
"body": {
"request": "ack"
},
"jsep": {
"type": "answer",
"sdp": "v=0\r\no=- 6100000000002000000 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4100001234567908,
"transaction": "t-2"
}
// (bob did the same: attach, setup, ack, data channel open)
// → alice sends on the data channel
{
"textroom": "join",
"room": 1234,
"username": "alice",
"display": "Alice",
"transaction": "tx1"
}
// ⇠ alice receives on the data channel
{
"textroom": "join",
"room": 1234,
"username": "alice",
"display": "Alice"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"participants": [],
"transaction": "tx1"
}
// → bob sends on the data channel
{
"textroom": "join",
"room": 1234,
"username": "bob",
"display": "Bob",
"transaction": "tx2"
}
// ⇠ bob receives on the data channel
{
"textroom": "join",
"room": 1234,
"username": "bob",
"display": "Bob"
}
// ⇠ alice receives on the data channel
{
"textroom": "join",
"room": 1234,
"username": "bob",
"display": "Bob"
}
// ⇠ bob receives on the data channel
{
"textroom": "success",
"participants": [
{
"username": "alice",
"display": "Alice"
}
],
"transaction": "tx2"
}
// → bob sends on the data channel
{
"textroom": "message",
"room": 1234,
"text": "hello room",
"transaction": "tx3"
}
// ⇠ alice receives on the data channel
{
"textroom": "message",
"room": 1234,
"from": "bob",
"date": "2026-11-04T09:30:01+0000",
"text": "hello room"
}
// ⇠ bob receives on the data channel
{
"textroom": "message",
"room": 1234,
"from": "bob",
"date": "2026-11-04T09:30:01+0000",
"text": "hello room"
}
// ⇠ bob receives on the data channel
{
"textroom": "success",
"transaction": "tx3"
}
// → bob sends on the data channel
{
"textroom": "message",
"room": 1234,
"to": "alice",
"text": "psst",
"ack": false,
"transaction": "tx4"
}
// ⇠ alice receives on the data channel
{
"textroom": "message",
"room": 1234,
"from": "bob",
"date": "2026-11-04T09:30:01+0000",
"text": "psst",
"whisper": true
}
// → alice sends on the data channel
{
"textroom": "listparticipants",
"room": 1234,
"transaction": "tx5"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"room": 1234,
"participants": [
{
"username": "alice",
"display": "Alice"
},
{
"username": "bob",
"display": "Bob"
}
],
"transaction": "tx5"
}
// → alice sends on the data channel
{
"textroom": "list",
"transaction": "tx6"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"list": [
{
"room": 1234,
"description": "Demo Room",
"pin_required": false,
"num_participants": 2,
"history": 0
}
],
"transaction": "tx6"
}
// → alice sends on the data channel
{
"textroom": "announcement",
"room": 1234,
"secret": "adminpwd",
"text": "Maintenance in five minutes",
"transaction": "tx7"
}
// ⇠ alice receives on the data channel
{
"textroom": "announcement",
"room": 1234,
"date": "2026-11-04T09:30:02+0000",
"text": "Maintenance in five minutes"
}
// ⇠ bob receives on the data channel
{
"textroom": "announcement",
"room": 1234,
"date": "2026-11-04T09:30:02+0000",
"text": "Maintenance in five minutes"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"transaction": "tx7"
}
// → alice sends on the data channel
{
"textroom": "kick",
"room": 1234,
"secret": "adminpwd",
"username": "bob",
"transaction": "tx8"
}
// ⇠ alice receives on the data channel
{
"textroom": "kicked",
"room": 1234,
"username": "bob"
}
// ⇠ bob receives on the data channel
{
"textroom": "kicked",
"room": 1234,
"username": "bob"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"transaction": "tx8"
}
// → bob sends on the data channel
{
"textroom": "message",
"room": 1234,
"text": "can anyone hear me?",
"transaction": "tx9"
}
// ⇠ bob receives on the data channel
{
"textroom": "error",
"error_code": 422,
"error": "Not in room 1234",
"transaction": "tx9"
}
// → alice sends on the data channel
{
"textroom": "leave",
"room": 1234,
"transaction": "tx10"
}
// ⇠ alice receives on the data channel
{
"textroom": "leave",
"room": 1234,
"username": "alice"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"transaction": "tx10"
}
// → alice sends on the data channel
{
"textroom": "join",
"room": 4321,
"username": "alice",
"transaction": "tx11"
}
// ⇠ alice receives on the data channel
{
"textroom": "error",
"error_code": 417,
"error": "No such room (4321)",
"transaction": "tx11"
}
// → alice sends on the data channel
{
"textroom": "bogus",
"transaction": "tx12"
}
// ⇠ alice receives on the data channel
{
"textroom": "error",
"error_code": 415,
"error": "Unsupported request bogus",
"transaction": "tx12"
}What to notice:
- A
joinsends ajoinevent to everyone in the room, including the user who joined. The reply (success, with theparticipantsthat were already there) comes after. - A public
messageis delivered to everyone, including the sender. The sender also gets"success"unless it sent"ack": false. - A whisper (
to) arrives with"whisper": trueand only the recipient sees it. - After
kick, the kicked user stays connected to the plugin but is out of the room: the nextmessagefails with 422. - Errors on the data channel look like
{"textroom": "error", "error_code": …, "error": …, "transaction": …}.
Rooms with a PIN and history over the data channel
// Room management over the data channel: the same verbs as the Janus API, with `textroom` as the key.
// alice and bob are still connected from textroom-chat. Room 8000 keeps the last 2 messages and needs a PIN.
// → alice sends on the data channel
{
"textroom": "create",
"room": 8000,
"description": "Pinned room with history",
"pin": "4242",
"secret": "s3cret",
"history": 2,
"is_private": false,
"transaction": "tx13"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"room": 8000,
"permanent": false,
"transaction": "tx13"
}
// → alice sends on the data channel
{
"textroom": "join",
"room": 8000,
"username": "alice",
"pin": "wrong",
"transaction": "tx14"
}
// ⇠ alice receives on the data channel
{
"textroom": "error",
"error_code": 419,
"error": "Unauthorized (wrong pin)",
"transaction": "tx14"
}
// → alice sends on the data channel
{
"textroom": "join",
"room": 8000,
"username": "alice",
"pin": "4242",
"transaction": "tx15"
}
// ⇠ alice receives on the data channel
{
"textroom": "join",
"room": 8000,
"username": "alice"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"participants": [],
"transaction": "tx15"
}
// → alice sends on the data channel
{
"textroom": "message",
"room": 8000,
"text": "first",
"transaction": "tx16"
}
// ⇠ alice receives on the data channel
{
"textroom": "message",
"room": 8000,
"from": "alice",
"date": "2026-11-04T09:30:01+0000",
"text": "first"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"transaction": "tx16"
}
// → alice sends on the data channel
{
"textroom": "message",
"room": 8000,
"text": "second",
"transaction": "tx17"
}
// ⇠ alice receives on the data channel
{
"textroom": "message",
"room": 8000,
"from": "alice",
"date": "2026-11-04T09:30:02+0000",
"text": "second"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"transaction": "tx17"
}
// → alice sends on the data channel
{
"textroom": "message",
"room": 8000,
"text": "third",
"transaction": "tx18"
}
// ⇠ alice receives on the data channel
{
"textroom": "message",
"room": 8000,
"from": "alice",
"date": "2026-11-04T09:30:02+0000",
"text": "third"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"transaction": "tx18"
}
// → bob sends on the data channel
{
"textroom": "join",
"room": 8000,
"username": "bob",
"pin": "4242",
"transaction": "tx19"
}
// ⇠ bob receives on the data channel
{
"textroom": "message",
"room": 8000,
"from": "alice",
"date": "2026-11-04T09:30:02+0000",
"text": "second"
}
// ⇠ bob receives on the data channel
{
"textroom": "message",
"room": 8000,
"from": "alice",
"date": "2026-11-04T09:30:02+0000",
"text": "third"
}
// ⇠ bob receives on the data channel
{
"textroom": "join",
"room": 8000,
"username": "bob"
}
// ⇠ bob receives on the data channel
{
"textroom": "success",
"participants": [
{
"username": "alice"
}
],
"transaction": "tx19"
}
// ⇠ alice receives on the data channel
{
"textroom": "join",
"room": 8000,
"username": "bob"
}
// → alice sends on the data channel
{
"textroom": "edit",
"room": 8000,
"secret": "s3cret",
"new_description": "Renamed",
"transaction": "tx20"
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"room": 8000,
"permanent": false,
"transaction": "tx20"
}
// → alice sends on the data channel
{
"textroom": "destroy",
"room": 8000,
"secret": "s3cret",
"transaction": "tx21"
}
// ⇠ alice receives on the data channel
{
"textroom": "destroyed",
"room": 8000
}
// ⇠ alice receives on the data channel
{
"textroom": "destroyed",
"room": 8000
}
// ⇠ alice receives on the data channel
{
"textroom": "success",
"room": 8000,
"permanent": false,
"transaction": "tx21"
}
// ⇠ bob receives on the data channel
{
"textroom": "destroyed",
"room": 8000
}A room with history: 2 replays the last two messages to a new participant, before the join event and the reply.
Reference
Janus API requests
| Request | Parameters | Reply |
|---|---|---|
setup | none | ack, then event {"textroom": "event", "result": "ok"} with a JSEP offer |
ack | none, with the JSEP answer | ack |
restart | none | event with a new offer (ICE restart); answer it with ack |
list | admin_key (string, optional) | {"textroom": "success", "list": [{room, description, pin_required, num_participants, history}]} |
listparticipants | room (number, required) | {"textroom": "success", "room", "participants": [{username, display}]} |
exists | room (number, required) | {"textroom": "success", "room", "exists": true or false} |
create | room (number, optional: random if missing), admin_key, description, secret, pin, is_private (default true), history (number of messages, default 0), post (HTTP backend URL), permanent (default false) | {"textroom": "created", "room", "permanent"} |
edit | room (required), secret (if the room has one), new_description, new_secret, new_pin, new_is_private, permanent | {"textroom": "edited", "room", "permanent"} |
destroy | room (required), secret, permanent | {"textroom": "destroyed", "room", "permanent"}; the room's users get a destroyed event |
allowed | room, secret, action (enable, disable, add, remove), allowed (array of tokens, for add and remove) | {"textroom": "success", "room", "allowed": […]} |
kick | room, secret, username | {"textroom": "success"}; the room gets a kicked event |
announcement | room, secret, text | {"textroom": "success"}; the room gets an announcement event |
Over the Janus API the replies for create, edit and destroy say created, edited and destroyed. The same requests over the data channel reply success (see the transcript).
Data channel requests
Every request has "textroom": "<name>" and "transaction": "<string>". The management requests above work here too (list, listparticipants, exists, create, edit, destroy, allowed, kick, announcement), with textroom in place of request. In addition:
| Request | Parameters | Reply and side effects |
|---|---|---|
join | room (required), username (required, unique in the room), display, pin, token (if the room has an ACL), history (boolean, default true: replay history) | {"textroom": "success", "participants": […]}; everyone in the room, including you, gets a join event |
leave | room | {"textroom": "success"}; the others get a leave event |
message | room (required), text (string, required), to (username) or tos (array of usernames) for a whisper, ack (default true) | {"textroom": "success"} if ack is true; recipients get a message event |
to and tos cannot be used together. text is any string: send JSON as a string if you want structure.
Events the server pushes on the data channel
They have no transaction.
textroom | Fields |
|---|---|
join | room, username, display |
leave | room, username |
message | room, from, date (ISO 8601), text, whisper (only for whispers, true) |
announcement | room, date, text |
kicked | room, username |
destroyed | room |
Error codes
| Code | Meaning |
|---|---|
| 411, 412 | No message, invalid JSON |
| 413, 414 | Missing element, invalid element |
| 415 | Unknown or unsupported request |
| 416 | Already set up |
| 417, 418 | No such room, room exists |
| 419 | Unauthorized (wrong secret, PIN or admin key) |
| 420, 421, 422 | Username exists, already in the room, not in the room |
| 423 | No such user |
| 499 | Other error |
Behaviour you may not expect
- The
joinevent reaches the joiner too, and a publicmessageis echoed to its sender. Filter onusernameorfromif you do not want that. createwithout aroompicks a random room id (a number of about 16 digits).- On the data channel an unknown request answers
{"textroom": "error", "error_code": 415, "error": "Unsupported request bogus"}. Over the Janus API the text isUnknown request 'bogus', and thetextroomvalue isevent. - A data channel
joinis also refused over the Janus API: it answers 415Unknown request 'join'. - Admin requests over the data channel (
create,edit,destroy,allowed,kick,announcement) need atransactionlike all the others.
Known gaps
- HTTP backend (
post). The binary POSTs room messages tohttp://URLs and sends URL credentials as Basic auth. It refuseshttps://(TLS is an owner decision, seeplugins/textroom/src/lib.rs). Not exercised here: written from the source. - Not recorded here:
restart(ICE restart) andallowedwith a joining token. They are written from the C source and the Rust request table, not run against the binary. - Nothing else is known to differ from C.
parity/traces/textroom*hold the C transcripts the Rust server is compared against.
Test it in Janus Bench
Open the TextRoom page, or run the check textroom.chat from Run all checks. It sets up two users, joins room 1234, sends a public message and a whisper, and lists the participants. Result today: verified.