Skip to content

Commit

Permalink
first pass at channels
Browse files Browse the repository at this point in the history
  • Loading branch information
or-else committed Jul 20, 2020
1 parent 6da719a commit 7df05ae
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 29 deletions.
14 changes: 11 additions & 3 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,19 @@ The 'private' parameter of a P2P topic is defined by each participant individual

### Group Topics

Group topics represent communication channels between multiple users. The name of a group topic is `grp` followed by a string of characters from base64 URL-encoding set. No other assumptions can be made about internal structure or length of the group name.
Group topic represents a communication channel between multiple users. The name of a group topic is `grp` or `chn` followed by a string of characters from base64 URL-encoding set. No other assumptions can be made about internal structure or length of the group name.

A group topic is created by sending a `{sub}` message with the topic field set to string `new` optionally followed by any characters, e.g. `new` or `newAbC123` are equivalent. Tinode will respond with a `{ctrl}` message with the name of the newly created topic, i.e. `{sub topic="new"}` is replied with `{ctrl topic="grpmiKBkQVXnm3P"}`. If topic creation fails, the error is reported on the original topic name, i.e. `new` or `newAbC123`. The user who created the topic becomes topic owner. Ownership can be transferred to another user with a `{set}` message but one user must remain the owner at all times.
Group topics support limited number of subscribers (controlled by a `max_subscriber_count` parameter in configuration file) with access permissions of each subscriber managed individually. Group topics may also be enabled to support any number of read-only users - `readers`. All `readers` have the same access permissions. Group topics with enabled `readers` are called `channels`.

A user joining or leaving the topic generates a `{pres}` message to all other users who are currently in the joined state with the topic.
A group topic is created by sending a `{sub}` message with the topic field set to string `new` or `nch` optionally followed by any characters, e.g. `new` or `newAbC123` are equivalent. Tinode will respond with a `{ctrl}` message with the name of the newly created topic, i.e. `{sub topic="new"}` is replied with `{ctrl topic="grpmiKBkQVXnm3P"}`. If topic creation fails, the error is reported on the original topic name, i.e. `new` or `newAbC123`. The user who created the topic becomes topic owner. Ownership can be transferred to another user with a `{set}` message but one user must remain the owner at all times.

A `channel` topic is different from the non-channel group topic in the following ways:

