Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
./docker/tinode/releases/*
*.gz
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "server/static"]
path = server/static
url = https://github.com/tinode/webapp
120 changes: 120 additions & 0 deletions build-custom.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env bash

set -ex

### builds custom image for makeomatic purposes
# usage "./build-custom.sh tag=v0.15.9"
goplat=( linux )
goarc=( amd64 )
dbtags=( rethinkdb )
releasepath="./docker/tinode/releases"
repository="makeomatic"

export GOPATH=`go env GOPATH`

for line in $@; do
eval "$line"
done

version=${tag#?}

if [ -z "$version" ]; then
echo "Must provide tag as 'tag=v1.2.3'"
exit 1
fi

echo "Releasing $version"

GOSRC=${GOPATH}/src/github.com/tinode
git submodule update --init --recursive

# Prepare directory for the new release
rm -fR ${releasepath}/${version}
mkdir -p ${releasepath}/${version}

if [[ ! -x "$GOPATH/bin/gox" ]]; then
go get github.com/mitchellh/gox
fi

for plat in "${goplat[@]}"
do
for arc in "${goarc[@]}"
do
# Keygen is database-independent
# Remove previous build
rm -f $GOPATH/bin/keygen
# Build
$GOPATH/bin/gox -osarch="${plat}/${arc}" -ldflags "-s -w" -output $GOPATH/bin/keygen ./keygen > /dev/null

for dbtag in "${dbtags[@]}"
do
echo "Building ${dbtag}-${plat}/${arc}..."
tmppath=`mktemp -d`

# Remove previous builds
rm -f $GOPATH/bin/tinode
rm -f $GOPATH/bin/init-db
# Build tinode server and database initializer for RethinkDb and MySQL.
$GOPATH/bin/gox -osarch="${plat}/${arc}" \
-ldflags "-s -w -X main.buildstamp=`git describe --tags`" \
-tags ${dbtag} -output $GOPATH/bin/tinode ./server > /dev/null
$GOPATH/bin/gox -osarch="${plat}/${arc}" \
-ldflags "-s -w" \
-tags ${dbtag} -output $GOPATH/bin/init-db ./tinode-db > /dev/null

# Tar on Mac is inflexible about directories. Let's just copy release files to
# one directory.
mkdir -p ${tmppath}/static/img
mkdir ${tmppath}/static/css
mkdir ${tmppath}/static/audio
mkdir ${tmppath}/static/src
mkdir ${tmppath}/static/umd
mkdir ${tmppath}/templ

# Copy templates and database initialization files
cp ./server/tinode.conf ${tmppath}
cp ./server/templ/*.templ ${tmppath}/templ
cp ./server/static/img/*.png ${tmppath}/static/img
cp ./server/static/img/*.svg ${tmppath}/static/img
cp ./server/static/audio/*.mp3 ${tmppath}/static/audio
cp ./server/static/css/*.css ${tmppath}/static/css
cp ./server/static/index.html ${tmppath}/static
cp ./server/static/index-dev.html ${tmppath}/static
cp ./server/static/umd/*.js ${tmppath}/static/umd
cp ./server/static/manifest.json ${tmppath}/static
cp ./server/static/service-worker.js ${tmppath}/static
# Create empty FCM client-side config.
echo > ${tmppath}/static/firebase-init.js
cp ./tinode-db/data.json ${tmppath}
cp ./tinode-db/*.jpg ${tmppath}
cp ./tinode-db/credentials.sh ${tmppath}

# Build archive. All platforms but Windows use tar for archiving. Windows uses zip.
plat2=$plat
# Rename 'darwin' tp 'mac'
if [ "$plat" = "darwin" ]; then
plat2=mac
fi
# Copy binaries
cp $GOPATH/bin/tinode ${tmppath}
cp $GOPATH/bin/init-db ${tmppath}
cp $GOPATH/bin/keygen ${tmppath}

# Remove possibly existing archive.
rm -f ${releasepath}/${version}/tinode-${dbtag}."${plat2}-${arc}".tar.gz
# Generate a new one
tar -C ${tmppath} -zcf ${releasepath}/${version}/tinode-${dbtag}."${plat2}-${arc}".tar.gz .

rm -fR ${tmppath}
done
done
done

for dbtag in "${dbtags[@]}"
do
rmitags="${repository}/tinode-${dbtag}:${version}"

docker rmi ${rmitags} -f
docker build --build-arg VERSION=$version --build-arg TARGET_DB=${dbtag} --tag ${rmitags} docker/tinode
docker push $rmitags
done
6 changes: 1 addition & 5 deletions docker/tinode/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,7 @@ RUN apk update && \
WORKDIR /opt/tinode

# Get the desired Tinode build.
ADD https://github.com/tinode/chat/releases/download/v$VERSION/tinode-$TARGET_DB.linux-amd64.tar.gz .

# Unpack the Tinode archive.
RUN tar -xzf tinode-$TARGET_DB.linux-amd64.tar.gz \
&& rm tinode-$TARGET_DB.linux-amd64.tar.gz
ADD ./releases/$VERSION/tinode-$TARGET_DB.linux-amd64.tar.gz .

# Copy config template to the container.
COPY config.template .
Expand Down
2 changes: 1 addition & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ User-dependent topic properties:
* given: access permissions given to this user
* private: an application-defined object that is unique to the current user.

Topic usually have subscribers. One the the subscribers may be designated as topic owner (`O` access permission) with full access permissions. The list of subscribers can be queries with a `{get what="sub"}` message. The list of subscribers is returned in a `sub` section of a `{meta}` message.
Topic usually have subscribers. One of the subscribers may be designated as topic owner (`O` access permission) with full access permissions. The list of subscribers can be queries with a `{get what="sub"}` message. The list of subscribers is returned in a `sub` section of a `{meta}` message.

### `me` Topic

Expand Down
2 changes: 1 addition & 1 deletion docs/monitoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ As of the time of this writing the following stats are published:
* `TotalSessions`: the count of all sessions which were created during server's life time.
* `LiveSessions`: the number of sessions currently live, regardless of authentication status.
* `TotalTopics`: the count of all topics activated during servers's life time.
* `LiveTpics`: the number of currently active topics.
* `LiveTopics`: the number of currently active topics.
4 changes: 4 additions & 0 deletions server/hdl_grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"github.com/tinode/chat/pbx"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/channelz/service"
"google.golang.org/grpc/keepalive"
)

Expand Down Expand Up @@ -143,6 +145,8 @@ func serveGrpc(addr string, kaEnabled bool, tlsConf *tls.Config) (*grpc.Server,
}

srv := grpc.NewServer(opts...)
reflection.Register(srv)
service.RegisterChannelzServiceToServer(srv)
pbx.RegisterNodeServer(srv, &grpcNodeServer{})
log.Printf("gRPC/%s%s server is registered at [%s]", grpc.Version, secure, addr)

Expand Down
1 change: 1 addition & 0 deletions server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
// Push notifications
"github.com/tinode/chat/server/push"
_ "github.com/tinode/chat/server/push/fcm"
_ "github.com/tinode/chat/server/push/http"
_ "github.com/tinode/chat/server/push/stdout"

"github.com/tinode/chat/server/store"
Expand Down
156 changes: 156 additions & 0 deletions server/push/http/push_http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package http

import (
"bytes"
"encoding/json"
"errors"
"github.com/tinode/chat/server/drafty"
"github.com/tinode/chat/server/store"
"strconv"
"time"

t "github.com/tinode/chat/server/store/types"
"log"
"net/http"

"github.com/tinode/chat/server/push"
)

var handler httpPush

// How much to buffer the input channel.
const defaultBuffer = 32

type httpPush struct {
initialized bool
input chan *push.Receipt
stop chan bool
}

type configType struct {
Enabled bool `json:"enabled"`
Buffer int `json:"buffer"`
Url string `json:"url"`
}

// Init initializes the handler
func (httpPush) Init(jsonconf string) error {
log.Printf("Init HTTP push")

// Check if the handler is already initialized
if handler.initialized {
return errors.New("already initialized")
}

var config configType
if err := json.Unmarshal([]byte(jsonconf), &config); err != nil {
return errors.New("failed to parse config: " + err.Error())
}

handler.initialized = true

if !config.Enabled {
return nil
}

if config.Buffer <= 0 {
config.Buffer = defaultBuffer
}

handler.input = make(chan *push.Receipt, config.Buffer)
handler.stop = make(chan bool, 1)

go func() {
for {
select {
case msg := <-handler.input:
go sendPushToHttp(msg, config.Url)
case <-handler.stop:
return
}
}
}()

log.Printf("Initialized HTTP push")
return nil
}

func messagePayload(payload *push.Payload) map[string]string {
data := make(map[string]string)
data["topic"] = t.ParseUserId(payload.Topic).String()
data["from"] = t.ParseUserId(payload.From).String()
data["ts"] = payload.Timestamp.Format(time.RFC3339)
data["seq"] = strconv.Itoa(payload.SeqId)
data["mime"] = payload.ContentType
data["content"], _ = drafty.ToPlainText(payload.Content)

return data
}

func sendPushToHttp(msg *push.Receipt, url string) {
log.Print("Prepare to sent HTTP push from: ", msg.Payload.From)

recipientsIds := make([]t.Uid, len(msg.To))
for recipientId := range msg.To {
recipientsIds = append(recipientsIds, recipientId)
}

/*
* Sender user data
*/
sender, _ := store.Users.Get(t.ParseUserId(msg.Payload.From))

