Drafty is a text format used by Tinode to style messages. The intent of Drafty is to be expressive just enough without opening too many possibilities for security issues. One may think of it as JSON-encapsulated markdown. Drafty is influenced by FB's draft.js specification. As of the time of this writing Javascript, Java and Swift implementations exist. A Go implementation can convert Drafy to plain text.
this is bold,
code
and italic,strike
combined bold and italic
an url: https://www.example.com/abc#fragment and another https://web.tinode.co
this is a @mention and a #hashtag in a string
second #hashtag
Sample Drafty-JSON representation of the text above:
{
"txt": "this is bold, code and italic, strike combined bold and italic an url: https://www.example.com/abc#fragment and another www.tinode.co this is a @mention and a #hashtag in a string second #hashtag",
"fmt": [
{ "at":8, "len":4,"tp":"ST" },{ "at":14, "len":4, "tp":"CO" },{ "at":23, "len":6, "tp":"EM"},
{ "at":31, "len":6, "tp":"DL" },{ "tp":"BR", "len":1, "at":37 },{ "at":56, "len":6, "tp":"EM" },
{ "at":47, "len":15, "tp":"ST" },{ "tp":"BR", "len":1, "at":62 },{ "at":120, "len":13, "tp":"EM" },
{ "at":71, "len":36, "key":0 },{ "at":120, "len":13, "key":1 },{ "tp":"BR", "len":1, "at":133 },
{ "at":144, "len":8, "key":2 },{ "at":159, "len":8, "key":3 },{ "tp":"BR", "len":1, "at":179 },
{ "at":187, "len":8, "key":3 },{ "tp":"BR", "len":1, "at":195 }
],
"ent": [
{ "tp":"LN", "data":{ "url":"https://www.example.com/abc#fragment" } },
{ "tp":"LN", "data":{ "url":"http://www.tinode.co" } },
{ "tp":"MN", "data":{ "val":"mention" } },
{ "tp":"HT", "data":{ "val":"hashtag" } }
]
}
Drafty object has three fields: plain text txt
, inline markup fmt
, and entities ent
.
The message to be sent is converted to plain Unicode text with all markup stripped and kept in txt
field. In general, a valid Drafy may contain just the txt
field.
Inline formatting is an array of individual styles in the fmt
field. Each style is represented by an object with at least at
and len
fields. The at
value means 0-based offset into txt
, len
is the number of characters to apply formatting to. The third value of style is either tp
or key
.
If tp
is provided, it means the style is a basic text decoration:
ST
: strong or bold text: bold.EM
: emphasized text, usually represented as italic: italic.DL
: deleted or strikethrough text:strikethrough.CO
: code or monotyped text, possibly with different background:monotype
.BR
: line break.RW
: logical grouping of formats, a row.HD
: hidden text.HL
: highlighted text, such as text in a different color or with a different background; the color cannot be specified.
If key is provided, it's a 0-based index into the ent
field which contains an entity definition such as an image or an URL:
LN
: link (URL) https://api.tinode.coMN
: mention such as @tinodeHT
: hashtag, e.g. #hashtagIM
: inline imageEX
: generic attachmentFM
: form / set of fieldsBN
: interactive button
Examples:
{ "at":8, "len":4, "tp":"ST"}
: apply formattingST
(strong/bold) to 4 characters starting at offset 8 intotxt
.{ "at":144, "len":8, "key":2 }
: insert entityent[2]
into position 144, the entity spans 8 characters.{ "at":-1, "len":0, "key":4 }
: show theent[4]
as a file attachment, don't apply any styling to text.
Form provides means to add paragraph-level formatting to a logical group of elements:
Do you agree? |
---|
Yes |
No |
{
"txt": "Do you agree? Yes No",
"fmt": [
{"at": 0, "len": 20, "tp": "FM"},
{"at": 0, "len": 13, "tp": "ST"}
{"at": 13, "len": 1, "tp": "BR"},
{"at": 14, "len": 3, "key": 0},
{"at": 17, "len": 1, "tp": "BR"},
{"at": 18, "len": 2, "key": 1},
],
"ent": [
{"tp": "BN", "data": {"name": "yes", "act": "pub", "val": "oh yes!"}},
{"tp": "BN", "data": {"name": "no", "act": "pub"}}
]
}
If a Yes
button is pressed in the example above, the client is expected to send a message to the server with the following content:
{
"txt": "Yes",
"fmt": [{
"at":-1
}],
"ent": [{
"tp": "EX",
"data": {
"mime": "application/json",
"val": {
"seq": 15, // seq id of the message containing the form.
"resp": {"yes": "oh yes!"}
}
}
}]
}
In general, an entity is a text decoration which requires additional (possibly large) data. An entity is represented by an object with two fields: tp
indicates type of the entity, data
is type-dependent styling information. Unknown fields are ignored.
LN
is an URL. The data
contains a single url
field:
{ "tp": "LN", "data": { "url": "https://www.example.com/abc#fragment" } }
The url
could be any valid URL that the client knows how to interpret, for instance it could be email or phone URL too: email:[email protected]
or tel:+17025550001
.
IMPORTANT Security Consideration: the url
field may be maliciously constructed. The client should disable certain URL schemes such as javascript:
and data:
.
IM
is an image. The data
contains the following fields:
{
"tp": "IM",
"data": {
"mime": "image/png",
"val": "Rt53jUU...iVBORw0KGgoA==",
"ref": "https://api.tinode.co/file/s/abcdef12345.jpg",
"width": 512,
"height": 512,
"name": "sample_image.png",
"size": 123456
}
}
mime
: data type, such as 'image/jpeg'.val
: optional in-band image data: base64-encoded image bits.ref
: optional reference to out-of-band image data. Eitherval
orref
must be present.width
,height
: linear dimensions of the image in pixelsname
: optional name of the original file.size
: optional size of the file in bytes
To create a message with just a single image and no text, use the following Drafty:
{
txt: " ",
fmt: [{len: 1}],
ent: [{tp: "IM", data: {<your image data here>}]}
}
IMPORTANT Security Consideration: the val
and ref
fields may contain malicious payload. The client should restrict URL scheme in the ref
field to http
and https
only. The client should present content of val
field to the user only if it's correctly converted to an image.
EX
is an attachment which the client should not try to interpret. The data
contains the following fields:
{
"tp": "EX",
"data": {
"mime", "text/plain",
"val", "Q3l0aG9uPT0w...PT00LjAuMAo=",
"ref": "https://api.tinode.co/file/s/abcdef12345.txt",
"name", "requirements.txt",
"size": 1234
}
}
mime
: data type, such as 'application/octet-stream'.val
: optional in-band base64-encoded file data.ref
: optional reference to out-of-band file data. Eitherval
orref
must be present.name
: optional name of the original file.size
: optional size of the file in bytes.
To generate a message with the file attachment shown as a downloadable file, use the following format:
{
at: -1,
len: 0,
key: <EX entity reference>
}
IMPORTANT Security Consideration: the ref
fields may contain malicious payload. The client should restrict URL scheme in the ref
field to http
and https
only.
MN
: mention such as @alice
Mention data
contains a single val
field with ID of the mentioned user:
{ "tp":"MN", "data":{ "val":"usrFsk73jYRR" } }
HT
: hashtag, e.g. #tinode
Hashtag data
contains a single val
field with the hashtag value which the client software needs to interpret, for instance it could be a search term:
{ "tp":"HT", "data":{ "val":"tinode" } }
BN
offers an option to send data to a server, either origin server or another one. The data
contains the following fields:
{
"tp": "BN",
"data": {
"name": "confirmation",
"act": "url",
"val": "some-value",
"ref": "https://www.example.com/"
}
}
act
: type of action in response to button click:pub
: send a{pub}
message to the current server.url
: issue anHTTP GET
request to the URL from thedata.ref
field. Aname=val
anduid=<current-user-ID>
query parameters are appended to the url.
name
: optional name of the button which is reported back to the server.val
: additional opaque data. Ifname
is provided butval
is not,val
is assumed to be1
.ref
: the actual URL for theurl
action.
The button in this example will send a HTTP GET to https://www.example.com/?confirmation=some-value&uid=usrFsk73jYRR
IMPORTANT Security Consideration: the client should restrict URL scheme in the url
field to http
and https
only.