* Channel-enabled topic is created by sending `{sub topic="nch"}`. Sending `{sub topic="new"}` will create a group topic without enabling channel functionality.
* Sending `{sub topic="chnAbC123"}` will create a `reader` subscription to a channel. A non-channel topic will reject such subscription request.
* When searching for topics using [`fnd`](#fnd-and-tags-finding-users-and-topics), channels will show addresses with `chn` prefixes, non-channel topic will show with `grp` prefixes.
* Default permissions for a channel and non-channel group topics are different: channel group topic grants no permissions at all.
* A subscriber joining or leaving the topic generates a `{pres}` message to all other subscribers who are currently in the joined state with the topic and have appropriate permissions. Reader joining or leaving the channel generates no `{pres}` message.

### `sys` Topic

Expand Down
43 changes: 24 additions & 19 deletions server/init_topic.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ func topicInit(t *Topic, join *sessionJoin, h *Hub) {
// Request to load an existing or create a new p2p topic, then attach to it.
err = initTopicP2P(t, join)
case strings.HasPrefix(t.xoriginal, "new"):
// Processing request to create a new group topic or a channel.
err = initTopicNewGrp(t, join)
case strings.HasPrefix(t.xoriginal, "grp"):
// Load existing group topic.
err = initTopicGrpChn(t, join, false)
case strings.HasPrefix(t.xoriginal, "chn"):
// Load existing channel.
err = initTopicGrpChn(t, join, true)
// Processing request to create a new group topic.
err = initTopicNewGrp(t, join, false)
case strings.HasPrefix(t.xoriginal, "nch"):
// Processing request to create a new channel.
err = initTopicNewGrp(t, join, true)
case strings.HasPrefix(t.xoriginal, "grp") || strings.HasPrefix(t.xoriginal, "chn"):
// Load existing group topic (or channel).
err = initTopicGrp(t, join)
case t.xoriginal == "sys":
// Initialize system topic.
err = initTopicSys(t, join)
Expand Down Expand Up @@ -477,7 +477,7 @@ func initTopicP2P(t *Topic, sreg *sessionJoin) error {
}

// Create a new group topic
func initTopicNewGrp(t *Topic, sreg *sessionJoin) error {
func initTopicNewGrp(t *Topic, sreg *sessionJoin, isChan bool) error {
timestamp := types.TimeNow()
pktsub := sreg.pkt.Sub

Expand All @@ -486,8 +486,14 @@ func initTopicNewGrp(t *Topic, sreg *sessionJoin) error {
// Generic topics have parameters stored in the topic object
t.owner = types.ParseUserId(sreg.pkt.AsUser)

t.accessAuth = getDefaultAccess(t.cat, true)
t.accessAnon = getDefaultAccess(t.cat, false)
if isChan {
// The channel is not accessible by default.
t.accessAuth = types.ModeNone
t.accessAnon = types.ModeNone
} else {
t.accessAuth = getDefaultAccess(t.cat, true)
t.accessAnon = getDefaultAccess(t.cat, false)
}

// Owner/creator gets full access to the topic. Owner may change the default modeWant through 'set'.
userData := perUserData{
Expand Down Expand Up @@ -562,6 +568,7 @@ func initTopicNewGrp(t *Topic, sreg *sessionJoin) error {
ObjHeader: types.ObjHeader{Id: sreg.pkt.RcptTo, CreatedAt: timestamp},
Access: types.DefaultAccess{Auth: t.accessAuth, Anon: t.accessAnon},
Tags: tags,
UseBt: isChan,
Public: t.public}

// store.Topics.Create will add a subscription record for the topic creator
Expand All @@ -578,14 +585,10 @@ func initTopicNewGrp(t *Topic, sreg *sessionJoin) error {
return nil
}

// Initialize existing group topic. There is a race condition
// when two users attempt to load the same topic at the same time.
func initTopicGrpChn(t *Topic, sreg *sessionJoin, isChan bool) error {
if isChan {
t.cat = types.TopicCatChn
} else {
t.cat = types.TopicCatGrp
}
// Initialize existing group topic. There is a race condition when two users attempt to load
// the same topic at the same time. It's prevented at hub level.
func initTopicGrp(t *Topic, sreg *sessionJoin) error {
t.cat = types.TopicCatGrp

// TODO(gene): check and validate topic name
stopic, err := store.Topics.Get(t.name)
Expand All @@ -599,6 +602,8 @@ func initTopicGrpChn(t *Topic, sreg *sessionJoin, isChan bool) error {
return err
}

t.isChan = stopic.UseBt

// t.owner is set by loadSubscriptions

t.accessAuth = stopic.Access.Auth
Expand Down
6 changes: 4 additions & 2 deletions server/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,8 @@ func (s *Session) dispatch(msg *ClientComMessage) {

// Request to subscribe to a topic
func (s *Session) subscribe(msg *ClientComMessage) {
if strings.HasPrefix(msg.Original, "new") {
// Request to create a new named topic.
if strings.HasPrefix(msg.Original, "new") || strings.HasPrefix(msg.Original, "nch") {
// Request to create a new group topic.
// If we are in a cluster, make sure the new topic belongs to the current node.
msg.RcptTo = globals.cluster.genLocalTopicName()
} else {
Expand Down Expand Up @@ -1069,6 +1069,8 @@ func (s *Session) expandTopicName(msg *ClientComMessage) (string, *ServerComMess
return "", ErrPermissionDenied(msg.Id, msg.Original, msg.timestamp)
}
routeTo = uid1.P2PName(uid2)
} else if strings.HasPrefix(msg.Original, "chn") {
routeTo = strings.Replace(msg.Original, "chn", "grp", 1)
} else {
routeTo = msg.Original
}
Expand Down
6 changes: 1 addition & 5 deletions server/store/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1189,8 +1189,6 @@ const (
TopicCatGrp
// TopicCatSys is a constant indicating a system topic.
TopicCatSys
// TopicCatChn is a constant indicating a channel.
TopicCatChn
)

// GetTopicCat given topic name returns topic category.
Expand All @@ -1200,14 +1198,12 @@ func GetTopicCat(name string) TopicCat {
return TopicCatMe
case "p2p":
return TopicCatP2P
case "grp":
case "grp", "chn":
return TopicCatGrp
case "fnd":
return TopicCatFnd
case "sys":
return TopicCatSys
case "chn":
return TopicCatChn
default:
panic("invalid topic type for name '" + name + "'")
}
Expand Down
3 changes: 3 additions & 0 deletions server/topic.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type Topic struct {
// Topic category
cat types.TopicCat

// Channel functionality is enabled for the group topic.
isChan bool

// If isProxy == true, the actual topic is hosted by another cluster member.
// The topic should:
// 1. forward all messages to master
Expand Down

0 comments on commit 7df05ae

Please sign in to comment.