/*
* Recipients list with user data, and conversation status
*/
recipientsList, _ := store.Users.GetAll(recipientsIds...)
recipients := map[string]map[string]interface{}{}
for _, r := range recipientsList {
user := map[string]interface{}{
"user": r,
}
recipients[r.Id] = user
}
for uid, to := range msg.To {
recipients[uid.String()]["device"] = to
}

/*
* Generate payload
*/
data := make(map[string]interface{})
data["recipients"] = recipients
data["sender"] = sender
data["payload"] = messagePayload(&msg.Payload)
data["head"] = msg.Payload.Head
requestData, _ := json.Marshal(data)

/*
* Send push through http
*/
log.Print("Sent HTTP push from: ", sender.Id, "to: ", recipientsIds)
_, err := http.Post(url, "application/json", bytes.NewBuffer(requestData))
if err != nil {
log.Fatal("Http send push failed: ", err)
}
}

// IsReady checks if the handler is initialized.
func (httpPush) IsReady() bool {
return handler.input != nil
}

// Push returns a channel that the server will use to send messages to.
// If the adapter blocks, the message will be dropped.
func (httpPush) Push() chan<- *push.Receipt {
return handler.input
}

// Stop terminates the handler's worker and stops sending pushes.
func (httpPush) Stop() {
handler.stop <- true
}

func init() {
push.Register("http", &handler)
}
2 changes: 2 additions & 0 deletions server/push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type Payload struct {
ContentType string `json:"mime"`
// Actual Data.Content of the message, if requested
Content interface{} `json:"content,omitempty"`
// Message head with custom parameters
Head map[string]interface{} `json:"head,omitempty"`
}

// Handler is an interface which must be implemented by handlers.
Expand Down
1 change: 1 addition & 0 deletions server/static
Submodule static added at 44c5e3
9 changes: 9 additions & 0 deletions server/tinode.conf
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,15 @@
"enabled": false
}
},
{
// Send push through http. Useful for handling push notifications by your service.
"name":"http",
"config": {
// Disabled.
"enabled": false,
"url": "http://localhost/tinode"
}
},
{
// Google FCM notificator.
"name":"fcm",
Expand Down
4 changes: 3 additions & 1 deletion server/topic.go
Original file line number Diff line number Diff line change
Expand Up @@ -2556,7 +2556,9 @@ func (t *Topic) makePushReceipt(fromUid types.Uid, data *MsgServerData) *push.Re
From: data.From,
Timestamp: data.Timestamp,
SeqId: data.SeqId,
Content: data.Content}}
Content: data.Content,
Head: data.Head,
}}

for uid := range t.perUser {
// Send only to those who have notifications enabled, exclude the originating user.
Expand Down