Showing preview only (2,614K chars total). Download the full file or copy to clipboard to get everything.
Repository: tinode/chat
Branch: master
Commit: a9164d34d9de
Files: 230
Total size: 2.5 MB
Directory structure:
gitextract_oztk1vft/
├── .gitattributes
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
├── CONTRIBUTING.md
├── INSTALL.md
├── LICENSE
├── README.md
├── README_ko.md
├── SECURITY.md
├── build-all.sh
├── build-py-grpc.sh
├── chatbot/
│ ├── LICENSE
│ ├── README.md
│ ├── csharp/
│ │ └── README.md
│ └── python/
│ ├── .gitignore
│ ├── README.md
│ ├── basic-cookie.sample
│ ├── chatbot.py
│ ├── quotes.txt
│ ├── requirements.txt
│ ├── setup.py
│ └── token-cookie.sample
├── docker/
│ ├── README.md
│ ├── chatbot/
│ │ └── Dockerfile
│ ├── docker-compose/
│ │ ├── README.md
│ │ ├── cluster.mongodb.yml
│ │ ├── cluster.postgres.yml
│ │ ├── cluster.rethinkdb.yml
│ │ ├── cluster.yml
│ │ ├── single-instance.mongodb.yml
│ │ ├── single-instance.postgres.yml
│ │ ├── single-instance.rethinkdb.yml
│ │ └── single-instance.yml
│ ├── exporter/
│ │ ├── Dockerfile
│ │ └── entrypoint.sh
│ └── tinode/
│ ├── Dockerfile
│ ├── config.template
│ └── entrypoint.sh
├── docker-build.sh
├── docker-release.sh
├── docs/
│ ├── API.md
│ ├── CLA.md
│ ├── call-establishment.md
│ ├── drafty.md
│ ├── faq.md
│ ├── monitoring.md
│ ├── thecard.md
│ └── translations.md
├── go.mod
├── go.sum
├── keygen/
│ ├── README.md
│ └── keygen.go
├── loadtest/
│ ├── LICENSE
│ ├── README.md
│ ├── loadtest.scala
│ ├── tinode.beam
│ ├── tinode.erl
│ ├── tinode.scala
│ ├── tsung.xml
│ └── users.csv
├── monitoring/
│ ├── LICENSE
│ ├── README.md
│ └── exporter/
│ ├── README.md
│ ├── build.sh
│ ├── influxdb_exporter.go
│ ├── main.go
│ ├── prom_exporter.go
│ └── scraper.go
├── pbx/
│ ├── README.md
│ ├── go-generate.sh
│ ├── model.pb.go
│ ├── model.proto
│ ├── model_grpc.pb.go
│ ├── py-generate.sh
│ └── py_fix.py
├── py_grpc/
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── pyproject.toml
│ ├── tinode_grpc/
│ │ ├── __init__.py
│ │ ├── model_pb2.py
│ │ ├── model_pb2.pyi
│ │ └── model_pb2_grpc.py
│ └── version.py
├── rest-auth/
│ ├── README.md
│ ├── auth.py
│ ├── dummy_data.json
│ └── requirements.txt
├── server/
│ ├── .golangci.yml
│ ├── api_key.go
│ ├── auth/
│ │ ├── anon/
│ │ │ └── auth_anon.go
│ │ ├── auth.go
│ │ ├── basic/
│ │ │ └── auth_basic.go
│ │ ├── code/
│ │ │ └── auth_code.go
│ │ ├── mock_auth/
│ │ │ └── mock_auth.go
│ │ ├── rest/
│ │ │ ├── README.md
│ │ │ └── auth_rest.go
│ │ └── token/
│ │ └── auth_token.go
│ ├── calls.go
│ ├── cluster.go
│ ├── cluster_leader.go
│ ├── concurrency/
│ │ ├── goroutinepool.go
│ │ └── simplemutex.go
│ ├── datamodel.go
│ ├── db/
│ │ ├── adapter.go
│ │ ├── common/
│ │ │ ├── common.go
│ │ │ ├── common_test.go
│ │ │ └── test_data/
│ │ │ └── test_data.go
│ │ ├── mongodb/
│ │ │ ├── adapter.go
│ │ │ ├── blank.go
│ │ │ ├── schema.md
│ │ │ └── tests/
│ │ │ ├── mongo_test.go
│ │ │ └── test.conf
│ │ ├── mysql/
│ │ │ ├── adapter.go
│ │ │ ├── blank.go
│ │ │ ├── schema.sql
│ │ │ └── tests/
│ │ │ ├── mysql_test.go
│ │ │ └── test.conf
│ │ ├── postgres/
│ │ │ ├── adapter.go
│ │ │ ├── blank.go
│ │ │ ├── schema.sql
│ │ │ └── tests/
│ │ │ ├── postgres_test.go
│ │ │ └── test.conf
│ │ └── rethinkdb/
│ │ ├── adapter.go
│ │ ├── blank.go
│ │ ├── schema.md
│ │ └── tests/
│ │ ├── rethink_test.go
│ │ └── test.conf
│ ├── drafty/
│ │ ├── drafty.go
│ │ ├── drafty_test.go
│ │ └── grapheme.go
│ ├── hdl_files.go
│ ├── hdl_grpc.go
│ ├── hdl_longpoll.go
│ ├── hdl_websock.go
│ ├── http.go
│ ├── http_pprof.go
│ ├── hub.go
│ ├── init_topic.go
│ ├── logs/
│ │ └── logs.go
│ ├── main.go
│ ├── media/
│ │ ├── fs/
│ │ │ └── filesys.go
│ │ ├── media.go
│ │ ├── media_test.go
│ │ └── s3/
│ │ └── s3.go
│ ├── pbconverter.go
│ ├── plugins.go
│ ├── pres.go
│ ├── push/
│ │ ├── common/
│ │ │ └── typedef.go
│ │ ├── fcm/
│ │ │ ├── README.md
│ │ │ ├── payload.go
│ │ │ └── push_fcm.go
│ │ ├── push.go
│ │ ├── stdout/
│ │ │ ├── README.md
│ │ │ └── push_stdout.go
│ │ └── tnpg/
│ │ ├── README.md
│ │ └── push_tnpg.go
│ ├── push.go
│ ├── ringhash/
│ │ ├── ringhash.go
│ │ └── ringhash_test.go
│ ├── run-cluster.sh
│ ├── sanity-test.sh
│ ├── session.go
│ ├── session_test.go
│ ├── sessionstore.go
│ ├── stats.go
│ ├── store/
│ │ ├── mock_store/
│ │ │ └── mock_store.go
│ │ ├── store.go
│ │ └── types/
│ │ ├── types.go
│ │ ├── uidgen.go
│ │ └── uidgen_test.go
│ ├── templ/
│ │ ├── email-password-reset-en.templ
│ │ ├── email-password-reset-es.templ
│ │ ├── email-password-reset-fr.templ
│ │ ├── email-password-reset-pt.templ
│ │ ├── email-password-reset-ru.templ
│ │ ├── email-password-reset-uk.templ
│ │ ├── email-password-reset-vi.templ
│ │ ├── email-password-reset-zh-TW.templ
│ │ ├── email-password-reset-zh.templ
│ │ ├── email-validation-en.templ
│ │ ├── email-validation-es.templ
│ │ ├── email-validation-fr.templ
│ │ ├── email-validation-pt.templ
│ │ ├── email-validation-ru.templ
│ │ ├── email-validation-uk.templ
│ │ ├── email-validation-vi.templ
│ │ ├── email-validation-zh-TW.templ
│ │ ├── email-validation-zh.templ
│ │ ├── sms-universal-en.templ
│ │ ├── sms-universal-es.templ
│ │ ├── sms-universal-fr.templ
│ │ ├── sms-universal-pt.templ
│ │ ├── sms-universal-ru.templ
│ │ ├── sms-universal-uk.templ
│ │ ├── sms-universal-vi.templ
│ │ ├── sms-universal-zh-TW.templ
│ │ └── sms-universal-zh.templ
│ ├── tinode.conf
│ ├── topic.go
│ ├── topic_proxy.go
│ ├── topic_test.go
│ ├── user.go
│ ├── utils.go
│ ├── utils_test.go
│ └── validate/
│ ├── email/
│ │ └── validate.go
│ ├── tel/
│ │ ├── twilio.go
│ │ └── validate.go
│ └── validator.go
├── tinode-db/
│ ├── README.md
│ ├── credentials.sh
│ ├── data.json
│ ├── gendb.go
│ ├── generate_dataset.py
│ ├── main.go
│ └── tinode.conf
└── tn-cli/
├── CODE-STRUCTURE.md
├── LICENSE
├── README.md
├── client.py
├── commands.py
├── input_handler.py
├── macros.py
├── requirements.txt
├── sample-macro-script.txt
├── sample-script.txt
├── tn-cli.py
├── tn_globals.py
└── utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
model_pb2.py binary
model.pb.go binary
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve Tinode
title: ''
labels: 'bug'
assignees: ''
---
**If you are not reporting a bug, please post to https://groups.google.com/d/forum/tinode instead.**
---
### Subject of the issue
Describe your issue here.
### Your environment
#### Server-side
- [ ] web.tinode.co, api.tinode.co
- [ ] sandbox.tinode.co
- [ ] Your own setup:
* platform (Windows, Mac, Linux etc)
* version of Tinode server, e.g. `0.15.2-rc3`
* database backend
* cluster or standalone
#### Client-side
- [ ] TinodeWeb/tinodejs: javascript client
* Browser make and version.
* IMPORTANT! Use `index-dev.html` to reproduce the problem, not `index.html`.
- [ ] Tindroid: Android app
* Android API level (e.g. 25).
* Emulator or hardware, if hardware describe it.
- [ ] Tinodios: iOS app
* iOS version
* Simulator or hardware, if hardware describe it.
- [ ] tn-cli
* Python version
- [ ] Chatbot
* Python version
- Version of the client, e.g. `0.15.1`
- [ ] Your own client. Describe it:
* Transport (gRPC, websocket, long polling)
* Programming language.
* gRPC version, if applicable.
### Steps to reproduce
Tell us how to reproduce this issue.
### Expected behaviour
Tell us what should happen.
### Actual behaviour
Tell us what happens instead.
### Server-side log
Copy server-side log here. You may also attach it to the issue as a file.
### Client-side log
Copy client-side log here (Android logcat, Javascript console, etc). You may also attach it to the issue as a file. When posting console log from Webapp, please use `index-dev.html`, not `index.html`; `index.html` uses minified javascript which produces unusable logs).
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'feature request'
assignees: ''
---
**If you are not requesting a feature, please post to https://groups.google.com/d/forum/tinode instead.**
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
We're happy you want to contribute! You can help us in different ways:
- [Open an issue](https://github.com/tinode/chat/issues) with suggestions for improvements
- Fork this repository and submit a pull request
- Improve the documentation
To submit a pull request, fork the [repository](https://github.com/tinode/chat) and then clone your fork:
git clone git@github.com:<your-repo-name>/chat.git
Make your suggested changes, `git push` and then [submit a pull request](https://github.com/tinode/chat/compare/). Note that before we can accept your pull requests, you need to sign our [Contributor License Agreement](docs/CLA.md).
## Why is the Contributor License Agreement necessary?
We very much appreciate your wanting to contribute to Tinode Chat, but we need to add you to the contributors list first. Note that the [agreement](docs/CLA.md) is not a transfer of copyright ownership, this simply is a license agreement for contributions. You also do not change your rights to use your own contributions for any other purpose. For some background on why contributor license agreements are necessary, you can read FAQs from many other open source projects:
- Django's [CLA FAQ](https://www.djangoproject.com/foundation/cla/faq/)
- A [chapter](http://producingoss.com/en/copyright-assignment.html) from Karl Fogel's _Producing Open Source Software_ on CLAs
- The [Wikipedia article on CLAs](http://en.wikipedia.org/wiki/Contributor_license_agreement)
This is part of the legal framework of the open-source ecosystem that adds some red tape, but protects both the contributor and the company / foundation behind the project. It also gives us the option to relicense the code with a more permissive license in the future.
================================================
FILE: INSTALL.md
================================================
# Installing Tinode
The config file [`tinode.conf`](./server/tinode.conf) contains extensive instructions on configuring the server.
## Installing from Binaries
1. Visit the [Releases page](https://github.com/tinode/chat/releases/), choose the latest or otherwise the most suitable release. From the list of binaries download the one for your database (supported: MySQL, PostgreSQL, MongoDB, RethinkDB) and platform (Linux ARM or Intel, Windows, Mac ARM or Intel). Once the binary is downloaded, unpack it to a directory of your choosing, `cd` to that directory.
2. Make sure your database is running. Make sure it's configured to accept connections from `localhost`. In case of MySQL, Tinode will try to connect as `root` without the password. In case of PostgreSQL, Tinode will try connect as `postgres` with the password `postgres`. See notes below (_Building from Source_, section 4) on how to configure Tinode to use a different user or a password. MySQL 5.7 or above is required (use InnoDB, not MyISAM storage engine). MySQL 5.6 or below **will not work**, use of MyISAM **will cause problems**. PostgreSQL 13 or above is required. PostgreSQL 12 or below **will not work**. MongoDB 4.4 or above is required. MongoDB 4.2 and below **will not work**.
3. Run the database initializer `init-db` (or `init-db.exe` on Windows):
```
./init-db -data=data.json
```
4. Run the `tinode` (or `tinode.exe` on Windows) server. It will work without any parameters.
```
./tinode
```
5. Test your installation by pointing your browser to http://localhost:6060/
## Docker
See [instructions](./docker/README.md)
## Building from Source
1. Install [Go environment](https://golang.org/doc/install). The installation instructions below are for Go 1.18 and newer. Building with the latest Go environment is recommended.
2. OPTIONAL only if you intend to modify the code: Install [protobuf](https://developers.google.com/protocol-buffers/) and [gRPC](https://grpc.io/docs/languages/go/quickstart/) including [code generator](https://developers.google.com/protocol-buffers/docs/reference/go-generated) for Go.
3. Make sure one of the following databases is installed and running:
* MySQL 5.7 or above configured with `InnoDB` engine (8.x preferred). MySQL 5.6 or below **will not work**.
* PostgreSQL 13 or above. PostgreSQL 12 or below **will not work**.
* MongoDB 4.4 or above (8.x preferred). MongoDB 4.2 and below **will not work**.
* RethinkDB (deprecated, support will be dropped in 2027 unless RethinkDB team resumes development).
4. Fetch, build Tinode server and tinode-db database initializer:
- **MySQL**:
```
go install -tags mysql github.com/tinode/chat/server@latest
go install -tags mysql github.com/tinode/chat/tinode-db@latest
```
- **PostgreSQL**:
```
go install -tags postgres github.com/tinode/chat/server@latest
go install -tags postgres github.com/tinode/chat/tinode-db@latest
```
- **MongoDB**:
```
go install -tags mongodb github.com/tinode/chat/server@latest
go install -tags mongodb github.com/tinode/chat/tinode-db@latest
```
- **RethinkDb**:
```
go install -tags rethinkdb github.com/tinode/chat/server@latest
go install -tags rethinkdb github.com/tinode/chat/tinode-db@latest
```
- **All** (bundle all of the above DB adapters):
```
go install -tags "mysql rethinkdb mongodb postgres" github.com/tinode/chat/server@latest
go install -tags "mysql rethinkdb mongodb postgres" github.com/tinode/chat/tinode-db@latest
```
The steps above install Tinode binaries at `$GOPATH/bin/`, sorces and supporting files are located at `$GOPATH/pkg/mod/github.com/tinode/chat@vX.XX.X/` where `X.XX.X` is the version you installed, such as `0.19.1`.
Note the required **`-tags rethinkdb`**, **`-tags mysql`**, **`-tags mongodb`** or **`-tags postgres`** build option.
You may also optionally define `main.buildstamp` for the server by adding a build option, for instance, with a timestamp:
```
go install -tags mysql -ldflags "-X main.buildstamp=`date -u '+%Y%m%dT%H:%M:%SZ'`" github.com/tinode/chat/server@latest
```
The value of `buildstamp` will be sent by the server to the clients.
Building with Go 1.17 or below **will fail**!
5. Open `tinode.conf` (located at `$GOPATH/pkg/mod/github.com/tinode/chat@vX.XX.X/server/`). Check that the database connection parameters are correct for your database. If you are using MySQL make sure [DSN](https://github.com/go-sql-driver/mysql#dsn-data-source-name) in `"mysql"` section is appropriate for your MySQL installation. Option `parseTime=true` is required.
```js
"mysql": {
"dsn": "root@tcp(localhost)/tinode?parseTime=true",
"database": "tinode"
},
```
6. Make sure you specify the adapter name in your `tinode.conf`. E.g. you want to run Tinode with MySQL:
```js
"store_config": {
...
"use_adapter": "mysql",
...
},
```
7. Now that you have built the binaries, follow instructions in the _Running a Standalone Server_ section.
## Running a Standalone Server
If you followed instructions in the previous section then the Tinode binaries are installed in `$GOPATH/bin/`, the sources and supporting files are located in `$GOPATH/pkg/mod/github.com/tinode/chat@vX.XX.X/`, where `X.XX.X` is the version you installed, for example `0.19.1`.
Switch to sources directory (replace `X.XX.X` with your actual version, such as `0.19.1`):
```
cd $GOPATH/pkg/mod/github.com/tinode/chat@vX.XX.X
```
1. Make sure your database is running:
- **MySQL**: https://dev.mysql.com/doc/mysql-startstop-excerpt/5.7/en/mysql-server.html
```
mysql.server start
```
- **PostgreSQL**: https://www.postgresql.org/docs/current/app-pg-ctl.html
```
pg_ctl start
```
- **MongoDB**: https://docs.mongodb.com/manual/administration/install-community/
MongoDB should run as single node replicaset. See https://docs.mongodb.com/manual/administration/replica-set-deployment/
```
mongod
```
- **RethinkDB**: https://www.rethinkdb.com/docs/start-a-server/
```
rethinkdb --bind all --daemon
```
2. Run DB initializer
```
$GOPATH/bin/tinode-db -config=./tinode-db/tinode.conf
```
add `-data=./tinode-db/data.json` flag if you want sample data to be loaded:
```
$GOPATH/bin/tinode-db -config=./tinode-db/tinode.conf -data=./tinode-db/data.json
```
DB initializer needs to be run only once per installation. See [instructions](tinode-db/README.md) for more options.
3. Unpack JS client to a directory, for instance `$HOME/tinode/webapp/` by unzipping `https://github.com/tinode/webapp/archive/master.zip` and `https://github.com/tinode/tinode-js/archive/master.zip` to the same directory.
4. Copy or symlink template directory `./server/templ` to `$GOPATH/bin/templ`
```
ln -s ./server/templ $GOPATH/bin
```
5. Run the server
```
$GOPATH/bin/server -config=./server/tinode.conf -static_data=$HOME/tinode/webapp/
```
6. Test your installation by pointing your browser to [http://localhost:6060/](http://localhost:6060/). The static files from the `-static_data` path are served at web root `/`. You can change this by editing the line `static_mount` in the config file.
**Important!** If you are running Tinode alongside another webserver, such as Apache or nginx, keep in mind that you need to launch the webapp from the URL served by Tinode. Otherwise it won't work.
## Running a Cluster
- Install and run the database, run DB initializer, unpack JS files, and link or copy template directory as described in the previous section. Both MySQL and RethinkDB supports [cluster](https://www.mysql.com/products/cluster/) [mode](https://www.rethinkdb.com/docs/start-a-server/#a-rethinkdb-cluster-using-multiple-machines). You may consider it for added resiliency.
- Cluster expects at least two nodes. A minimum of three nodes is recommended.
- The following section configures the cluster.
```
"cluster_config": {
// Name of the current node.
"self": "",
// List of all cluster nodes, including the current one.
"nodes": [
{"name": "one", "addr":"localhost:12001"},
{"name": "two", "addr":"localhost:12002"},
{"name": "three", "addr":"localhost:12003"}
],
// Configuration of failover feature. Don't change.
"failover": {
"enabled": true,
"heartbeat": 100,
"vote_after": 8,
"node_fail_after": 16
}
}
```
* `self` is the name of the current node. Generally it's more convenient to specify the name of the current node at the command line using `cluster_self` option. Command line value overrides the config file value. If the value is not provided either in the config file or through the command line, the clustering is disabled.
* `nodes` defines individual cluster nodes. The sample defines three nodes named `one`, `two`, and `tree` running at the localhost at the specified cluster communication ports. Cluster addresses don't need to be exposed to the outside world.
* `failover` is an experimental feature which migrates topics from failed cluster nodes keeping them accessible:
* `enabled` turns on failover mode; failover mode requires at least three nodes in the cluster.
* `heartbeat` interval in milliseconds between heartbeats sent by the leader node to follower nodes to ensure they are accessible.
* `vote_after` number of failed heartbeats before a new leader node is elected.
* `node_fail_after` number of heartbeats that a follower node misses before it's considered to be down.
If you are testing the cluster with all nodes running on the same host, you also must override the `listen` and `grpc_listen` ports. Here is an example for launching two cluster nodes from the same host using the same config file:
```
$GOPATH/bin/tinode -config=./server/tinode.conf -static_data=./server/webapp/ -listen=:6060 -grpc_listen=:6080 -cluster_self=one &
$GOPATH/bin/tinode -config=./server/tinode.conf -static_data=./server/webapp/ -listen=:6061 -grpc_listen=:6081 -cluster_self=two &
```
A bash script [run-cluster.sh](./server/run-cluster.sh) may be found useful.
### Enabling Push Notifications
Follow [instructions](./docs/faq.md#q-how-to-setup-push-notifications-with-google-fcm).
### Enabling Video Calls
Video calls use [WebRTC](https://en.wikipedia.org/wiki/WebRTC). WebRTC is a peer to peer protocol: once the call is established, the client applications exchange data directly. Direct data exchange is efficient but creates a problem when the parties are not accessible from the internet. WebRTC solves it by means of [ICE](https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment) servers which implement protocols [TURN(S)](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) and [STUN](https://en.wikipedia.org/wiki/STUN) as fallback.
Tinode does not provide ICE servers out of the box. You must install and configure (or purchase) your own servers otherwise video and voice calling will not be available.
Once you obtain the ICE TURN/STUN configuration from your service provider, add it to `tinode.conf` section `"webrtc"` - `"ice_servers"` (or `"ice_servers_file"`). Also change `"webrtc"` - `"enabled"` to `true`. An example configuration is provided in the `tinode.conf` for illustration only. IT WILL NOT FUNCTION because it uses dummy values instead of actual server addresses.
You may find this information useful for choosing the servers: https://gist.github.com/yetithefoot/7592580
### Note on Running the Server in Background
There is [no clean way](https://github.com/golang/go/issues/227) to daemonize a Go process internally. One must use external tools such as shell `&` operator, `systemd`, `launchd`, `SMF`, `daemon tools`, `runit`, etc. to run the process in the background.
Specific note for [nohup](https://en.wikipedia.org/wiki/Nohup) users: an `exit` must be issued immediately after `nohup` call to close the foreground session cleanly:
```
nohup $GOPATH/bin/server -config=./server/tinode.conf -static_data=$HOME/tinode/webapp/ &
exit
```
Otherwise `SIGHUP` may be received by the server if the shell connection is broken before the ssh session has terminated (indicated by `Connection to XXX.XXX.XXX.XXX port 22: Broken pipe`). In such a case the server will shutdown because `SIGHUP` is intercepted by the server and interpreted as a shutdown request.
For more details see https://github.com/tinode/chat/issues/25.
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
================================================
FILE: README.md
================================================
# Tinode Instant Messaging Server
<img src="docs/logo.svg" align="left" width=128 height=128> Instant messaging full stack. Backend in pure [Go](http://golang.org) (license [GPL 3.0](http://www.gnu.org/licenses/gpl-3.0.en.html)), clients for Android (Java), iOS (Swift), and web (ReactJS), as well as [gRPC](https://grpc.io/) client support for C++, C#, Go, Java, Node, PHP, Python, Ruby, Objective-C, etc (all clients licensed under [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)). Wire transport is JSON over websocket (long polling is also available) or [protobuf](https://developers.google.com/protocol-buffers/) with gRPC.
This is beta-quality software: feature-complete and stable but probably with a few bugs or missing features. Follow [instructions](INSTALL.md) to install and run or use one of the cloud services below. Read [API documentation](docs/API.md).
Tinode is *not* XMPP/Jabber. It is *not* compatible with XMPP. It's meant as a replacement for XMPP. On the surface, it's a lot like open source WhatsApp or Telegram.
<a href="https://apps.apple.com/us/app/tinode/id1483763538"><img src="docs/app-store.svg" height=36></a> <a href="https://play.google.com/store/apps/details?id=co.tinode.tindroidx"><img src="docs/play-store.svg" height=36></a> <a href="https://web.tinode.co/"><img src="docs/web-app.svg" height=36></a>
## Why?
The promise of [XMPP](http://xmpp.org/) was to deliver federated instant messaging: anyone would be able to spin up an IM server capable of exchanging messages with any other XMPP server in the world. Unfortunately, XMPP never delivered on this promise. Instant messengers are still a bunch of incompatible walled gardens, similar to what AoL of the late 1990s was to the open Internet.
The goal of this project is to deliver on XMPP's original vision: create a modern open platform for federated instant messaging with an emphasis on mobile communication. A secondary goal is to create a decentralized IM platform that is much harder to track and block by the governments.
An explicit NON-goal: we are not building yet another Slack replacement.
## Installing and running
See [general instructions](./INSTALL.md) or [docker-specific instructions](./docker/README.md).
## Getting support
* Read [API documentation](docs/API.md) and [FAQ](docs/faq.md). Read configuration instructions contained in the [`tinode.conf`](./server/tinode.conf) file.
* For support, general questions, discussions post to [https://groups.google.com/d/forum/tinode](https://groups.google.com/d/forum/tinode).
* For bugs and feature requests [open an issue](https://github.com/tinode/chat/issues/new/choose).
* Use https://tinode.co/contact for commercial inquiries.
## Helping out
* If you appreciate our work, please help spread the word! Sharing on Reddit, HN, and other communities helps more than you think.
* Consider buying paid support: https://tinode.co/support.html
* If you are a software developer, send us your pull requests with bug fixes and new features.
* If you use the app and discover bugs or missing features, let us know by filing bug reports and feature requests. Vote for existing [feature requests](https://github.com/tinode/chat/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A%22feature+request%22) you find most valuable.
* If you speak a language other than English, [translate](docs/translations.md) the apps into your language. You may also review and improve existing translations.
* If you are a UI/UX expert, help us polish the app UI.
* Use it: install it for your colleagues or friends at work or at home.
## Public service
A [public Tinode service](https://web.tinode.co/) is available. You can use it just like any other instant messenger. Keep in mind that demo accounts present in [sandbox](https://sandbox.tinode.co/) are not available in the public service. You must register an account using valid email in order to use the service.
### Web
TinodeWeb, a single page web app, is available at https://web.tinode.co/ ([source](https://github.com/tinode/webapp/)). See screenshots below.
### Android
[Tinode for Android](https://play.google.com/store/apps/details?id=co.tinode.tindroidx) a.k.a Tindroid is stable and functional ([source](https://github.com/tinode/tindroid)). See the screenshots below. A [debug APK](https://github.com/tinode/tindroid/releases/latest) is also provided for convenience.
### iOS
[Tinode for iOS](https://apps.apple.com/us/app/tinode/id1483763538) a.k.a. Tinodios is stable and functional ([source](https://github.com/tinode/ios)). See the screenshots below.
## Demo/Sandbox
A sandboxed demo service is available at https://sandbox.tinode.co/.
Log in as one of `alice`, `bob`, `carol`, `dave`, `frank`. Password is `<login>123`, e.g. login for `alice` is `alice123`. You can discover other users by email or phone by prefixing them with `email:` or `tel:` respectively. Emails are `<login>@example.com`, e.g. `alice@example.com`, phones are `+17025550001` through `+17025550009`.
When you register a new account you are asked for an email address to send validation code to. For demo purposes you may use `123456` as a universal validation code. The code you get in the email is also valid.
### Sandbox Notes
* The sandbox server is reset (all data wiped) every night at 3:15am Pacific time. An error message `User not found or offline` means the server was reset while you were connected. If you see it on the web, reload and relogin. On Android log out and re-login. If the database was changed, delete the app then reinstall.
* Sandbox user `Tino` is a [basic chatbot](./chatbot) which responds with a [random quote](http://fortunes.cat-v.org/) to any message.
* As generally accepted, when you register a new account you are asked for an email address. The server will send an email with a verification code to that address and you can use it to validate the account. To make things easier for testing, the server will also accept `123456` as a verification code. Remove line `"debug_response": "123456"` from `tinode.conf` to disable this option.
* The sandbox server is configured to use [ACME](https://letsencrypt.org/) TLS [implementation](https://godoc.org/golang.org/x/crypto/acme) with hard-coded requirement for [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication). If you are unable to connect then the most likely reason is your TLS client's missing support for SNI. Use a different client.
* The default web app loads a single minified javascript bundle and minified CSS. The un-minified version is also available at https://sandbox.tinode.co/index-dev.html
* [Docker images](https://hub.docker.com/u/tinode/) with the same demo are available.
* You are welcome to test your client software against the sandbox, hack it, etc. No DDoS-ing though please.
## Features
### Supported
* Multiple native platforms:
* [Android](https://github.com/tinode/tindroid/) (Java)
* [iOS](https://github.com/tinode/ios) (Swift)
* [Web](https://github.com/tinode/webapp/) (React.js)
* Scriptable [command line](tn-cli/) (Python)
* User features:
* One-on-one and group messaging.
* Video and voice calls. Voice messages.
* Channels with unlimited number of read-only subscribers.
* All chats are synchronized across all devices.
* Granular access control with permissions for various actions.
* User search/discovery.
* Rich formatting of messages markdown-style: \*style\* → **style**, with inline images, videos, file attachments.
* Forms and templated responses suitable for chatbots.
* Verified/staff/untrusted account markers.
* Leave notes to self, bookmark (save) messages.
* Message status notifications: message delivery to server; received and read notifications; typing notifications.
* Most recent message preview in contact list.
* Server-generated presence notifications for people, group chats.
* Forwarding and replying to messages.
* Editing sent messages.
* Pinned chats and messages.
* Customizable message backgrounds (wallpapers).
* Light/dark/system UI themes.
* Administration:
* Granular access control with permissions for various actions.
* Support for custom authentication backends.
* Ability to block unwanted communication server-side.
* Anonymous users (important for use cases related to tech support over chat).
* Plugins to extend functionality, for example, to support moderation or chatbots.
* Scriptable [command-line tool](tn-cli/) for server administration.
* Performance, reliability and development:
* Sharded clustering with failover.
* Storage and out of band transfer of large objects like images or document files using local file system or Amazon S3 (other storage systems can be supported with [media handlers](https://github.com/tinode/chat/blob/master/server/media/media.go#L21)).
* JSON or [protobuf version 3](https://developers.google.com/protocol-buffers/) wire protocols.
* Bindings for various programming languages:
* Javascript with no external dependencies.
* Java with dependencies on [Jackson](https://github.com/FasterXML/jackson), [Java-Websocket](https://github.com/TooTallNate/Java-WebSocket), [ICU4J](https://github.com/unicode-org/icu). Suitable for Android but with no Android SDK dependencies.
* Swift with no external dependencies.
* C/C++, C#, Go, Python, PHP, Ruby and many other languages using [gRPC](https://grpc.io/docs/languages/).
* Choice of a database backend. Other databases can be added by writing [adapters](server/db/adapter.go).
* MySQL (and MariaDB, Percona as long as they remain SQL and wire protocol compatible)
* PostgreSQL
* MongoDB
* [RethinkDB](http://rethinkdb.com/). Support is deprecated and will be dropped in 2027 because RethinkDB is no longer being developed (unless its development resumes).
### Planned
* [Federation](https://en.wikipedia.org/wiki/Federation_(information_technology)).
* Location and contacts sharing.
* Previews of attached documents, links.
* Recording video messages.
* Video/audio broadcasting.
* Group video/audio calls.
* Attaching music/audio other than voice messages.
* Different levels of message persistence (from strict persistence to "store until delivered" to purely ephemeral messaging).
* Message encryption at rest.
* End to end encryption with [OTR](https://en.wikipedia.org/wiki/Off-the-Record_Messaging) for one-on-one messaging and undecided method for group messaging.
* Full text search in messages.
### Translations
All client software has support for [internationalization](docs/translations.md). The following translations are provided:
| Language | Server | Webapp | Android | iOS |
| --- | :---: | :---: | :---: | :---: |
| English | ✓ | ✓ | ✓ | ✓ |
| Arabic | | ✓ | | |
| Chinese simplified | ✓ | ✓ | ✓ | ✓ |
| Chinese traditional | ✓ | ✓ | ✓ | ✓ |
| French | ✓ | ✓ | ✓ | |
| German | | ✓ | ✓ | |
| Hindi | | | ✓ | |
| Italian | | ✓ | ✓ | ✓ |
| Korean | | ✓ | ✓ | |
| Portuguese | ✓ | | ✓ | |
| Romanian | | ✓ | ✓ | |
| Russian | ✓ | ✓ | ✓ | ✓ |
| Spanish | ✓ | ✓ | ✓ | ✓ |
| Thai | | ✓ | | |
| Ukrainian | ✓ | ✓ | ✓ | ✓ |
| Vietnamese | ✓ | ✓ | | |
More translations are [welcome](docs/translations.md). In addition to languages listed above, particularly interested in Bengali, Indonesian, Urdu, Japanese, Turkish, Persian.
## Third-Party
### Projects
* [Arango DB adapter](https://github.com/gfxlabs/chat/tree/master/server/db/arango) (outdated)
* [DynamoDB adapter](https://github.com/riandyrn/chat/tree/master/server/db/dynamodb) (outdated)
### Licenses
* Demo avatars and some other graphics are from https://www.pexels.com/ under [CC0 license](https://www.pexels.com/photo-license/) and https://pixabay.com/ under their [license](https://pixabay.com/service/license/).
* Web and Android background patterns are from http://subtlepatterns.com/ under [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) license.
* Android icons are from https://material.io/tools/icons/ under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) license.
## Screenshots
### [Android](https://github.com/tinode/tindroid/)
<p align="center">
<img src="docs/android-contacts.png" alt="Android screenshot: list of chats" width=250 />
<img src="docs/android-chat.png" alt="Android screenshot: one conversation" width=250 />
<img src="docs/android-video-call.png" alt="Android screenshot: video call" width=250 />
</p>
### [iOS](https://github.com/tinode/ios)
<p align="center">
<img src="docs/ios-contacts.png" alt="iOS screenshot: list of chats" width=250 /> <img src="docs/ios-chat.png" alt="iOS screenshot: one conversation" width=250 /> <img src="docs/ios-video-call.png" alt="iOS screenshot: video call" width="250" />
</p>
### [Desktop Web](https://github.com/tinode/webapp/)
<p align="center">
<img src="docs/web-desktop.jpg" alt="Desktop web: full app" width=810 />
</p>
### [Mobile Web](https://github.com/tinode/webapp/)
<p align="center">
<img src="docs/web-mob-contacts.png" alt="Mobile web: contacts" width=250 /> <img src="docs/web-mob-chat.png" alt="Mobile web: chat" width=250 /> <img src="docs/web-mob-video-call.png" alt="Mobile web: topic info" width=250 />
</p>
#### SEO Strings
Words 'chat' and 'instant messaging' in Chinese, Russian, Persian and a few other languages.
* 聊天室 即時通訊
* чат мессенджер
* インスタントメッセージ
* 인스턴트 메신저
* پیام رسان فوری
* تراسل فوري
* فوری پیغام رسانی
* Nhắn tin tức thời
* anlık mesajlaşma sohbet
* mensageiro instantâneo
* pesan instan
* mensajería instantánea
* চ্যাট ইন্সট্যান্ট মেসেজিং
* चैट त्वरित संदेश
* তাৎক্ষণিক বার্তা আদান প্রদান
================================================
FILE: README_ko.md
================================================
# Tinode 인스턴트 메시징 서버
## This document is outdated. For up to date info use [README.md](./README.md)
<img src="docs/logo.svg" align="left" width=128 height=128> 인스턴트 메시징 서버. C++, C#, [Go](http://golang.org), Java, Node, PHP, Python, Ruby, Objective-C 등에 대한 [gRPC](https://grpc.io/) 클라이언트 지원은 물론 순수Go(라이선스 [GPL 3.0](http://www.gnu.org/licenses/gpl-3.0.en.html))의 백엔드와 Java,Javasript 및 Swift의 클라이언트 측 바인딩(라이선스 [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)). 와이어 전송은 사용자 정의 바인딩을 위해 웹소켓을 통한 JSON(롱 폴링도 가능) 즉, gRPC와 [protobuf](https://developers.google.com/protocol-buffers/). 영구 저장소 [RethinkDB](http://rethinkdb.com/), MySQL 및 MongoDB(실험적). 지원되지 않는 타사 [DynamoDB adapter](https://github.com/riandyrn/chat/tree/master/server/db/dynamodb) adapter도 있습니다. 사용자 정의 어댑터를 작성하여 다른 데이터베이스를 지원할 수 있습니다.
Tinode는 XMPP/ Jabber 가 아닙니다. Tinode는 XMPP와 호환되지 않습니다. XMPP를 대체하기 위한 것입니다. 표면적으로는 오픈소스 WhatsApp 또는 Telegram과 매우 유사합니다.
버전 0.16은 베타 급 소프트웨어입니다. 기능은 완전하지만 몇 가지 버그가 있습니다. 아래 클라우드 서비스 중 하나를 설치 및 실행하거나 사용하려면 [지시사항](INSTALL.md)을 따르십시오. [API 설명서](docs/API.md)를 읽으십시오.
<a href="https://apps.apple.com/us/app/tinode/id1483763538"><img src="docs/app-store.svg" height=36></a> <a href="https://play.google.com/store/apps/details?id=co.tinode.tindroidx"><img src="docs/play-store.svg" height=36></a> <a href="https://web.tinode.co/"><img src="docs/web-app.svg" height=36></a>
## Why?
[XMPP](http://xmpp.org/)의 약속은 연합된 인스턴스 메시징을 제공하는 것입니다. 누구나 전세계의 다른 XMPP서버와 메시지를 교환할 수 있는 IM 서버를 가동할 수 있습니다. 불행하게도, XMPP는 이 약속을 이행하지 않았습니다. 인스턴트 메신저들은 1990년대 후반의 AoL공개 인터넷과 비슷한, 양립할 수 없는 벽으로 둘러싸인 정원의 무리들입니다.
이 프로젝트의 목표는 XMPP의 원래 비전인 모바일 통신을 강조하여 연합 인스턴트 메시징을 위한 현대적인 개방형 플랫폼을 만드는 것입니다. 두 번째 목표는 정부가 추적하고 차단하기 훨씬 어려운 분산형 IM플랫폼을 만드는 것입니다.
XMPP: XML에 기반한 메시지 지향 통신 프로토콜
IM: Instant Messenger
## 설치 및 실행
[일반 지침](./INSTALL.md) 또는 [도커별 지침](./docker/README.md)을 참조하십시오.
## 지원받기
* [API 설명서](docs/API.md) 및 [FAQ](docs/faq.md)를 읽으십시오.
* 지원, 일반적인 질문, 토로은[https://groups.google.com/d/forum/tinode](https://groups.google.com/d/forum/tinode).에 게시하십시오.
* 버그 및 기능 요청에 대해서는 [issue](https://github.com/tinode/chat/issues/new)를 여십시오.
## 공공서비스
[Tinode 공공 서비스](https://web.tinode.co/)는 지금 바로 사용할 수 있습니다. 다른 메신저들처럼 사용하면 됩니다. [샌드박스](https://sandbox.tinode.co/)에 있는 데모 계정은 공공 서비스에서 사용할 수 없습니다. 서비스를 이용하려면 유효한 이메일을 사용하여 계정을 등록해야 합니다.
### 웹
Tinode웹은 단일 페이지의 웹으로 https://web.tinode.co/ ([원본](https://github.com/tinode/webapp/))에서 이용이 가능합니다. . 아래에 있는 스크린 샷을 참고하세요. 현재 영어, 중국어 간체, 러시아어를 지원합니다. 더 많은 번역을 환영합니다.
### 안드로이드
Tindroid라고 불리는 [안드로이드 버전의 Tinode](https://play.google.com/store/apps/details?id=co.tinode.tindroidx) 는 안정적으로 가동됩니다. ([원본](https://github.com/tinode/tindroid)). 아래에 있는 스크린 샷을 참고하세요. 편의를 위해 [디버그 APK](https://github.com/tinode/tindroid/releases/latest)도 제공합니다. 현재 영어, 중국어 간체, 러시아어를 지원합니다. 더 많은 번역을 환영합니다.
### iOS
Tinodios라고 불리는 [iOS 버전의 Tinode](https://apps.apple.com/app/reference-to-tinodios-here/id123) 안정적으로 가동됩니다.([원본](https://github.com/tinode/ios)). 아래에 있는 스크린샷을 참고하세요. 현재 영어와 중국어 간체를 지원합니다. 더 많은 번역을 환영합니다.
## 데모/샌드박스
샌드박스 데모 버전은 https://sandbox.tinode.co/ 에서 이용 가능합니다.
alice, bob, carol, dave, frank 중 하나로 로그인할 수 있습니다. 비밀번호는 <이름>123으로 예를 들어, alice의 비밀번호는 alice123입니다. 사용자 이름을 맨 앞에쓴 <이름>@example.com 형식의 이메일이나 +17025550001 부터 +17025550009의 전화번호를 이용해서 다른 사용자들을 찾을 수 있습니다.
새로운 계정을 등록하면 유효성 검사 코드를 보낼 이메일 주소를 묻는 메시지가 나타납니다. 데모의 목적으로 123456을 범용 유효성 검사 코드로 사용할 수 있습니다. 실제 이메일로 받은 코드도 유효합니다.
### 샌드박스 노트
* 샌드박스 서버는 태평양 표준시 기준 매일 오전 3시 15분에 초기화됩니다(모든 데이터가 지워짐). 사용자를 찾을 수 없습니다 또는 오프라인 같은 오류 메시지는 서버에 연결하는 동안 서버가 초기화 되었음을 의미합니다. 만약 해당 오류 메시지가 표시되면 새로고침 후 다시 로그인 하세요. 안드로이드에서 로그아웃 후 다시 로그인 하세요. 만약 데이터베이스가 변경된 경우에는 앱을 삭제했다가 다시 설치하면 됩니다.
* 샌드박스 유저 Tino는 [기본적인 챗봇](./chatbot)으로 모든 메시지에 [임의의 인용구](http://fortunes.cat-v.org/)로 응답합니다.
* 일반적으로 새로운 계정을 등록하면 이메일 주소를 묻는 메시지가 표시됩니다. 서버는 유효성 검사 코드가 포함된 메일을 보내며 이를 사용하여 계정을 검증하는 데 사용할 수 있습니다. 테스트를 보다 쉽게 할 수 있도록 서버는 유효성 검사 코드로 123456을 또한 허용합니다. Tinode.conf에서 ”debug_response”: “123456”행을 제거하여 이 옵션을 비활성화 시킬 수 있습니다.
* 샌드박스 서버는 [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication)에 대한 하드 코딩된 요구사항과 함께 [ACME](https://letsencrypt.org/) TLS [구현](https://godoc.org/golang.org/x/crypto/acme)을 사용하도록 구성되었습니다. 만약 연결할 수 없을 경우 TLS 클라이언트의 SNI 지원 누락일 가능성이 높습니다. 그 경우 다른 클라이언트를 사용하세요.
* 기본 웹 앱은 하나의 축소된 자바스크립트 번들과 축소된 CSS를 가져옵니다. 축소되지 않은 버전은 https://sandbox.tinode.co/index-dev.html 에서도 제공됩니다.
* 데모가 같은 [도커 이미지](https://hub.docker.com/u/tinode/)도 사용 가능합니다.
* 샌드박스에 대해 소프트웨어를 테스트하고 해킹하는 작업, 기타 작업들을 수행할 수 있습니다. DDos는 절대 사용하지 마세요.
## 특징
### 지원 기능
* [Android](https://github.com/tinode/tindroid/), [iOS](https://github.com/tinode/ios), [web](https://github.com/tinode/webapp/), 그리고 [command line](tn-cli/) 클라이언트.
* 1대1 메시징.
* 모든 구성원의 접근 권한을 가진 그룹 메시징을 개별적으로 관리한다. 최대 구성원 수는 설정할 수 있다(기본적으로 128명).
* 다양한 작업에 대한 권한을 가진 항목 액세스 제어
* 서버에서 생성한 사용자 및 주제에 대한 존재 알림.
* 맞춤형 인증 지원
* failover를 통한 Sharded clustering
* 영구 메시지 저장소, 페이지가 지정된 메시지 기록
* 외부 의존성이 없는 javascript 바인딩.
* Android SDK dependencies.Java 바인딩(의존성: [Jackson](https://github.com/FasterXML/jackson), [Java-Websocket](https://github.com/TooTallNate/Java-WebSocket)). Android에 적합하지만 Android SDK 종속성이 없음.
* TCP 또는 Unix 소켓을 통한 Webocket, long polling, 및 [gRPC](https://grpc.io/).
* JSON 또는[protobuf 버전 3](https://developers.google.com/protocol-buffers/) 와이어 프로토콜.
* [암호화](https://letsencrypt.org/) 또는 기존 인증서를 내장한 [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) 옵션
* 사용자 검색/발견.
* 풍부한 메시지 형식, 마크다운 스타일: \*style\* → **style**.
* 인라인 이미지 및 첨부 파일.
* 챗봇에 적합한 양식 및 템플리트 응답.
* 메시지 상태 알림: 서버로 메시지 전달; 수신 및 읽기 알림; 입력 알림.
* 클라이언트 측 데이터 캐싱 지원.
* 원하지 않는 통신 서버를 차단하는 기능.
* 익명 사용자(대화 중 기술 지원 관련 사용 사례에 중요성).
* [FCM](https://firebase.google.com/docs/cloud-messaging/) 또는 [TNPG](server/push/tnpg/)를 사용하여 알림을 푸시.
* 로컬 파일 시스템 또는 Amazon S3를 사용하여 비디오 파일과 같은 대형 오브젝트의 저장 및 대역 외 전송.
* 챗봇을 활성화하기 위해 기능을 확장하는 플러그인.
### 계획
* [연방(연합,연맹)](https://en.wikipedia.org/wiki/Federation_(information_technology)).
* 일대일 메시징을 위한 [OTR](https://en.wikipedia.org/wiki/Off-the-Record_Messaging)과 그룹 메시징을 위한 미확정 방법으로 End to end 암호화.
* bearer token 액세스 제어를 가진 무제한 회원(또는 수십만 명)의 그룹 메시징.
* 자동 예비 시스템.
* 메시지 지속성 다른 수준(엄격한 지속성부터 "전달될 때까지 저장"까지, 완전히 짧은 메시징까지).
### 번역
모든 클라이언트 소프트웨어는 국제화를 지원한다. 번역은 영어, 중국어 간체, 러시아어(iOS 제외)에 제공된다. 더 많은 번역을 환영한다. 특히 스페인어, 아랍어, 독일어, 페르시아어, 인도네시아어, 포르투갈어, 힌디어, 벵골어에 관심이 많다.
## 타사 라이선스
* 데모 아바타와 일부 다른 그래픽은 [CC0](https://www.pexels.com/photo-license/) 라이센스에 따라 https://www.pexels.com/에서 제공된다.
* 웹 및 안드로이드 배경 패턴은 [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) 라이센스에 따라 http://subtlepatterns.com/ 에서 제공된다.
* Android 아이콘은 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) 라이센스의 https://material.io/tools/icons/ 에서 제공된다.
* 일부 iOS 아이콘은 [CC BY-ND 3.0](https://icons8.com/license) 라이센스에 따라 https://icons8.com/ 에서 제공된다.
## 스크린샷
### [안드로이드](https://github.com/tinode/tindroid/)
<p align="center">
<img src="docs/android-contacts.png" alt="Android screenshot: list of chats" width=270 />
<img src="docs/android-chat.png" alt="Android screenshot: one conversation" width=270 />
<img src="docs/android-account.png" alt="Android screenshot: account settings" width=270 />
</p>
### [iOS](https://github.com/tinode/ios)
<p align="center">
<img src="docs/ios-contacts.png" alt="iOS screenshot: list of chats" width=207 /> <img src="docs/ios-chat.png" alt="iOS screenshot: one conversation" width=207 /> <img src="docs/ios-acc-personal.png" alt="iOS screenshot: account settings" width="207" />
</p>
### [데스크탑 웹](https://github.com/tinode/webapp/)
<p align="center">
<img src="docs/web-desktop-2.png" alt="Desktop web: full app" width=810 />
</p>
### [모바일 웹](https://github.com/tinode/webapp/)
<p align="center">
<img src="docs/web-mob-contacts-1.png" alt="Mobile web: contacts" width=250 /> <img src="docs/web-mob-chat-1.png" alt="Mobile web: chat" width=250 /> <img src="docs/web-mob-info-1.png" alt="Mobile web: topic info" width=250 />
</p>
#### SEO 문자열
중국어, 러시아어, 페르시아어 및 다른 몇 가지 언어로 '챗'과 '인스턴트 메시징'을 표시한다.
* 聊天室 即時通訊
* чат мессенджер
* インスタントメッセージ
* 인스턴트 메신저
* پیامرسانی فوری گپ
* تراسل فوري
* Nhắn tin tức thời
* anlık mesajlaşma sohbet
* mensageiro instantâneo
* pesan instan
* mensajería instantánea
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Reporting a Vulnerability
Please report a vulnerability to `security@tinode.co`.
## Do NOT to report:
* Firebase initialization tokens. The Firebase tokens are really public: they must be distributed with the client applications and consequently are not private by design.
* Exposed `/pprof` and/or `/expvar`. We know they are exposed. It's intentional and harmless.
* Exposed Prometheus metrics `/metrics`. Like above, it's intentional and harmless.
* DMARC policy is not enabled `p=none`. We know and that's the way we like it for now.
* Weak cipher suites (TLS 1.0) at `*.tinode.co`. Yes, we know. Does not look serious/important.
================================================
FILE: build-all.sh
================================================
#!/bin/bash
# This script builds and archives binaries and supporting files for mac, linux, and windows.
# If directory ./server/static exists, it's asumed to contain TinodeWeb and then it's also
# copied and archived.
# Supported OSs: mac (darwin), windows, linux.
goplat=( darwin darwin windows linux linux )
# CPUs architectures: amd64 and arm64. The same order as OSs.
goarc=( amd64 arm64 amd64 amd64 arm64 )
# Number of platform+architectures.
buildCount=${#goplat[@]}
# Supported database tags
dbadapters=( mysql mongodb rethinkdb postgres )
dbtags=( ${dbadapters[@]} alldbs )
for line in $@; do
eval "$line"
done
version=${tag#?}
if [ -z "$version" ]; then
# Get last git tag as release version. Tag looks like 'v.1.2.3', so strip 'v'.
version=`git describe --tags`
version=${version#?}
fi
echo "Releasing $version"
GOSRC=..
pushd ${GOSRC}/chat > /dev/null
# Prepare directory for the new release
rm -fR ./releases/${version}
mkdir ./releases/${version}
# Tar on Mac is inflexible about directories. Let's just copy release files to
# one directory.
rm -fR ./releases/tmp
mkdir -p ./releases/tmp/templ
# Copy templates and database initialization files
cp ./server/tinode.conf ./releases/tmp
cp ./server/templ/*.templ ./releases/tmp/templ
cp ./tinode-db/data.json ./releases/tmp
cp ./tinode-db/*.jpg ./releases/tmp
cp ./tinode-db/credentials.sh ./releases/tmp
# Create directories for and copy TinodeWeb files.
if [[ -d ./server/static ]]
then
mkdir -p ./releases/tmp/static/img
mkdir ./releases/tmp/static/img/bkg
mkdir ./releases/tmp/static/css
mkdir ./releases/tmp/static/audio
mkdir ./releases/tmp/static/src
mkdir ./releases/tmp/static/umd
cp ./server/static/img/*.png ./releases/tmp/static/img
cp ./server/static/img/*.svg ./releases/tmp/static/img
cp ./server/static/img/*.jpeg ./releases/tmp/static/img
cp ./server/static/img/bkg/*.png ./releases/tmp/static/img/bkg
cp ./server/static/img/bkg/*.jpg ./releases/tmp/static/img/bkg
cp ./server/static/img/bkg/*.json ./releases/tmp/static/img/bkg
cp ./server/static/audio/*.m4a ./releases/tmp/static/audio
cp ./server/static/css/*.css ./releases/tmp/static/css
cp ./server/static/index.html ./releases/tmp/static
cp ./server/static/index-dev.html ./releases/tmp/static
cp ./server/static/version.js ./releases/tmp/static
cp ./server/static/umd/*.js ./releases/tmp/static/umd
cp ./server/static/manifest.json ./releases/tmp/static
cp ./server/static/service-worker.js ./releases/tmp/static
# Create empty FCM client-side config.
echo 'const FIREBASE_INIT = {};' > ./releases/tmp/static/firebase-init.js
else
echo "TinodeWeb not found, skipping"
fi
for (( i=0; i<${buildCount}; i++ ));
do
plat="${goplat[$i]}"
arc="${goarc[$i]}"
# Use .exe file extension for binaries on Windows.
ext=""
if [ "$plat" = "windows" ]; then
ext=".exe"
fi
# Remove possibly existing keygen from previous build.
rm -f ./releases/tmp/keygen
rm -f ./releases/tmp/keygen.exe
# Keygen is database-independent
env GOOS="${plat}" GOARCH="${arc}" go build -ldflags "-s -w" -o ./releases/tmp/keygen${ext} ./keygen > /dev/null
for dbtag in "${dbtags[@]}"
do
echo "Building ${dbtag}-${plat}/${arc}..."
# Remove possibly existing binaries from previous build.
rm -f ./releases/tmp/tinode
rm -f ./releases/tmp/tinode.exe
rm -f ./releases/tmp/init-db
rm -f ./releases/tmp/init-db.exe
# Build tinode server and database initializer for RethinkDb and MySQL.
# For 'alldbs' tag, we compile in all available DB adapters.
if [ "$dbtag" = "alldbs" ]; then
buildtag="${dbadapters[@]}"
else
buildtag=$dbtag
fi
env GOOS="${plat}" GOARCH="${arc}" go build \
-ldflags "-s -w -X main.buildstamp=`git describe --tags`" -tags "${buildtag}" \
-o ./releases/tmp/tinode${ext} ./server > /dev/null
env GOOS="${plat}" GOARCH="${arc}" go build \
-ldflags "-s -w" -tags "${buildtag}" -o ./releases/tmp/init-db${ext} ./tinode-db > /dev/null
# Build archive. All platforms but Windows use tar for archiving. Windows uses zip.
if [ "$plat" = "windows" ]; then
# Remove possibly existing archive.
rm -f ./releases/${version}/tinode-${dbtag}."${plat}-${arc}".zip
# Generate a new one
pushd ./releases/tmp > /dev/null
zip -q -r ../${version}/tinode-${dbtag}."${plat}-${arc}".zip ./*
popd > /dev/null
else
plat2=$plat
# Rename 'darwin' tp 'mac'
if [ "$plat" = "darwin" ]; then
plat2=mac
fi
# Remove possibly existing archive.
rm -f ./releases/${version}/tinode-${dbtag}."${plat2}-${arc}".tar.gz
# Generate a new one
tar -C ./releases/tmp -zcf ./releases/${version}/tinode-${dbtag}."${plat2}-${arc}".tar.gz .
fi
done
done
# Build chatbot release
echo "Building python code..."
./build-py-grpc.sh
# Release chatbot
echo "Packaging chatbot.py..."
rm -fR ./releases/tmp
mkdir -p ./releases/tmp
cp ${GOSRC}/chat/chatbot/python/chatbot.py ./releases/tmp
cp ${GOSRC}/chat/chatbot/python/quotes.txt ./releases/tmp
cp ${GOSRC}/chat/chatbot/python/requirements.txt ./releases/tmp
tar -C ${GOSRC}/chat/releases/tmp -zcf ./releases/${version}/py-chatbot.tar.gz .
pushd ./releases/tmp > /dev/null
zip -q -r ../${version}/py-chatbot.zip ./*
popd > /dev/null
# Release tn-cli
echo "Packaging tn-cli..."
rm -fR ./releases/tmp
mkdir -p ./releases/tmp
cp ${GOSRC}/chat/tn-cli/*.py ./releases/tmp
cp ${GOSRC}/chat/tn-cli/*.txt ./releases/tmp
tar -C ${GOSRC}/chat/releases/tmp -zcf ./releases/${version}/tn-cli.tar.gz .
pushd ./releases/tmp > /dev/null
zip -q -r ../${version}/tn-cli.zip ./*
popd > /dev/null
# Clean up temporary files
rm -fR ./releases/tmp
popd > /dev/null
================================================
FILE: build-py-grpc.sh
================================================
#!/bin/bash
echo "Packaging python tinode-grpc..."
pushd ./pbx > /dev/null
# Generate grpc bindings from the proto file.
./py-generate.sh v=3
pushd ../py_grpc > /dev/null
# Generate version file from git tags
python3 version.py
# Generate tinode-grpc package
python3 -m build > /dev/null
popd > /dev/null
popd > /dev/null
================================================
FILE: chatbot/LICENSE
================================================
The code in this folder and nested folders is licensed under Apache 2.0
http://www.apache.org/licenses/LICENSE-2.0
================================================
FILE: chatbot/README.md
================================================
# Tinode ChatBot Examples
* [Python chatbot](python/)
* [Karuha](https://github.com/Visecy/Karuha) - third party chatbot framework.
* [C# .Net/.NetCore chatbot](https://github.com/tinode/csharpbot)
================================================
FILE: chatbot/csharp/README.md
================================================
# Tinode Chatbot Example for .Net or .NetCore
Moved to a separate repo: https://github.com/tinode/csharpbot
================================================
FILE: chatbot/python/.gitignore
================================================
.tn-cookie
================================================
FILE: chatbot/python/README.md
================================================
# Tinode Chatbot
This is a simple chatbot for Tinode using [gRPC API](../../pbx/). It's written in Python as a demonstration
that the API is language-independent.
The chat bot subscribes to events stream using Plugin API and logs in to Tinode server as a regular user over gRPC interface (see `grpc_listen` in [tinode.conf](../../server/tinode.conf) file). The event stream API is used to listen for creation of new accounts. When a new account is created, the bot initiates a p2p topic with the new user. Then it listens for messages sent to the topic and responds to each with a random quote from `quotes.txt` file.
Generated files are provided for convenience in a [separate folder](../../py_grpc/tinode_grpc). You may re-generate them if needed:
```
python -m pip install grpcio-tools
python -m grpc_tools.protoc -../../pbx --python_out=. --grpc_python_out=. ../../pbx/model.proto
```
Chatbot expects gRPC binding to be provided as `tinode-grpc`. If you want to use them locally, first copy `model_pb2.py` and `model_pb2_grpc.py` to the same folder as `chatbot.py` then find the lines
```
from tinode_grpc import pb
from tinode_grpc import pbx
```
in `chatbot.py` and replace them with
```
import model_pb2 as pb
import model_pb2_grpc as pbx
```
## Installing and running
### Using PIP
#### Prerequisites
[gRPC](https://grpc.io/) requires [python](https://www.python.org/) 2.7 or 3.4 or higher.
Make sure [pip](https://pip.pypa.io/en/stable/installing/) 9.0.1 or higher is installed.
```
$ python -m pip install --upgrade pip
```
If you cannot upgrade pip due to a system-owned installation, you can run install it in a `virtualenv`:
```
$ python -m pip install virtualenv
$ virtualenv venv
$ source venv/bin/activate
$ python -m pip install --upgrade pip
```
#### Install dependencies:
```
$ python -m pip install -r requirements.txt
```
On El Capitan OSX, you may get the following error:
```
$ OSError: [Errno 1] Operation not permitted: '/tmp/pip-qwTLbI-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/six-1.4.1-py2.7.egg-info'
```
You can work around this using:
```
$ python -m pip install tinode_grpc --ignore-installed
```
### Run the chatbot
Start the [tinode server](../../INSTALL.md) first. Then start the chatbot with credentials of the user you want to be your bot, `alice` in this example:
```
python chatbot.py --login-basic=alice:alice123
```
If you want to run the bot in the background, start it as
```
nohup python chatbot.py --login-basic=alice:alice123 &
```
Run `python chatbot.py -h` for more options.
If you are using python 2, keep in mind that `condition.wait()` [is forever buggy](https://bugs.python.org/issue8844). As a consequence of this bug the bot cannot be terminated with a SIGINT. It has to be stopped with a SIGKILL.
You can use cookie file to store credentials. Sample cookie files are provided as `basic-cookie.sample` and `token-cookie.sample`. Once authenticated the bot will store the token in the cookie file, `.tn-cookie` by default. If you have a cookie file with the desired credentials, you can run the bot with no parameters:
```
python chatbot.py
```
If the server is configured to use TLS, i.e. running as `httpS://my-server.example.com/`, the gRPC endpoint also uses the same SSL certificate. In that case add the `--ssl` option when starting the chatbot. If you want the chatbot to connect to the secure server over a local network or under a different name rather than the `my-server.example.com`, for instance as `localhost`, you must specify the SSL domain name to use, otherwise the server will not be able to find the right SSL certificate:
```
python chatbot.py --host=localhost:16060 --ssl --ssl-host=my-server.example.com
```
Quotes are read from `quotes.txt` by default. The file is plain text with one quote per line.
### Using Docker
**Warning!** Although the chatbot itself is less than 11KB, the chatbot Docker image is 175MB: the `:slim` Python 3 image is about 140MB, gRPC adds another ~30MB.
1. Follow [instructions](../../docker/README.md) to build and run dockerized Tinode chat server up to and including _step 3_.
2. In _step 4_ run the server adding `--env PLUGIN_PYTHON_CHAT_BOT_ENABLED=true` and `--volume botdata:/botdata` to the command line:
1. **RethinkDB**:
```
$ docker run -p 6060:18080 -d --name tinode-srv --env PLUGIN_PYTHON_CHAT_BOT_ENABLED=true --volume botdata:/botdata --network tinode-net tinode/tinode-rethink:latest
```
2. **MySQL**:
```
$ docker run -p 6060:18080 -d --name tinode-srv --env PLUGIN_PYTHON_CHAT_BOT_ENABLED=true --volume botdata:/botdata --network tinode-net tinode/tinode-mysql:latest
```
3. **MongoDB**:
```
$ docker run -p 6060:18080 -d --name tinode-srv --env PLUGIN_PYTHON_CHAT_BOT_ENABLED=true --volume botdata:/botdata --network tinode-net tinode/tinode-mongodb:latest
```
3. Run the chatbot
```
$ docker run -d --name tino-chatbot --network tinode-net --volume botdata:/botdata tinode/chatbot:latest
```
4. Test that the bot is functional by pointing your browser to http://localhost:6060/, login and talk to user `Tino`. The user should respond to every message with a random quote.
You may replace the `:latest` with a different tag. See all available tags here:
* [Tinode-MySQL tags](https://hub.docker.com/r/tinode/tinode-mysql/tags/)
* [Tinode-RethinkDB tags](https://hub.docker.com/r/tinode/tinode-rethink/tags/)
* [Tinode-MongoDB tags](https://hub.docker.com/r/tinode/tinode-mongodb/tags/)
* [Chatbot tags](https://hub.docker.com/r/tinode/chatbot/tags/)
In general try to use docker images all with the same tag.
================================================
FILE: chatbot/python/basic-cookie.sample
================================================
{"schema": "basic", "secret": "alice:alice123"}
================================================
FILE: chatbot/python/chatbot.py
================================================
"""Python implementation of a Tinode chatbot."""
# For compatibility between python 2 and 3
from __future__ import print_function
import argparse
import base64
from concurrent import futures
from datetime import datetime
import json
import os
try:
from importlib.metadata import version
except ImportError:
# Fallback for Python < 3.8
from importlib_metadata import version
import platform
try:
import Queue as queue
except ImportError:
import queue
import random
import signal
import sys
import time
import grpc
from google.protobuf.json_format import MessageToDict
# Import generated grpc modules
from tinode_grpc import pb
from tinode_grpc import pbx
# For compatibility with python2
if sys.version_info[0] >= 3:
unicode = str
APP_NAME = "Tino-chatbot"
APP_VERSION = "1.2.3"
LIB_VERSION = version("tinode_grpc")
# Maximum length of string to log. Shorten longer strings.
MAX_LOG_LEN = 64
# User ID of the current user
botUID = None
# Dictionary wich contains lambdas to be executed when server response is received
onCompletion = {}
# This is needed for gRPC ssl to work correctly.
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
def log(*args):
print(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], *args)
# Add bundle for future execution
def add_future(tid, bundle):
onCompletion[tid] = bundle
# Shorten long strings for logging.
def clip_long_string(obj):
if isinstance(obj, unicode) or isinstance(obj, str):
if len(obj) > MAX_LOG_LEN:
return '<' + str(len(obj)) + ' bytes: ' + obj[:12] + '...' + obj[-12:] + '>'
return obj
elif isinstance(obj, (list, tuple)):
return [clip_long_string(item) for item in obj]
elif isinstance(obj, dict):
return dict((key, clip_long_string(val)) for key, val in obj.items())
else:
return obj
def to_json(msg):
return json.dumps(clip_long_string(MessageToDict(msg)))
# Resolve or reject the future
def exec_future(tid, code, text, params):
bundle = onCompletion.get(tid)
if bundle != None:
del onCompletion[tid]
try:
if code >= 200 and code < 400:
arg = bundle.get('arg')
bundle.get('onsuccess')(arg, params)
else:
log("Error: {} {} ({})".format(code, text, tid))
onerror = bundle.get('onerror')
if onerror:
onerror(bundle.get('arg'), {'code': code, 'text': text})
except Exception as err:
log("Error handling server response", err)
# List of active subscriptions
subscriptions = {}
def add_subscription(topic):
subscriptions[topic] = True
def del_subscription(topic):
subscriptions.pop(topic, None)
def subscription_failed(topic, errcode):
if topic == 'me':
# Failed 'me' subscription means the bot is disfunctional.
if errcode.get('code') == 502:
# Cluster unreachable. Break the loop and retry in a few seconds.
client_post(None)
else:
exit(1)
def login_error(unused, errcode):
# Check for 409 "already authenticated".
if errcode.get('code') != 409:
exit(1)
def server_version(params):
if params == None:
return
log("Server:", params['build'].decode('ascii'), params['ver'].decode('ascii'))
def next_id():
next_id.tid += 1
return str(next_id.tid)
next_id.tid = 100
# Quotes from the fortune cookie file
quotes = []
def next_quote():
idx = random.randrange(0, len(quotes))
# Make sure quotes are not repeated
while idx == next_quote.idx:
idx = random.randrange(0, len(quotes))
next_quote.idx = idx
return quotes[idx]
next_quote.idx = 0
# This is the class for the server-side gRPC endpoints
class Plugin(pbx.PluginServicer):
def Account(self, acc_event, context):
action = None
if acc_event.action == pb.CREATE:
action = "created"
# TODO: subscribe to the new user.
elif acc_event.action == pb.UPDATE:
action = "updated"
elif acc_event.action == pb.DELETE:
action = "deleted"
else:
action = "unknown"
log("Account", action, ":", acc_event.user_id, acc_event.public)
return pb.Unused()
queue_out = queue.Queue()
def client_generate():
while True:
msg = queue_out.get()
if msg == None:
return
log("out:", to_json(msg))
yield msg
def client_post(msg):
queue_out.put(msg)
def client_reset():
# Drain the queue
try:
while queue_out.get(False) != None:
pass
except queue.Empty:
pass
def hello():
tid = next_id()
add_future(tid, {
'onsuccess': lambda unused, params: server_version(params),
})
return pb.ClientMsg(hi=pb.ClientHi(id=tid, user_agent=APP_NAME + "/" + APP_VERSION + " (" +
platform.system() + "/" + platform.release() + "); gRPC-python/" + LIB_VERSION,
ver=LIB_VERSION, lang="EN"))
def login(cookie_file_name, scheme, secret):
tid = next_id()
add_future(tid, {
'arg': cookie_file_name,
'onsuccess': lambda fname, params: on_login(fname, params),
'onerror': lambda unused, errcode: login_error(unused, errcode),
})
return pb.ClientMsg(login=pb.ClientLogin(id=tid, scheme=scheme, secret=secret))
def subscribe(topic):
tid = next_id()
add_future(tid, {
'arg': topic,
'onsuccess': lambda topicName, unused: add_subscription(topicName),
'onerror': lambda topicName, errcode: subscription_failed(topicName, errcode),
})
return pb.ClientMsg(sub=pb.ClientSub(id=tid, topic=topic))
def leave(topic):
tid = next_id()
add_future(tid, {
'arg': topic,
'onsuccess': lambda topicName, unused: del_subscription(topicName)
})
return pb.ClientMsg(leave=pb.ClientLeave(id=tid, topic=topic))
def publish(topic, text):
tid = next_id()
return pb.ClientMsg(pub=pb.ClientPub(id=tid, topic=topic, no_echo=True,
head={"auto": json.dumps(True).encode('utf-8')}, content=json.dumps(text).encode('utf-8')))
def note_read(topic, seq):
return pb.ClientMsg(note=pb.ClientNote(topic=topic, what=pb.READ, seq_id=seq))
def init_server(listen):
# Launch plugin server: accept connection(s) from the Tinode server.
server = grpc.server(futures.ThreadPoolExecutor(max_workers=16))
pbx.add_PluginServicer_to_server(Plugin(), server)
server.add_insecure_port(listen)
server.start()
log("Plugin server running at '"+listen+"'")
return server
def init_client(addr, schema, secret, cookie_file_name, secure, ssl_host):
log("Connecting to", "secure" if secure else "", "server at", addr,
"SNI="+ssl_host if ssl_host else "")
channel = None
if secure:
opts = (('grpc.ssl_target_name_override', ssl_host),) if ssl_host else None
channel = grpc.secure_channel(addr, grpc.ssl_channel_credentials(), opts)
else:
channel = grpc.insecure_channel(addr)
# Call the server
stream = pbx.NodeStub(channel).MessageLoop(client_generate())
# Session initialization sequence: {hi}, {login}, {sub topic='me'}
client_post(hello())
client_post(login(cookie_file_name, schema, secret))
return stream
def client_message_loop(stream):
try:
# Read server responses
for msg in stream:
log(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], "in:", to_json(msg))
if msg.HasField("ctrl"):
# Run code on command completion
exec_future(msg.ctrl.id, msg.ctrl.code, msg.ctrl.text, msg.ctrl.params)
elif msg.HasField("data"):
# log("message from:", msg.data.from_user_id)
# Protection against the bot talking to self from another session.
if msg.data.from_user_id != botUID:
# Respond to message.
# Mark received message as read
client_post(note_read(msg.data.topic, msg.data.seq_id))
# Insert a small delay to prevent accidental DoS self-attack.
time.sleep(0.1)
# Respond with a witty quote
client_post(publish(msg.data.topic, next_quote()))
elif msg.HasField("pres"):
# log("presence:", msg.pres.topic, msg.pres.what)
# Wait for peers to appear online and subscribe to their topics
if msg.pres.topic == 'me':
if (msg.pres.what == pb.ServerPres.ON or msg.pres.what == pb.ServerPres.MSG) \
and subscriptions.get(msg.pres.src) == None:
client_post(subscribe(msg.pres.src))
elif msg.pres.what == pb.ServerPres.OFF and subscriptions.get(msg.pres.src) != None:
client_post(leave(msg.pres.src))
else:
# Ignore everything else
pass
except grpc._channel._Rendezvous as err:
log("Disconnected:", err)
def read_auth_cookie(cookie_file_name):
"""Read authentication token from a file"""
cookie = open(cookie_file_name, 'r')
params = json.load(cookie)
cookie.close()
schema = params.get("schema")
secret = None
if schema == None:
return None, None
if schema == 'token':
secret = base64.b64decode(params.get('secret').encode('utf-8'))
else:
secret = params.get('secret').encode('utf-8')
return schema, secret
def on_login(cookie_file_name, params):
global botUID
client_post(subscribe('me'))
"""Save authentication token to file"""
if params == None or cookie_file_name == None:
return
if 'user' in params:
botUID = params['user'].decode("ascii")[1:-1]
# Protobuf map 'params' is not a python object or dictionary. Convert it.
nice = {'schema': 'token'}
for key_in in params:
if key_in == 'token':
key_out = 'secret'
else:
key_out = key_in
nice[key_out] = json.loads(params[key_in].decode('utf-8'))
try:
cookie = open(cookie_file_name, 'w')
json.dump(nice, cookie)
cookie.close()
except Exception as err:
log("Failed to save authentication cookie", err)
def load_quotes(file_name):
with open(file_name) as f:
for line in f:
quotes.append(line.strip())
return len(quotes)
def run(args):
schema = None
secret = None
if args.login_token:
"""Use token to login"""
schema = 'token'
secret = args.login_token.encode('ascii')
log("Logging in with token", args.login_token)
elif args.login_basic:
"""Use username:password"""
schema = 'basic'
secret = args.login_basic.encode('utf-8')
log("Logging in with login:password", args.login_basic)
else:
"""Try reading the cookie file"""
try:
schema, secret = read_auth_cookie(args.login_cookie)
log("Logging in with cookie file", args.login_cookie)
except Exception as err:
log("Failed to read authentication cookie", err)
if schema:
# Load random quotes from file
log("Loaded {} quotes".format(load_quotes(args.quotes)))
# Start Plugin server
server = init_server(args.listen)
# Initialize and launch client
client = init_client(args.host, schema, secret, args.login_cookie, args.ssl, args.ssl_host)
# Setup closure for graceful termination
def exit_gracefully(signo, stack_frame):
log("Terminated with signal", signo)
server.stop(0)
client.cancel()
sys.exit(0)
# Add signal handlers
signal.signal(signal.SIGINT, exit_gracefully)
signal.signal(signal.SIGTERM, exit_gracefully)
# Run blocking message loop in a cycle to handle
# server being down.
while True:
client_message_loop(client)
time.sleep(3)
client_reset()
client = init_client(args.host, schema, secret, args.login_cookie, args.ssl, args.ssl_host)
# Close connections gracefully before exiting
server.stop(None)
client.cancel()
else:
log("Error: authentication scheme not defined")
if __name__ == '__main__':
"""Parse command-line arguments. Extract server host name, listen address, authentication scheme"""
random.seed()
purpose = "Tino, Tinode's chatbot."
log(purpose)
parser = argparse.ArgumentParser(description=purpose)
parser.add_argument('--host', default='localhost:16060', help='address of Tinode server gRPC endpoint')
parser.add_argument('--ssl', action='store_true', help='use SSL to connect to the server')
parser.add_argument('--ssl-host', help='SSL host name to use instead of default (useful for connecting to localhost)')
parser.add_argument('--listen', default='0.0.0.0:40051', help='address to listen on for incoming Plugin API calls')
parser.add_argument('--login-basic', help='login using basic authentication username:password')
parser.add_argument('--login-token', help='login using token authentication')
parser.add_argument('--login-cookie', default='.tn-cookie', help='read credentials from the provided cookie file')
parser.add_argument('--quotes', default='quotes.txt', help='file with messages for the chatbot to use, one message per line')
args = parser.parse_args()
run(args)
================================================
FILE: chatbot/python/quotes.txt
================================================
login:
$3,000,000
1 bulls, 3 cows
A Cray is the best machine for simulating the performance of a Cray.
A consistent indentation style is the hobgoblin of little minds.
A gentleman is one who is never rude unintentionally. -Noel Coward
A man must destroy himself before others can destroy him. -Mong Tse
A philosopher does not need a torch to gather glow-worms by at mid-day. --Earnest Bramah
A song in time is worth a dime.
A woman is only a woman, but a good cigar is a smoke. -Rudyard Kipling
Admiration is our polite recognition of another's resemblance to ourselves.
All the good ones are taken.
An atheist is a man with no invisible means of support.
Any country with "democratic" in the title isn't.
Are we not men?
Attend winter sheep meetings. Learning never ends!
Be the sea, and see me be.
Beware: the light at the end of the tunnel may be New Jersey.
Bubble bubble, toil and trouble; cast that float into a double.
C'est dommage, mais c'est vrai.
Caution: Do not view laser light with remaining eye.
Cogito cogito ergo cogito sum.
Crazee Edeee, his prices are INSANE!!!
Death to all fanatics!
Disk crisis, please clean up!
Do not meddle in the mouth.
Don't be overly suspicious where it's not warranted.
Don't let your thoughts get in a rut. The knife which spreads may also cut.
Don't worry if it doesn't work right; if everything did, you'd be out of a job.
E Pluribus Unix.
Either we are alone or we are not. Either way is mind-boggling.
Even these days, it's not as easy to go crazy as you think.
Everybody should believe in something -- I believe I'll have another drink.
Exercise is the Yuppie version of bulemia.
Far too noisy, my dear Mozart. Far too many notes. -Emperor Ferdinand.
First things first. Why not send for the Nazis right now.
Fortran est; non potest legi.
Generalizations are useful. The work contained in them can be reckoned as labor
God does not play dice.
Good day to avoid cops. Crawl to work.
Great shot, kid. That was one in a million.
Have you done your Christmas chopping yet? -anon. White House Advisor 12/24/81
He was al coltissh, ful of ragerye,/And ful of jargon as a flekke pye. -Chaucer
He who listens last is the last one listening.
History is a race between education and catastrophe. -H. G. Wells
How do I love thee? Hand me my calculator...
I am a high-pressure guy, and I didn't take this job to conduct a going-out-of-business sale. - A.A. Penzias
I don't even know what street Canada is on. - Al Capone
I have the most perfect confidence in your indiscretion.
I must have slipped a disk; my pack hurts.
I think that I shall never see a billboard as lovely as a tree. -Ogden Nash
I'd rather have my mail delivered by Lockheed than ride in a plane built by the Post Office.
IOT trap -- core dumped
If I had to choose between System V and 4.2, I'd resign. - Peter Honeyman
If butterflies had teeth like tigers they would never make it out of the hangar.
If it's not broken, don't fix it.
If the shoe fits, buy the other one, too.
If you don't care where you are, then you ain't lost.
If you take the last cup, make a new pot.
If your experiment needs statistics, you ought to have done a better experiment. - E. Rutherford
In challenging a kzin, a simple scream of rage is sufficient.
In this world, truth can wait; she's used to it.
It is better to have loved and lost than just to have lost.
It is useless to put on your brakes when you're upside down. -Paul Newman
It's a small world, but I'd hate to have to paint it.
It's hard to love someone who looks down on you because your hands get bloody protecting him.
It's not the time between the takes that takes the time - it's changing your mind between the takes that takes the time. -Stage Dept.
Join me and I will complete your training.
Lando's not a system, he's a man. He's a gambler, scoundrel. You'd like him.
Let sleeping wraiths lie.
Like winter snow on summer lawn, time past is time gone.
Lose a few, lose a few.
Macro context switch under way, please do not log out!
Man is in doubt to deem himself a god or beast. -Alexander Pope
May you live all the days of your life.
Mind your own business, Spock. I'm sick of your halfbreed interference.
Multilevel standards are like onions. They're smelly and make you cry a lot. -Ron Natalie
Never attribute to malice what can be found in scientific american, under computer recreations.
New career ideas are worth pursuing.
No man's life, liberty or property are safe while the legislature is in session.
Non serviam.
Nothing of interest ever happened on this day.
Oh, so there you are!
One scythe fits all.
Otto's too so-so to toss soot, too sot to toot SOSs. So?
People get it into their heads that this is a democracy. Well it isn't. -gwl
Personality is a flimsy thing on which to build an art. -John Cage
Populus vult decipi.
Promptness is its own reward, if one lives by the clock instead of the sword.
Real programmers can't say `lint' without adding `hbaxcu' -Wm Leler
Remember the Unknown Buffalo.
Rog-O-Matic callidus est.
Say "no" to long-sleeved shirts!! Support your right to bare arms!
Señor, if you hurry from here, you will wait longer there. -Mexico taxi driver
Smoked carp is terrible unless you're out of smoked salmon.
Some men are discovered; others are found out.
Specialization is for insects. -Robt. A. Heinlein
Stop searching. Happiness is right next to you.
System going down for 5 minutes -- back up in barrels.
Technological unemployment is total today -- 300 years ago we were all farmers.
That's the nice thing about standards -- there's so many to choose from. -trb
The Luddites always lose. Always.
The average legislator is somewhere nearly all the time. -Herb Nore
The dawning of the Information Age is bringing about dramatic changes in the fundamental fabric of our civilization. - AA Penzias
The first piece of luggage out of the chute doesn't belong to anyone, ever.
The hippo has no sting, but the wise man would rather be sat upon by the bee.
The meek shall inherit the earth -- they are pronounced "o".
The one interesting fact about the Diplodocus is that the accent is on the second syllable.
The plural of spouse is spice.
The skeletons in the cupboard will all come out in the wash.
The universe is laughing behind your back.
Them that dishes it out need not fall over every time someone blows hard.
There is no fear in love; but perfect love casteth out fear.
There'll always be an England - if not it would be necessary to invent one.
These widows, sir, are the most perverse creatures in the world. - Joseph Addison.
Things will be bright in P.M. A cop will shine a light in your face.
This space available. Call 686-7600 for details.
Those who in quarrels interpose must often wipe a bloody nose.
To be is to be related.
To play billiards well is a sign of an ill-spent youth.
Toto, I've a feeling we're not in any immediate danger of having just committed suicide!
Try a new system or a different approach.
Uneasy lies the head that wears a crown.
Veni, vidi, maeni, mo, cacha tigrem baedas to, iffi hollers, ledem go, veni, vidi, maeni, mo.
Warning: this fortune may change your life.
We don't know half of what we know.
We retard what we cannot repel, we palliate what we cannot cure. -Johnson
We're too close to System Test.
What garlic is to salad, insanity is to art.
When all else fails, read the instructions.
When in trouble or in doubt, run in circles; scream and shout.
Whenever I see his fingernails, I thank God I don't have to look at his feet.
Whom the gods must destroy they first must drive insane.
Without alkaloids, life itself would be impossible.
Yes, the red switch.
You can tell a man by the company that keeps him.
You cannot buy beer; you can only rent it.
You have bills.
You look strong enough to pull the ears off a Gundark!
You should go home.
You will never find a more wretched hive of scum and villainy.
Your computer account is overdrawn. Please reauthorize.
Zero is greater than minus zero, but don't ask by how much. -6600 ref. manual
`is false when preceded by its quotation' is false when preceded by its quotation.
fortune: not found
pic: 5 X 58008 picture shrunk to 0.000603365 X 7
uuxqt cmd (rnews ) status (ucsfcgl!uucp 256)
In days of yore, the crab and the crayfish lived in the forest.
* Method As described above, see details below.
Whatever happens, happens because it must.
Boost, don't knock
pass 2 error:(file ) more than 100 args?
Nepal premier won't resign.
init: /dev/console: getty failing, sleeping
Sentence without verb.
That's the way I got promoted, by eating everything. -pjw
Pittsburgh has become a kind of knowledge aircraft carrier, its "top-guns" scattered regularly around the planet.
When the music stops, the house of cards collapses and the emperor is found to be wearing no clothes.
Never put snow on a frostbitten part.
Put a smoke detector in your vacation cottage.
Draw up a family fire-escape plan.
A mathematician is a machine for turning coffee into theorems. -Paul Erdös
I'm TRYING to be a back end! - A Hume
There are only 26 calls and most of them are trivial.
162 is unimplemented
Incest more common than thought in United States
The product classroom is marked pass-fail.
ISDN is real and implementable.
To dissimulate is to feign not to have what one has. - J Baudrillard
Vacuums are nothings. We only mention them to let them know we know they're there.
Two wrongs don't make a right; three lefts do.
?12 Machine check during machine check.
The downside of having an architecture is wart-for-wart compatibility. - Bob Willard, DEC
It doesn't matter if you don't know how your program works, so long as it's parallel -R. O'Keefe
sendmail[94] AA00493: SYSERR: net hang reading from coma: Connection timed out during greeting wait with coma
Performance doesn't matter if your product is sufficiently feature-rich. --SF system engineer
PLEASE LOG IN TO 3B20'S AT 4800 BAUD.
SQUASH, do not crush (seen on a vegetable crate)
If you get to meet sufficently important people, it's ok to debase yourself. -pjw
The system is ready.
A watermelon will not ripen in your armpit.
Contrary to English and other similar languages, Turkish can be hyphenated with a simple 4 state finite-state machine.
nop...session...attach...clone...walk...open...
Spare me your sorrow's tears.
He is one inch good, one foot evil.
Heaven cannot use two suns or a house two masters.
To give ground is sometimes the best victory.
No word can cut kindness.
Let wisdom and virtue be the two wheels of your cart.
Willow branches never snap under the weight of snow.
Only a monkey tries to catch the full moon in the pond.
Don't lug dirt to a hilltop.
Don't paint on water or carve on ice.
The nail that raises its head is hammered down.
Who can tell the he-crow from his mate?
He is wise who knows what is enough.
His hand was bitten by his own dog.
To kill a general first shoot his horse.
What is left unsaid is rich as flowers.
If you are in a hurry go round-about.
Better be ignorant than mistaught.
You can't judge widows or horses without handling them.
Don't use the ox-cleaver to kill a hen.
Two hearts: and only one body.
Bread is better than blossoms.
Good medicine has often a bitter smack.
First among blossoms the cherry: among men the warrior.
You can't wrap up the wind or tie down the shadow.
You cannot live in the same world with your father's murderer.
Even a starving hawk won't lower himself to eat corn.
Keep your mouth shut, your eyes open.
Some ride in palanquins, some bear palanquins: some weave sandals for palanquin-bearers.
Tuning filesystem for rot 0...
Two things will make you lose your earrings, and one of them's dancing. -Bonnie Raitt.
When the humans are away, the monkeys enter the hut, eat up the maize, and rearrange the furniture.
The problem is not getting ksh to execute any particular command, the problem is recognizing that there might be a problem.
diff: usage diff [whatever] etc.
Intense opportunities for reorganization
Stringent ideas in the upper echelon mind
Leveraged brainpower at the labs
>>>>>>> REMOVE ALL YOUR FILES AND DIRECTORIES NOW! <<<<<<<
UX:lp: ERROR: Can't establish contact with the LP print service.
Awk is one of the world's greatest collections of surprises. -Doug McIlroy
If you think awk is the perfect programming language for the problem, you don't understand the problem yet. -Rob Pike
``Workers of the World, forgive us!'' (a banner in a Moscow counter-rally, Oct 8, 1989)
The first step is to determine what the remaining steps are. -Mark Horton
If you do something stupid on UNIX you generally get strange behavior. -Doug Gwyn
I'd still like you to explain that worm to me - Judge Munson to Robert T. Morris
Like raisins in a bread pudding, the moments lie within the body of Henry.
A real gentleman never takes bases unless he really has to.
There are two proteins involved in DNA synthesis, they are called DNAsynthase 1 and DNAsynthase 3.
Drawing on my fine command of the English language, I said nothing. -Robert Benchley
Hay, be seedy! He-effigy, hate-shy jaky yellow man, oh peek, you are rusty, you've edible, you ex-wise he!
The FSF is not overly concerned about security. - FSF
Make: Don't know how your program works
OSIfy, v.: To make code impenetrable.
Rule 3: If the character is comprised of a container without another radical, then
An economic reality of our time: computerized job deskilling. - a book review in Science
Estne ebriamen de furfure avenaceo factum?
The isomer with the higher dipole moment has the higher physical constants, regardless of the heat content. Van Arkel Rule
Other factors being equal, the metal which is most susceptible to failure is that with the lowest boiling point. Mogro-Campero Rule
The solid particle erosion rate of annealed face-centered cubic metals is inversely related to their hardness. Finnie-Wolak-Kabil Rule
In dichroic crystals, the faster ray is less absorbed. Babinet Rule
If you can't stand the heat, get a pool.
A rolling stone is a singing rock group.
UX:mail: INFO: No mail.
Real software has its own 800 support line. - Stu Feldman
Marine math: 2 beers times 39 Marines is 49 cases.
When times are bad, people feel compelled to overeat.
Of the physical pages in use, 3436738592 pages are permanently allocated to VMS.
If you carry on, head-to-head, on `solution' you don't get anywhere
This is like ignoring both the speed limit and the odometer in your car. It won't get you far. -Kenneth P. Birman
/bin/ls: exec header invalid
My pile of equipment is bigger than your pile of equipment -- philw
Connected to 192.65.218.43.
as1: Error: ../bpvvv.c, line 1324: Too many float literals--compile with "-Wb,-nopool"
The helpful thought for which you look/Is written somewhere in a book.
Is the tool broadly supported or maintained?
I'm pulling *something* here. - Dom Marotta
I'm drawing a line under the sand. - John Major
They bit the wrong chicken's head off with their own teeth and got blood all over their shirt - nls
I just want a bare-boned, straight EMACS. - Rae McLellan
*** Message content is not printable: delete, write or save it to a file ***
1181258 SAVECORE SEGMENTATION FAULT WHILE TRYING TO SAVE CORE
Everything that can ever be invented has been invented -Charles H. Duell,
Dan Quayle addressing the United Negro College Fund
Here in Nuremberg, information hiding is much more popular in the organization than it is in the software...
License Error : The license for this product(SPARCompiler C) has expired
My favorite thing about the Internet is that you get to go into the private world of real creeps without having to smell them. - Penn Jillette
Music is the pleasure the human soul experiences from counting without being aware that it is counting. -Leibnitz
Millenium parties/with loud music, lights and joy;/then, how cold!
warning: Hit heuristic-fence-post without finding enclosing function for address 0xfa2e470
War in the Clinton era is just P.R. by other means. - Michael Hirschorn
Application Developer's Architecture Guide
! TeX capacity exceeded, sorry [main memory size=263001].
His hand was bitten by his own side in an offhand way. - Mark V. Shaney
Sperm bank sued for tossing samples - Headline in Star-Ledger
boot: nop...cfs...session...no physical memory
Computers come in putty-colored boxes and have AUTOEXEC.BAT files and run screen-savers with flying toasters, and brains do not. - Steven Pinker
We have discovered a pervasive nonstationarity.
verb = a > b ? 'a': c > d ? 'd': 'c';
It's not only stupid it's wrong!
Lucky Numbers 12, 14, 19, 24, 36, 43
The goal of the experimental trials with the artificial heart is to "double the life span of these patients" to 60 days, Lederman said.
canlock: corrupted 0xcafebabe
Devil Duckie, when you float, it's like I'm bathing in a flaming moat!
He lost both Ali-pilaf and Vali-pilaf.
He is a man who gives bread.
We cut salt and bread together.
You can't make stew with cheap meat.
Absquotilate it in style, you old skunk,..and show the gentlemen what you can do.
Cookie import from '/usr/dhog/lib/cookies' failed: fil
Fortunately, Mac OS X supports *that* feature! - Brendan Connell
The full documentation for cat is maintained as a Texinfo manual. If the info and cat programs are properly installed at your site, the command "info cat" should give you access to the complete manual.
That's nothing.. RMS sent me a .doc file the other day...
Notegroups! They kill you!
Welcome, rqzgc_mfuv8
Isn't it funny how people say they'll never grow up to be their parents, then one day they look in the mirror and they're moving aircraft carriers into the Gulf region? - the Onion
Where would Christianity be if Jesus got eight to fifteen years, with time off for good behavior? -- New York Senator James H. Donovan on capital punishment.
Few, few the bird make her nest.
To become of bishop miller.
The dress don't make the monk.
He is mad to bind.
He turns as a weath turcocl.
After the paunch comes the dance.
Linux programmer's mind: don't invalidate the D-cache unless it wasn't enabled
Practically noiseless and impossible to explode. - ad for the 1897 Oldsmobile
The essence of XML is this: the problem it solves is not hard, and it does not solve the problem well. - Phil Wadler, POPL 2003
If you are idle for more than 1000 hours, the system will log you out. Please save reviews frequently.
Setting up your SIP account will allow you to call both other SIP users as well as pstn phones. - Wim Sweldens
What's grey? A melted penguin.
Linux: the world's best text adventure game.
i know what jmk did. he added reentrancy for threads. - boyd, about uintptr
I recently visited plan9.bell-labs.com/wiki/plan9 and I would like to offer my web design services. I can help you with your Plan 9 website. - spam
gcc is the holy cow of compilers, not the holy grail. - forsyth
we live in a world of dogmas, probably trying to fight it with a stronger dogma is a bad idea, but trying to fight it with preumpted objectivism clearly doesn't work. - uriel
Lucent is the best place 4 me to work. (Please keep confidential)
Subject: There's More to Nevada Than You Think
/* keep the code below somewhat more readonable; not used elsewhere */
swapon: /dev/disk/by-uuid/928aaabd-5744-4f0a-b9ef-aa101f286514: Invalid argument - Linux
I guess the idea is that ... even though the structures are all different.
rusty: kernel pseudo files are not the place for chit-chat
================================================
FILE: chatbot/python/requirements.txt
================================================
futures>=3.2.0; python_version<'3'
grpcio>=1.40.0
tinode-grpc>=0.20.0b3
importlib-metadata>=1.0; python_version<'3.8'
================================================
FILE: chatbot/python/setup.py
================================================
import setuptools
from subprocess import Popen, PIPE
with open('README.md', 'r') as fh:
long_description = fh.read()
setuptools.setup(
name="tinode-chatbot",
version=git_version(),
author="Tinode Authors",
author_email="info@tinode.co",
description="Tinode demo chatbot.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/tinode/chat",
packages=setuptools.find_packages(),
install_requires=['grpcio>=1.40.0'],
classifiers=(
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache 2.0",
"Operating System :: OS Independent",
),
)
def git_version():
try:
p = Popen(['git', 'describe', '--tags'],
stdout=PIPE, stderr=PIPE)
p.stderr.close()
line = p.stdout.readlines()[0]
return line.strip()
except:
return None
================================================
FILE: chatbot/python/token-cookie.sample
================================================
{"schema": "token", "secret": "mtXWlt9ERZCKsw9aFAABAFGGCnxinE8ruLE21t6SQfck4uBKCIy44kerjmOh4h1+", "expires": "2017-11-18T04:14:02Z"}
================================================
FILE: docker/README.md
================================================
# Using Docker to run Tinode
All images are available at https://hub.docker.com/r/tinode/
1. [Install Docker](https://docs.docker.com/install/) 1.8 or above. The provided dockerfiles are dependent on [Docker networking](https://docs.docker.com/network/) which may not work with the older Docker.
2. Create a bridge network. It's used to connect Tinode container with the database container.
```
$ docker network create tinode-net
```
3. Decide which database backend you want to use: MySQL, PostgreSQL, MongoDB or RethinkDB. Run the selected database container, attaching it to `tinode-net` network:
1. **MySQL**: If you've decided to use MySQL backend, run the official MySQL Docker container:
```
$ docker run --name mysql --network tinode-net --restart always --env MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql:5.7
```
See [instructions](https://hub.docker.com/_/mysql/) for more options. MySQL 5.7 or above is required.
2. **PostgreSQL**: If you've decided to use PostgreSQL backend, run the official PostgreSQL Docker container:
```
$ docker run --name postgres --network tinode-net --restart always --env POSTGRES_PASSWORD=postgres -d postgres:13
```
See [instructions](https://hub.docker.com/_/postgres/) for more options. PostgresSQL 13 or above is required.
The name `rethinkdb`, `mysql`, `mongodb` or `postgres` in the `--name` assignment is important. It's used by other containers as a database's host name.
3. **MongoDB**: If you've decided to use MongoDB backend, run the official MongoDB Docker container and initialise it as single node replica set (you can change "rs0" if you wish):
```
$ docker run --name mongodb --network tinode-net --restart always -d mongo:latest --replSet "rs0"
$ docker exec -it mongodb mongosh
# And inside mongo shell:
> rs.initiate( {"_id": "rs0", "members": [ {"_id": 0, "host": "mongodb:27017"} ]} )
> quit()
```
See [instructions](https://hub.docker.com/_/mongo/) for more options. MongoDB 4.2 or above is required.
4. **RethinkDB**: If you've decided to use RethinkDB backend, run the official RethinkDB Docker container:
```
$ docker run --name rethinkdb --network tinode-net --restart always -d rethinkdb:2.3
```
See [instructions](https://hub.docker.com/_/rethinkdb/) for more options.
4. Run the Tinode container for the appropriate database:
1. **MySQL**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-mysql:latest
```
2. **PostgreSQL**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-postgres:latest
```
3. **MongoDB**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-mongodb:latest
```
4. **RethinkDB**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-rethinkdb:latest
```
You can also run Tinode with the `tinode/tinode` image (which has all of the above DB adapters compiled in). You will need to specify the database adapter via `STORE_USE_ADAPTER` environment variable. E.g. for `mysql`, the command line will look like
```
$ docker run -p 6060:6060 -d -e STORE_USE_ADAPTER mysql --name tinode-srv --network tinode-net tinode/tinode:latest
```
See [below](#supported-environment-variables) for more options.
The port mapping `-p 5678:1234` tells Docker to map container's port 1234 to host's port 5678 making server accessible at http://localhost:5678/. The container will initialize the database with test data on the first run.
You may replace `:latest` with a different tag. See all all available tags here:
* [MySQL tags](https://hub.docker.com/r/tinode/tinode-mysql/tags/)
* [PostgreSQL tags](https://hub.docker.com/r/tinode/tinode-postgresql/tags/) (beta version)
* [MongoDB tags](https://hub.docker.com/r/tinode/tinode-mongodb/tags/)
* [RethinkDB tags](https://hub.docker.com/r/tinode/tinode-rethink/tags/)
* [All bundle tags](https://hub.docker.com/r/tinode/tinode/tags/)
5. Test the installation by pointing your browser to [http://localhost:6060/](http://localhost:6060/).
## Optional
### External config file
The container comes with a built-in config file which can be customized with values from the environment variables (see [Supported environment variables](#supported_environment_variables) below). If changes are extensive it may be more convenient to replace the built-in config file with a custom one. In that case map the config file located on your host (e.g. `/users/jdoe/new_tinode.conf`) to container (e.g. `/tinode.conf`) using [Docker volumes](https://docs.docker.com/storage/volumes/) `--volume /users/jdoe/new_tinode.conf:/tinode.conf` then instruct the container to use the new config `--env EXT_CONFIG=/tinode.conf`:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net \
--volume /users/jdoe/new_tinode.conf:/tinode.conf \
--env EXT_CONFIG=/tinode.conf \
tinode/tinode-mysql:latest
```
When `EXT_CONFIG` is set, most other environment variables are ignored. Consult [the table](#supported-environment-variables) below for a full list.
### Resetting or upgrading the database
The database schema may change from time to time. An error `Invalid database version 101. Expected 103` means the schema has changed and needs to be updated, in this case from version 101 to version 103. You need to either reset or upgrade the database to continue:
Shut down the Tinode container and remove it:
```
$ docker stop tinode-srv && docker rm tinode-srv
```
then repeat step 4 adding `--env RESET_DB=true` to reset or `--env UPGRADE_DB=true` to upgrade.
Also, the database is automatically created if missing.
### Enable push notifications
Tinode uses Google Firebase Cloud Messaging (FCM) to send pushes.
Follow [instructions](../docs/faq.md#q-how-to-setup-fcm-push-notifications) for obtaining the required FCM credentials.
* Download and save the [FCM service account credentials](https://cloud.google.com/docs/authentication/production) file.
* Obtain values for `apiKey`, `messagingSenderId`, `projectId`, `appId`, `messagingVapidKey`.
Assuming your Firebase credentials file is named `myproject-1234-firebase-adminsdk-abc12-abcdef012345.json`
and it's saved at `/Users/jdoe/`, web API key is `AIRaNdOmX4ULR-X6ranDomzZ2bHdRanDomq2tbQ`, Sender ID `141421356237`,
Project ID `myproject-1234`, App ID `1:141421356237:web:abc7de1234fab56cd78abc`, VAPID key (a.k.a. "Web Push certificates")
is `83_Or_So_Random_Looking_Characters`, start the container with the following parameters (using MySQL container as an example):
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net \
-v /Users/jdoe:/config \
--env FCM_CRED_FILE=/config/myproject-1234-firebase-adminsdk-abc12-abcdef012345.json \
--env FCM_API_KEY=AIRaNdOmX4ULR-X6ranDomzZ2bHdRanDomq2tbQ \
--env FCM_APP_ID=1:141421356237:web:abc7de1234fab56cd78abc \
--env FCM_PROJECT_ID=myproject-1234 \
--env FCM_SENDER_ID=141421356237 \
--env FCM_VAPID_KEY=83_Or_So_Random_Looking_Characters \
tinode/tinode-mysql:latest
```
### Configure video calling
Tinode uses [WebRTC](https://webrtc.org/) for video and audio calls. WebRTC needs [Interactive Communication Establishment (ICE)](https://tools.ietf.org/id/draft-ietf-ice-rfc5245bis-13.html) [TURN(S)](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) and/or [STUN](https://en.wikipedia.org/wiki/STUN) servers to traverse [NAT](https://en.wikipedia.org/wiki/Network_address_translation), otherwise calls may not work. Tinode does not include TURN(S) or STUN out of the box. You need to obtain and configure your own service. Once you setup your TURN(S) and/or STUN service, save its configuration to a file, for example `/Users/jdoe/turn-config.json` and provide path to this file when starting the container:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net \
-v /Users/jdoe:/config \
--env ICE_SERVERS_FILE=/config/turn-config.json \
< ... other config parameters ... >
tinode/tinode-mysql:latest
```
The config file uses the following format:
```json
[
{
"urls": [
"stun:stun.example.com"
]
},
{
"username": "user-name-to-use-for-authentication-with-the-server",
"credential": "your-password",
"urls": [
"turn:turn.example.com:80?transport=udp",
"turn:turn.example.com:3478?transport=tcp",
"turns:turn.example.com:443?transport=tcp",
]
}
]
```
[XIRSYS](https://xirsys.com/) offers a free tier for developers. We are in no way affiliated with XIRSYS. We do not endorse or otherwise take any responsibility for your use of their services.
### Run the chatbot
See [instructions](../chatbot/python/).
The chatbot password is generated only when the database is initialized or reset. It's saved to `/botdata` directory in the container. If you want to keep the data available between container changes, such as image upgrades, make sure the `/botdata` is a mounted volume (i.e. you always launch the container with `--volume botdata:/botdata` option).
## Supported environment variables
You can specify the following environment variables when issuing `docker run` command:
| Variable | Type | Default | Purpose |
| --- | --- | --- | --- |
| `ACC_GC_ENABLED`[^2] | bool | `false` | Enable/diable automatic deletion of unfinished account registrations. |
| `AUTH_TOKEN_KEY`[^2] | string | `wfaY2RgF2S1OQI/ZlK+LSrp1KB2jwAdGAIHQ7JZn+Kc=` | base64-encoded 32 random bytes used as salt for authentication tokens. |
| `AWS_ACCESS_KEY_ID`[^2] | string | | AWS Access Key ID when using `s3` media handler. |
| `AWS_CORS_ORIGINS`[^2] | string | `["*"]` | Allowed origins ([CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)) URL for downloads. Generally use your server URL and its aliases. |
| `AWS_REGION`[^2] | string | | AWS Region when using `s3` media handler |
| `AWS_S3_BUCKET`[^2] | string | | Name of the AWS S3 bucket when using `s3` media handler. |
| `AWS_S3_ENDPOINT`[^2] | string | | An endpoint URL (hostname only or fully qualified URI) to override the default endpoint; can be of any S3-compatible service, such as `minio-api.x.io` |
| `AWS_SECRET_ACCESS_KEY`[^2] | string | | AWS [Secret Access Key](https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/) when using `s3` media handler. |
| `CLUSTER_SELF` | string | | Node name if the server is running in a Tinode cluster. |
| `DEBUG_EMAIL_VERIFICATION_CODE`[^2] | string | | Enable dummy email verification code, e.g. `123456`. Disabled by default (empty string). |
| `DEFAULT_COUNTRY_CODE`[^2] | string | `US` | 2-letter country code to assign to sessions by default when the country isn't specified by the client explicitly and it's impossible to infer it. |
| `EXT_CONFIG`[^1] | string | | Path to external config file to use instead of the built-in one. If this parameter is used, most other variables are ignored[^1]. |
| `EXT_STATIC_DIR` | string | | Path to external directory containing static data (e.g. Tinode Webapp files). |
| `FCM_CRED_FILE`[^2] | string | | Path to JSON file with FCM server-side service account credentials which will be used to send push notifications. |
| `FCM_API_KEY` | string | | Firebase API key; required for receiving push notifications in the web client. |
| `FCM_APP_ID` | string | | Firebase web app ID; required for receiving push notifications in the web client. |
| `FCM_PROJECT_ID` | string | | Firebase project ID; required for receiving push notifications in the web client. |
| `FCM_SENDER_ID` | string | | Firebase FCM sender ID; required for receiving push notifications in the web client. |
| `FCM_VAPID_KEY` | string | | Also called 'Web Client certificate' in the FCM console; required by the web client to receive push notifications. |
| `FCM_INCLUDE_ANDROID_NOTIFICATION`[^2] | boolean | true | If true, pushes a data + notification message, otherwise a data-only message. [More info](https://firebase.google.com/docs/cloud-messaging/concept-options). |
| `FCM_MEASUREMENT_ID` | string | | Google Analytics ID of the form `G-123ABCD789`. |
| `FS_CORS_ORIGINS`[^2] | string | `["*"]` | Cors origins when media is served from the file system. See `AWS_CORS_ORIGINS` for details. |
| `ICE_SERVERS_FILE`[^2] | string | | Path to JSON file with configuration of ICE servers to be used for video calls. |
| `MEDIA_HANDLER`[^2] | string | `fs` | Handler of large files, either `fs` or `s3`. |
| `MYSQL_DSN`[^2] | string | <code>'root@tcp(mysql)/tinode?​parseTime=true​&collation=utf8mb4_0900_ai_ci'</code> | MySQL [DSN](https://github.com/go-sql-driver/mysql#dsn-data-source-name). |
| `PLUGIN_PYTHON_CHAT_BOT_ENABLED`[^2] | bool | `false` | Enable calling into the plugin provided by Python chatbot. |
| `POSTGRES_DSN`[^2] | string | <code>'postgresql://postgres:postgres@​localhost:5432/tinode?​sslmode=disable​&connect_timeout=10'</code> | PostgreSQL [DSN](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING). |
| `RESET_DB` | bool | `false` | Drop and recreate the database. |
| `SAMPLE_DATA` | string | _see comment →_ | File with sample data to load. Default `data.json` when resetting or generating new DB, none when upgrading. Use `` (empty string) to disable. |
| `SMTP_AUTH_MECHANISM`[^2] | string | `"plain"` | SMTP authentication mechanism to use; one of "login", "cram-md5", "plain". |
| `SMTP_DOMAINS`[^2] | string | | White list of email domains; when non-empty, accept registrations with emails from these domains only (email verification). |
| `SMTP_HELO_HOST`[^2] | string | _see comment →_ | FQDN to use in SMTP HELO/EHLO command; if missing, the hostname from `SMTP_HOST_URL` is used. |
| `SMTP_HOST_URL`[^2] | string | `'http://localhost:6060/'` | URL of the host where the webapp is running (email verification). |
| `SMTP_LOGIN`[^2] | string | | Optional login to use for authentication with the SMTP server (email verification). |
| `SMTP_PASSWORD`[^2] | string | | Optional password to use for authentication with the SMTP server (email verification). |
| `SMTP_PORT`[^2] | number | | Port number of the SMTP server to use for sending verification emails, e.g. `25` or `587`. |
| `SMTP_SENDER` | string | | [RFC 5322](https://tools.ietf.org/html/rfc5322) email address to use in the `FROM` field of verification emails, e.g. `'"John Doe" <jdoe@example.com>'`. |
| `SMTP_SERVER`[^2] | string | | Name of the SMTP server to use for sending verification emails, e.g. `smtp.gmail.com`. If SMTP_SERVER is not defined, email verification will be disabled. |
| `STORE_USE_ADAPTER`[^2] | string | | DB adapter name (specify with `tinode/tinode` container only). |
| `TEL_HOST_URL`[^2] | string | `'http://localhost:6060/'` | URL of the host where the webapp is running for phone verification. |
| `TEL_SENDER`[^2] | string | | Sender name to pass to SMS sending service. |
| `TLS_CONTACT_ADDRESS`[^2] | string | | Optional email to use as contact for [LetsEncrypt](https://letsencrypt.org/) certificates, e.g. `jdoe@example.com`. |
| `TLS_DOMAIN_NAME`[^2] | string | | If non-empty, enables TLS (http**s**) and configures domain name of your container, e.g. `www.example.com`. In order for TLS to work you have to expose your HTTPS port to the Internet and correctly configure DNS. It WILL FAIL with `localhost` or unroutable IPs. |
| `TNPG_AUTH_TOKEN` | string | | Tinode Push Gateway authentication token. |
| `TNPG_ORG`[^2] | string | | Tinode Push Gateway organization name as registered at https://console.tinode.co |
| `UID_ENCRYPTION_KEY`[^2] | string | `la6YsO+bNX/+XIkOqc5Svw==` | base64-encoded 16 random bytes used as an encryption key for user IDs. |
| `UPGRADE_DB` | bool | `false` | Upgrade database schema, if necessary. |
| `WAIT_FOR` | string | | If non-empty, waits for the specified database `host:port` to be available before starting the server. |
[^1]: If set, variables marked with the footnote `[2]` are ignored.
[^2]: Ignored if `EXT_CONFIG` is set.
A convenient way to generate a desired number of random bytes and base64-encode them on Linux and Mac:
```
$ openssl rand -base64 <desired length>
```
## Metrics Exporter
See [monitoring/exporter/README](../monitoring/exporter/README.md) for information on the Exporter.
Container is also available as a part of the Tinode docker distribution: `tinode/exporter`.
Run it with
```
$ docker run -p 6222:6222 -d --name tinode-exporter --network tinode-net \
--env SERVE_FOR=<prometheus|influxdb> \
--env TINODE_ADDR=<tinode metrics endpoint> \
... <monitoring service specific vars> \
tinode/exporter:latest
```
Available variables:
| Variable | Type | Default | Function |
| --- | --- | --- | --- |
| `SERVE_FOR` | string | `` | Monitoring service: `prometheus` or `influxdb` |
| `TINODE_ADDR` | string | `http://localhost/stats/expvar/` | Tinode metrics path |
| `INFLUXDB_VERSION` | string | `1.7` | InfluxDB version (`1.7` or `2.0`) |
| `INFLUXDB_ORGANIZATION` | string | `org` | InfluxDB organization |
| `INFLUXDB_PUSH_INTERVAL` | int | `60` | Exporter metrics push interval in seconds |
| `INFLUXDB_PUSH_ADDRESS` | string | `https://mon.tinode.co/intake` | InfluxDB backend url |
| `INFLUXDB_AUTH_TOKEN` | string | `` | InfluxDB auth token |
| `PROM_NAMESPACE` | string | `tinode` | Prometheus namespace |
| `PROM_METRICS_PATH` | string | `/metrics` | Exporter webserver path that Prometheus server scrapes |
================================================
FILE: docker/chatbot/Dockerfile
================================================
# Dockerfile builds an image with a chatbot (Tino) for Tinode.
FROM python:3.13-slim
ARG VERSION=0.25
ARG LOGIN_AS=
ARG TINODE_HOST=tinode-srv:16060
ENV VERSION=$VERSION
ARG BINVERS=$VERSION
LABEL maintainer="Tinode Team <info@tinode.co>"
LABEL name="TinodeChatbot"
LABEL version=$VERSION
RUN mkdir -p /usr/src/bot
WORKDIR /usr/src/bot
# Volume with login cookie. Not created automatically.
# VOLUME /botdata
# Get tarball with the chatbot code and data.
ADD https://github.com/tinode/chat/releases/download/v${BINVERS}/py-chatbot.tar.gz .
# Unpack chatbot, delete archive
RUN tar -xzf py-chatbot.tar.gz \
&& rm py-chatbot.tar.gz
RUN pip install --no-cache-dir -r requirements.txt
# Healthcheck: try to connect to the gRPC port (40051)
HEALTHCHECK --interval=1m --timeout=3s --start-period=15s \
CMD python -c "import socket; s=socket.create_connection(('localhost', 40051), timeout=1); s.close()" || exit 1
# Use docker's command line parameter `-e LOGIN_AS=user:password` to login as someone other than Tino.
CMD ["/bin/sh", "-c", "python chatbot.py --login-basic=${LOGIN_AS} --login-cookie=/botdata/.tn-cookie --host=${TINODE_HOST} >> /var/log/chatbot.log"]
# Plugin port
EXPOSE 40051
================================================
FILE: docker/docker-compose/README.md
================================================
# Docker compose for end-to-end setup.
These reference docker-compose files will run Tinode with the MySql backend either as [a single-instance](single-instance.yml) or [a 3-node cluster](cluster.yml) setup.
```
docker-compose -f <name of the file> [-f <name of override file>] up -d
```
By default, this command starts up a mysql instance, Tinode server(s) and Tinode exporter(s).
Tinode server(s) is(are) configured similar to [Tinode Demo/Sandbox](../../README.md#demosandbox) and
maps its web port to the host's port 6060 (6061, 6062). Tinode exporter(s) serve(s) metrics for InfluxDB.
Reference configuration for the following databases is also available in the override files:
* [PostgreSQL 15.2](https://hub.docker.com/_/postgres/tags)
* [MongoDB 4.2.3](https://hub.docker.com/_/mongo/tags)
* [RethinkDB 2.4.2](https://hub.docker.com/_/rethinkdb/tags)
## Commands
### Full stack
To bring up the full stack, you can use the following commands:
* MySql:
- Single-instance setup: `docker-compose -f single-instance.yml up -d`
- Cluster: `docker-compose -f cluster.yml up -d`
* PostgreSQL:
- Single-instance setup: `docker-compose -f single-instance.yml -f single-instance.postgres.yml up -d`
- Cluster: `docker-compose -f cluster.yml -f cluster.postgres.yml up -d`
* MongoDb:
- Single-instance setup: `docker-compose -f single-instance.yml -f single-instance.mongodb.yml up -d`
- Cluster: `docker-compose -f cluster.yml -f cluster.mongodb.yml up -d`
* RethinkDb:
- Single-instance setup: `docker-compose -f single-instance.yml -f single-instance.rethinkdb.yml up -d`
- Cluster: `docker-compose -f cluster.yml -f cluster.rethinkdb.yml up -d`
You can run individual/separate components of the setup by providing their names to the `docker-compose` command.
E.g. to start the Tinode server in the single-instance MySql setup,
```
docker-compose -f single-instance.yml up -d tinode-0
```
### Database resets and/or version upgrades
To reset the database or upgrade the database version, you can set `RESET_DB` or `UPGRADE_DB` environment variable to true when starting Tinode with docker-compose.
E.g. for upgrading the database in MongoDb cluster setup, use:
```
UPGRADE_DB=true docker-compose -f cluster.yml -f cluster.mongodb.yml up -d tinode-0
```
For resetting the database in RethinkDb single-instance setup, run:
```
RESET_DB=true docker-compose -f single-instance.yml -f single-instance.rethinkdb.yml up -d tinode-0
```
## Troubleshooting
Print out and verify your docker-compose configuration by running:
```
docker-compose -f <name of the file> [-f <name of override file>] config
```
If the Tinode server(s) are failing, you can print the job's stdout/stderr with:
```
docker logs tinode-<instance number>
```
Additionally, you can examine the jobs `tinode.log` file. To download it from the container, run:
```
docker cp tinode-<instance number>:/var/log/tinode.log .
```
================================================
FILE: docker/docker-compose/cluster.mongodb.yml
================================================
version: '3.8'
x-mongodb-tinode-env-vars: &mongodb-tinode-env-vars
"STORE_USE_ADAPTER": "mongodb"
services:
db:
image: mongo:4.2.3
container_name: mongodb
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs0" ]
healthcheck:
test: ["CMD", "curl -f http://localhost:28017/ || exit 1"]
# Initializes MongoDb replicaset.
initdb:
image: mongo:4.2.3
container_name: initdb
depends_on:
- db
command: >
bash -c "echo 'Starting replica set initialize';
until mongo --host mongodb --eval 'print(\"waited for connection\")'; do sleep 2; done;
echo 'Connection finished';
echo 'Creating replica set';
echo \"rs.initiate({'_id': 'rs0', "members": [ {'_id': 0, 'host': 'mongodb:27017'} ]})\" | mongo --host mongodb"
tinode-0:
environment:
<< : *mongodb-tinode-env-vars
"WAIT_FOR": "mongodb:27017"
tinode-1:
environment:
<< : *mongodb-tinode-env-vars
tinode-2:
environment:
<< : *mongodb-tinode-env-vars
================================================
FILE: docker/docker-compose/cluster.postgres.yml
================================================
version: '3.8'
x-postgres-tinode-env-vars: &postgres-tinode-env-vars
"STORE_USE_ADAPTER": "postgres"
services:
db:
image: postgres:15.2
container_name: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
tinode-0:
environment:
<< : *postgres-tinode-env-vars
"WAIT_FOR": "postgres:5432"
tinode-1:
environment:
<< : *postgres-tinode-env-vars
tinode-2:
environment:
<< : *postgres-tinode-env-vars
================================================
FILE: docker/docker-compose/cluster.rethinkdb.yml
================================================
version: '3.8'
x-rethinkdb-tinode-env-vars: &rethinkdb-tinode-env-vars
"STORE_USE_ADAPTER": "rethinkdb"
services:
db:
image: rethinkdb:2.4.2
container_name: rethinkdb
healthcheck:
test: ["CMD", "curl -f http://localhost:8080/ || exit 1"]
tinode-0:
environment:
<< : *rethinkdb-tinode-env-vars
"WAIT_FOR": "rethinkdb:8080"
tinode-1:
environment:
<< : *rethinkdb-tinode-env-vars
tinode-2:
environment:
<< : *rethinkdb-tinode-env-vars
================================================
FILE: docker/docker-compose/cluster.yml
================================================
# Reference configuration for a simple 3-node Tinode cluster.
# Includes:
# * Mysql database
# * 3 Tinode servers
# * 3 exporters
version: '3.8'
# Base Tinode template.
x-tinode:
&tinode-base
depends_on:
- db
image: tinode/tinode:latest
restart: always
x-exporter:
&exporter-base
image: tinode/exporter:latest
restart: always
x-tinode-env-vars: &tinode-env-vars
"STORE_USE_ADAPTER": "mysql"
"PPROF_URL": "/pprof"
# You can provide your own tinode config by setting EXT_CONFIG env var and binding your configuration file to
# "EXT_CONFIG": "/etc/tinode/tinode.conf"
"WAIT_FOR": "mysql:3306"
# Push notifications.
# Modify as appropriate.
# Tinode Push Gateway configuration.
"TNPG_PUSH_ENABLED": "false"
# "TNPG_USER": "<user name>"
# "TNPG_AUTH_TOKEN": "<token>"
# FCM specific server configuration.
"FCM_PUSH_ENABLED": "false"
# "FCM_CRED_FILE": "<path to FCM credentials file>"
# "FCM_INCLUDE_ANDROID_NOTIFICATION": false
#
# FCM Web client configuration.
"FCM_API_KEY": "AIzaSyD6X4ULR-RUsobvs1zZ2bHdJuPz39q2tbQ"
"FCM_APP_ID": "1:114126160546:web:aca6ea2981feb81fb44dfb"
"FCM_PROJECT_ID": "tinode-1000"
"FCM_SENDER_ID": 114126160546
"FCM_VAPID_KEY": "BOgQVPOMzIMXUpsYGpbVkZoEBc0ifKY_f2kSU5DNDGYI6i6CoKqqxDd7w7PJ3FaGRBgVGJffldETumOx831jl58"
"FCM_MEASUREMENT_ID": "G-WNJDQR34L3"
# iOS app universal links configuration.
# "IOS_UNIV_LINKS_APP_ID": "<ios universal links app id>"
# Video calls
"WEBRTC_ENABLED": "false"
# "ICE_SERVERS_FILE": "<path to ICE servers config>"
x-exporter-env-vars: &exporter-env-vars
"TINODE_ADDR": "http://tinode.host:18080/stats/expvar/"
# InfluxDB configation:
"SERVE_FOR": "influxdb"
"INFLUXDB_VERSION": 1.7
"INFLUXDB_ORGANIZATION": "<your organization>"
"INFLUXDB_PUSH_INTERVAL": 30
"INFLUXDB_PUSH_ADDRESS": "https://mon.tinode.co/intake"
"INFLUXDB_AUTH_TOKEN": "<auth token>"
# Prometheus configuration:
# "SERVE_FOR": "prometheus"
# "PROM_NAMESPACE": "tinode"
# "PROM_METRICS_PATH": "/metrics"
services:
db:
image: mysql:8.0
container_name: mysql
restart: always
# Use your own volume.
# volumes:
# - <mysql directory in your file system>:/var/lib/mysql
environment:
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
timeout: 5s
retries: 10
security_opt:
- seccomp=unconfined
# Tinode servers.
tinode-0:
<< : *tinode-base
container_name: tinode-0
hostname: tinode-0
# You can mount your volumes as necessary:
# volumes:
# # E.g. external config (assuming EXT_CONFIG is set).
# - <path to your tinode.conf>:/etc/tinode/tinode.conf
# # Logs directory.
# - <path to your tinode-0 logs directory>:/var/log
ports:
- "6060:6060"
environment:
<< : *tinode-env-vars
"CLUSTER_SELF": "tinode-0"
"RESET_DB": ${RESET_DB:-false}
"UPGRADE_DB": ${UPGRADE_DB:-false}
tinode-1:
<< : *tinode-base
container_name: tinode-1
hostname: tinode-1
# You can mount your volumes as necessary:
# volumes:
# # E.g. external config (assuming EXT_CONFIG is set).
# - <path to your tinode.conf>:/etc/tinode/tinode.conf
# # Logs directory.
# - <path to your tinode-1 logs directory>:/var/log
ports:
- "6061:6060"
environment:
<< : *tinode-env-vars
"CLUSTER_SELF": "tinode-1"
# Wait for tinode-0, not the database since
# we let tinode-0 perform all database initialization and upgrade work.
"WAIT_FOR": "tinode-0:6060"
"NO_DB_INIT": "true"
tinode-2:
<< : *tinode-base
container_name: tinode-2
hostname: tinode-2
# You can mount your volumes as necessary:
# volumes:
# # E.g. external config (assuming EXT_CONFIG is set).
# - <path to your tinode.conf>:/etc/tinode/tinode.conf
# # Logs directory.
# - <path to your tinode-2 logs directory>:/var/log
ports:
- "6062:6060"
environment:
<< : *tinode-env-vars
"CLUSTER_SELF": "tinode-2"
# Wait for tinode-0, not the database since
# we let tinode-0 perform all database initialization and upgrade work.
"WAIT_FOR": "tinode-0:6060"
"NO_DB_INIT": "true"
# Monitoring.
# Exporters are paired with tinode instances.
exporter-0:
<< : *exporter-base
container_name: exporter-0
hostname: exporter-0
depends_on:
- tinode-0
ports:
- 6222:6222
links:
- tinode-0:tinode.host
environment:
<< : *exporter-env-vars
"INSTANCE": "tinode-0"
"WAIT_FOR": "tinode-0:6060"
exporter-1:
<< : *exporter-base
container_name: exporter-1
hostname: exporter-1
depends_on:
- tinode-1
ports:
- 6223:6222
links:
- tinode-1:tinode.host
environment:
<< : *exporter-env-vars
"INSTANCE": "tinode-1"
"WAIT_FOR": "tinode-1:6060"
exporter-2:
<< : *exporter-base
container_name: exporter-2
hostname: exporter-2
depends_on:
- tinode-2
ports:
- 6224:6222
links:
- tinode-2:tinode.host
environment:
<< : *exporter-env-vars
"INSTANCE": "tinode-2"
"WAIT_FOR": "tinode-2:6060"
================================================
FILE: docker/docker-compose/single-instance.mongodb.yml
================================================
version: '3.8'
x-mongodb-tinode-env-vars: &mongodb-tinode-env-vars
"STORE_USE_ADAPTER": "mongodb"
services:
db:
image: mongo:4.2.3
container_name: mongodb
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs0" ]
healthcheck:
test: ["CMD", "curl -f http://localhost:28017/ || exit 1"]
# Initializes MongoDb replicaset.
initdb:
image: mongo:4.2.3
container_name: initdb
depends_on:
- db
command: >
bash -c "echo 'Starting replica set initialize';
until mongo --host mongodb --eval 'print(\"waited for connection\")'; do sleep 2; done;
echo 'Connection finished';
echo 'Creating replica set';
echo \"rs.initiate({'_id': 'rs0', "members": [ {'_id': 0, 'host': 'mongodb:27017'} ]})\" | mongo --host mongodb"
tinode-0:
environment:
<< : *mongodb-tinode-env-vars
"WAIT_FOR": "mongodb:27017"
================================================
FILE: docker/docker-compose/single-instance.postgres.yml
================================================
version: '3.8'
x-postgres-tinode-env-vars: &postgres-tinode-env-vars
"STORE_USE_ADAPTER": "postgres"
services:
db:
image: postgres:15.2
container_name: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
tinode-0:
environment:
<< : *postgres-tinode-env-vars
"WAIT_FOR": "postgres:5432"
================================================
FILE: docker/docker-compose/single-instance.rethinkdb.yml
================================================
version: '3.8'
x-rethinkdb-tinode-env-vars: &rethinkdb-tinode-env-vars
"STORE_USE_ADAPTER": "rethinkdb"
services:
db:
image: rethinkdb:2.4.0
container_name: rethinkdb
healthcheck:
test: ["CMD", "curl -f http://localhost:8080/ || exit 1"]
tinode-0:
environment:
<< : *rethinkdb-tinode-env-vars
"WAIT_FOR": "rethinkdb:8080"
================================================
FILE: docker/docker-compose/single-instance.yml
================================================
# Reference configuration for a simple Tinode server.
# Includes:
# * Mysql database
# * Tinode server
# * Tinode exporters
version: '3.8'
# Base Tinode template.
x-tinode:
&tinode-base
depends_on:
- db
image: tinode/tinode:latest
restart: always
x-tinode-env-vars: &tinode-env-vars
"STORE_USE_ADAPTER": "mysql"
"PPROF_URL": "/pprof"
# You can provide your own tinode config by setting EXT_CONFIG env var and binding your configuration file to
# "EXT_CONFIG": "/etc/tinode/tinode.conf"
"WAIT_FOR": "mysql:3306"
# Push notifications.
# Modify as appropriate.
# Tinode Push Gateway configuration.
"TNPG_PUSH_ENABLED": "false"
# "TNPG_USER": "<user name>"
# "TNPG_AUTH_TOKEN": "<token>"
# FCM specific server configuration.
"FCM_PUSH_ENABLED": "false"
# "FCM_CRED_FILE": "<path to FCM credentials file>"
# "FCM_INCLUDE_ANDROID_NOTIFICATION": false
#
# FCM Web client configuration.
"FCM_API_KEY": "AIzaSyD6X4ULR-RUsobvs1zZ2bHdJuPz39q2tbQ"
"FCM_APP_ID": "1:114126160546:web:aca6ea2981feb81fb44dfb"
"FCM_PROJECT_ID": "tinode-1000"
"FCM_SENDER_ID": 114126160546
"FCM_VAPID_KEY": "BOgQVPOMzIMXUpsYGpbVkZoEBc0ifKY_f2kSU5DNDGYI6i6CoKqqxDd7w7PJ3FaGRBgVGJffldETumOx831jl58"
"FCM_MEASUREMENT_ID": "G-WNJDQR34L3"
# iOS app universal links configuration.
# "IOS_UNIV_LINKS_APP_ID": "<ios universal links app id>"
# Video calls
"WEBRTC_ENABLED": "false"
# "ICE_SERVERS_FILE": "<path to ICE servers config>"
x-exporter-env-vars: &exporter-env-vars
"TINODE_ADDR": "http://tinode.host:6060/stats/expvar/"
# InfluxDB configation:
"SERVE_FOR": "influxdb"
"INFLUXDB_VERSION": 1.7
"INFLUXDB_ORGANIZATION": "<your organization>"
"INFLUXDB_PUSH_INTERVAL": 30
"INFLUXDB_PUSH_ADDRESS": "https://mon.tinode.co/intake"
"INFLUXDB_AUTH_TOKEN": "<auth token>"
# Prometheus configuration:
# "SERVE_FOR": "prometheus"
# "PROM_NAMESPACE": "tinode"
# "PROM_METRICS_PATH": "/metrics"
services:
db:
image: mysql:8.0
container_name: mysql
restart: always
# Use your own volume.
# volumes:
# - <mysql directory in your file system>:/var/lib/mysql
environment:
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
timeout: 5s
retries: 10
security_opt:
- seccomp=unconfined
# Tinode.
tinode-0:
<< : *tinode-base
container_name: tinode-0
hostname: tinode-0
# You can mount your volumes as necessary:
# volumes:
# # E.g. external config (assuming EXT_CONFIG is set).
# - <path to your tinode.conf>:/etc/tinode/tinode.conf
# # Logs directory.
# - <path to your tinode-0 logs directory>:/var/log
ports:
- "6060:6060"
environment:
<< : *tinode-env-vars
"RESET_DB": ${RESET_DB:-false}
"UPGRADE_DB": ${UPGRADE_DB:-false}
# Monitoring.
# Exporters are paired with tinode instances.
exporter-0:
container_name: exporter-0
hostname: exporter-0
depends_on:
- tinode-0
image: tinode/exporter:latest
restart: always
ports:
- "6222:6222"
links:
- tinode-0:tinode.host
environment:
<< : *exporter-env-vars
"WAIT_FOR": "tinode-0:6060"
================================================
FILE: docker/exporter/Dockerfile
================================================
FROM alpine:3.14
ARG VERSION=0.16.4
ENV VERSION=$VERSION
LABEL maintainer="Tinode Team <info@tinode.co>"
LABEL name="TinodeMetricExporter"
LABEL version=$VERSION
ENV SERVE_FOR=""
ENV WAIT_FOR=""
ENV TINODE_ADDR=http://localhost/stats/expvar/
ENV INSTANCE="exporter-instance"
ENV INFLUXDB_VERSION=1.7
ENV INFLUXDB_ORGANIZATION="org"
ENV INFLUXDB_PUSH_INTERVAL=60
ENV INFLUXDB_PUSH_ADDRESS=""
ENV INFLUXDB_AUTH_TOKEN=""
ENV PROM_NAMESPACE="tinode"
ENV PROM_METRICS_PATH="/metrics"
WORKDIR /opt/tinode
RUN apk add --no-cache bash
# Fetch exporter build from Github.
ADD https://github.com/tinode/chat/releases/download/v$VERSION/exporter.linux-amd64 ./exporter
COPY entrypoint.sh .
RUN chmod +x exporter && chmod +x entrypoint.sh
ENTRYPOINT ./entrypoint.sh
EXPOSE 6222
================================================
FILE: docker/exporter/entrypoint.sh
================================================
#!/bin/bash
# Check if environment variables (provided as argument list) are set.
function check_vars() {
local varnames=( "$@" )
for varname in "${varnames[@]}"
do
eval value=\$${varname}
if [ -z "$value" ] ; then
echo "$varname env var must be specified."
exit 1
fi
done
}
# Make sure the system uses /etc/hosts when resolving domain names
# (needed for docker-compose's `extra_hosts` param to work correctly).
# See https://github.com/gliderlabs/docker-alpine/issues/367,
# https://github.com/golang/go/issues/35305 for details.
echo "hosts: files dns" > /etc/nsswitch.conf
# Accept http requests at.
LISTEN_AT=":6222"
# Required env vars.
common_vars=( TINODE_ADDR INSTANCE SERVE_FOR )
influx_varnames=( INFLUXDB_VERSION INFLUXDB_ORGANIZATION INFLUXDB_PUSH_INTERVAL \
INFLUXDB_PUSH_ADDRESS INFLUXDB_AUTH_TOKEN )
prometheus_varnames=( PROM_NAMESPACE PROM_METRICS_PATH )
check_vars "${common_vars[@]}"
# Common arguments.
args=("--tinode_addr=${TINODE_ADDR}" "--instance=${INSTANCE}" "--listen_at=${LISTEN_AT}" "--serve_for=${SERVE_FOR}")
# Platform-specific arguments.
case "$SERVE_FOR" in
"prometheus")
check_vars "${prometheus_varnames[@]}"
args+=("--prom_namespace=${PROM_NAMESPACE}" "--prom_metrics_path=${PROM_METRICS_PATH}")
if [ ! -z "$PROM_TIMEOUT" ]; then
args+=("--prom_timeout=${PROM_TIMEOUT}")
fi
;;
"influxdb")
check_vars "${influxdb_varnames[@]}"
args+=("--influx_db_version=${INFLUXDB_VERSION}" \
"--influx_organization=${INFLUXDB_ORGANIZATION}" \
"--influx_push_interval=${INFLUXDB_PUSH_INTERVAL}" \
"--influx_push_addr=${INFLUXDB_PUSH_ADDRESS}" \
"--influx_auth_token=${INFLUXDB_AUTH_TOKEN}")
if [ ! -z "$INFLUXDB_BUCKET" ]; then
args+=("--influx_bucket=${INFLUXDB_BUCKET}")
fi
;;
*)
echo "\$SERVE_FOR must be set to either 'prometheus' or 'influxdb'"
exit 1
;;
esac
# Wait for Tinode server if needed.
if [ ! -z "$WAIT_FOR" ] ; then
IFS=':' read -ra TND <<< "$WAIT_FOR"
if [ ${#TND[@]} -ne 2 ]; then
echo "\$WAIT_FOR (${WAIT_FOR}) env var should be in form HOST:PORT"
exit 1
fi
until nc -z -v -w5 ${TND[0]} ${TND[1]}; do echo "waiting for ${WAIT_FOR}..."; sleep 5; done
fi
./exporter "${args[@]}"
================================================
FILE: docker/tinode/Dockerfile
================================================
# Docker file builds an image with a tinode chat server.
#
# In order to run the image you have to link it to a running database container. For example, to
# to use RethinkDB (named 'rethinkdb') and map the port where the tinode server accepts connections:
#
# $ docker run -p 6060:6060 -d --link rethinkdb \
# --env UID_ENCRYPTION_KEY=base64+encoded+16+bytes= \
# --env API_KEY_SALT=base64+encoded+32+bytes \
# --env AUTH_TOKEN_KEY=base64+encoded+32+bytes \
# tinode-server
FROM alpine:3.22
ARG VERSION=0.25
ENV VERSION=$VERSION
ARG BINVERS=$VERSION
LABEL maintainer="Tinode Team <info@tinode.co>"
LABEL name="TinodeChatServer"
LABEL version=$VERSION
# Build-time options.
# Database selector. Builds for MySQL by default.
# Alternatively use one of: postgres mongodb rethinkdb for a corresponsing
# DB backend or alldbs to build a generic Tinode docker image, for example:
# `--build-arg TARGET_DB=postgres` to build for PostgreSQL.
ARG TARGET_DB=mysql
ENV TARGET_DB=$TARGET_DB
# Runtime options.
# Specifies the database host:port pair to wait for before running Tinode.
# Ignored if empty.
ENV WAIT_FOR=
# An option to reset database.
ENV RESET_DB=false
# An option to upgrade database.
ENV UPGRADE_DB=false
# Option to skip DB initialization when it's missing.
ENV NO_DB_INIT=false
# Load sample data to database from data.json.
ARG SAMPLE_DATA=data.json
ENV SAMPLE_DATA=$SAMPLE_DATA
# Default country code to use in communication.
ENV DEFAULT_COUNTRY_CODE=US
# The MySQL DSN connection.
ENV MYSQL_DSN='root@tcp(mysql)/tinode?parseTime=true&collation=utf8mb4_0900_ai_ci'
# The PostgreSQL DSN connection.
ENV POSTGRES_DSN='postgresql://postgres:postgres@localhost:5432/tinode?sslmode=disable&connect_timeout=10'
# Disable chatbot plugin by default.
ENV PLUGIN_PYTHON_CHAT_BOT_ENABLED=false
# Default handler for large files
ENV MEDIA_HANDLER=fs
# Whitelisted domains for file and S3 large media handler.
ENV FS_CORS_ORIGINS='["*"]'
ENV AWS_CORS_ORIGINS='["*"]'
# AWS S3 parameters
ENV AWS_ACCESS_KEY_ID=
ENV AWS_SECRET_ACCESS_KEY=
ENV AWS_REGION=
ENV AWS_S3_BUCKET=
ENV AWS_S3_ENDPOINT=
# Default externally-visible hostname for email verification.
ENV SMTP_HOST_URL='http://localhost:6060'
# Email parameters decalarations.
ENV SMTP_SERVER=
ENV SMTP_PORT=
ENV SMTP_SENDER=
ENV SMTP_LOGIN=
ENV SMTP_PASSWORD=
ENV SMTP_AUTH_MECHANISM=
ENV SMTP_HELO_HOST=
ENV EMAIL_VERIFICATION_REQUIRED=
ENV DEBUG_EMAIL_VERIFICATION_CODE=
# Whitelist of permitted email domains for email verification (empty list means all domains are permitted)
ENV SMTP_DOMAINS=''
# Various encryption and salt keys. Replace with your own in production.
# Salt used to generate the API key. Don't change it unless you also change the
# API key in the webapp & Android.
ENV API_KEY_SALT=T713/rYYgW7g4m3vG6zGRh7+FM1t0T8j13koXScOAj4=
# Key used to sign authentication tokens.
ENV AUTH_TOKEN_KEY=wfaY2RgF2S1OQI/ZlK+LSrp1KB2jwAdGAIHQ7JZn+Kc=
# Key to initialize UID generator
ENV UID_ENCRYPTION_KEY=la6YsO+bNX/+XIkOqc5Svw==
# Disable TLS by default.
ENV TLS_ENABLED=false
ENV TLS_DOMAIN_NAME=
ENV TLS_CONTACT_ADDRESS=
# Disable push notifications by default.
ENV FCM_PUSH_ENABLED=false
# Declare FCM-related vars
ENV FCM_API_KEY=
ENV FCM_APP_ID=
ENV FCM_SENDER_ID=
ENV FCM_PROJECT_ID=
ENV FCM_VAPID_KEY=
ENV FCM_MEASUREMENT_ID=
# Enable Android-specific notifications by default.
ENV FCM_INCLUDE_ANDROID_NOTIFICATION=true
# Disable push notifications via Tinode Push Gateway.
ENV TNPG_PUSH_ENABLED=false
# Tinode Push Gateway authentication token.
ENV TNPG_AUTH_TOKEN=
# Tinode Push Gateway organization name as registered at console.tinode.co
ENV TNPG_ORG=
# Video calls configuration.
ENV WEBRTC_ENABLED=false
ENV ICE_SERVERS_FILE=
# Use the target db by default.
# When TARGET_DB is "alldbs", it is the user's responsibility
# to set STORE_USE_ADAPTER to the desired db adapter correctly.
ENV STORE_USE_ADAPTER=$TARGET_DB
# Url path for exposing the server's internal status. E.g. '/status'
ENV SERVER_STATUS_PATH=''
# Garbage collection of unfinished account registrations.
ENV ACC_GC_ENABLED=false
# Install root certificates, they are needed for email validator to work
# with the TLS SMTP servers like Gmail or Mailjet. Also add bash and grep.
RUN apk update && \
apk add --no-cache ca-certificates bash grep
WORKDIR /opt/tinode
# Copy config template to the container.
COPY config.template .
COPY entrypoint.sh .
# Get the desired Tinode build.
ADD https://github.com/tinode/chat/releases/download/v$BINVERS/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
# Create directory for chatbot data.
RUN mkdir /botdata
# Make scripts runnable
RUN chmod +x entrypoint.sh
RUN chmod +x credentials.sh
# Healthcheck: check if port 6060 is open
HEALTHCHECK --interval=1m --timeout=3s --start-period=30s \
CMD nc -z localhost 6060 || exit 1
# Generate config from template and run the server.
ENTRYPOINT ["./entrypoint.sh"]
# HTTP, gRPC, cluster ports
EXPOSE 6060 16060 12000-12003
================================================
FILE: docker/tinode/config.template
================================================
{
"listen": ":6060",
"api_path": "/",
"cache_control": 39600,
"static_mount": "/",
"grpc_listen": ":16060",
"grpc_keepalive_enabled": true,
"api_key_salt": "$API_KEY_SALT",
"max_message_size": 4194304,
"max_subscriber_count": 128,
"max_tag_count": 16,
"expvar": "/stats/expvar/",
"server_status": "$SERVER_STATUS_PATH",
"use_x_forwarded_for": true,
"default_country_code": "$DEFAULT_COUNTRY_CODE",
"media": {
"use_handler": "$MEDIA_HANDLER",
"max_size": 33554432,
"gc_period": 60,
"gc_block_size": 100,
"handlers": {
"fs": {
"upload_dir": "uploads",
"cache_control": "max-age=86400",
"cors_origins": $FS_CORS_ORIGINS
},
"s3":{
"access_key_id": "$AWS_ACCESS_KEY_ID",
"secret_access_key": "$AWS_SECRET_ACCESS_KEY",
"region": "$AWS_REGION",
"bucket": "$AWS_S3_BUCKET",
"endpoint": "$AWS_S3_ENDPOINT",
"presign_ttl": 3600,
"cache_control": "max-age=86400",
"cors_origins": $AWS_CORS_ORIGINS
}
}
},
"tls": {
"enabled": $TLS_ENABLED,
"http_redirect": ":80",
"strict_max_age": 604800,
"autocert": {
"cache": "/etc/letsencrypt/live/$TLS_DOMAIN_NAME",
"email": "$TLS_CONTACT_ADDRESS",
"domains": ["$TLS_DOMAIN_NAME"]
}
},
"auth_config": {
"logical_names": [],
"basic": {
"add_to_tags": true,
"min_login_length": 3,
"min_password_length": 6
},
"token": {
"expire_in": 1209600,
"serial_num": 1,
"key": "$AUTH_TOKEN_KEY"
},
"code": {
"expire_in": 900,
"max_retries": 3,
"code_length": 6
}
},
"store_config": {
"uid_key": "$UID_ENCRYPTION_KEY",
"max_results": 1024,
"use_adapter": "$STORE_USE_ADAPTER",
"adapters": {
"mysql": {
"database": "tinode",
"dsn": "$MYSQL_DSN"
},
"postgres": {
"database": "tinode",
"dsn": "$POSTGRES_DSN"
},
"rethinkdb": {
"database": "tinode",
"addresses": "rethinkdb"
},
"mongodb": {
"database": "tinode",
"addresses": "mongodb",
"replica_set": "rs0"
}
}
},
"acc_validation": {
"email": {
"add_to_tags": true,
"required": [$EMAIL_VERIFICATION_REQUIRED],
"config": {
"host_url": "$SMTP_HOST_URL",
"smtp_server": "$SMTP_SERVER",
"smtp_port": "$SMTP_PORT",
"sender": "$SMTP_SENDER",
"login": "$SMTP_LOGIN",
"sender_password": "$SMTP_PASSWORD",
"auth_mechanism": "$SMTP_AUTH_MECHANISM",
"smtp_helo_host": "$SMTP_HELO_HOST",
"languages": ["en", "es", "fr", "ru", "vi", "zh"],
"validation_templ": "./templ/email-validation-{{.Language}}.templ",
"reset_secret_templ": "./templ/email-password-reset-{{.Language}}.templ",
"max_retries": 3,
"domains": [$SMTP_DOMAINS],
"debug_response": "$DEBUG_EMAIL_VERIFICATION_CODE"
}
},
"tel": {
"add_to_tags": true,
"config": {
"host_url": "$TEL_HOST_URL",
"languages": ["en", "es", "fr", "pt", "ru", "vi", "zh"],
"sender": "$TEL_SENDER",
"universal_templ": "./templ/sms-universal-{{.Language}}.templ",
"max_retries": 3,
"debug_response": "$DEBUG_TEL_VERIFICATION_CODE"
}
}
},
"acc_gc_config": {
"enabled": $ACC_GC_ENABLED,
"gc_period": 3600,
"gc_block_size": 10,
"gc_min_account_age": 48
},
"push": [
{
"name":"tnpg",
"config": {
"enabled": $TNPG_PUSH_ENABLED,
"token": "$TNPG_AUTH_TOKEN",
"org": "$TNPG_ORG"
}
},
{
"name":"fcm",
"config": {
"enabled": $FCM_PUSH_ENABLED,
"project_id": "$FCM_PROJECT_ID",
"credentials_file": "$FCM_CRED_FILE",
"time_to_live": 3600,
"android": {
"enabled": $FCM_INCLUDE_ANDROID_NOTIFICATION,
"icon": "ic_logo_push",
"icon_color": "#3949AB",
"click_action": ".MessageActivity",
"msg": {
"title_loc_key": "new_message",
"title": "",
"body_loc_key": "",
"body": ""
},
"sub": {
"title_loc_key": "new_chat",
"body_loc_key": ""
}
}
}
}
],
"webrtc": {
"enabled": $WEBRTC_ENABLED,
"call_establishment_timeout": 30,
"ice_servers_file": "$ICE_SERVERS_FILE"
},
"cluster_config": {
"self": "",
"nodes": [
{"name": "tinode-0", "addr": "tinode-0:12000"},
{"name": "tinode-1", "addr": "tinode-1:12001"},
{"name": "tinode-2", "addr": "tinode-2:12002"}
],
"failover": {
"enabled": true,
"heartbeat": 100,
"vote_after": 8,
"node_fail_after": 16
}
},
"plugins": [
{
"enabled": $PLUGIN_PYTHON_CHAT_BOT_ENABLED,
"name": "python_chat_bot",
"timeout": 20000,
"filters": {
"account": "C"
},
"failure_code": 0,
"failure_text": null,
"service_addr": "tcp://localhost:40051"
}
]
}
================================================
FILE: docker/tinode/entrypoint.sh
================================================
#!/bin/bash
# If EXT_CONFIG is set, use it as a config file.
if [ ! -z "$EXT_CONFIG" ] ; then
CONFIG="$EXT_CONFIG"
# Enable push notifications.
if [ ! -z "$FCM_SENDER_ID" ] ; then
FCM_PUSH_ENABLED=true
fi
else
CONFIG=working.config
# Remove the old config.
rm -f working.config
# The 'alldbs' is not a valid adapter name.
if [ "$TARGET_DB" = "alldbs" ] ; then
TARGET_DB=
fi
# Enable email verification if $SMTP_SERVER is defined.
if [ ! -z "$SMTP_SERVER" ] ; then
EMAIL_VERIFICATION_REQUIRED='"auth"'
fi
# Enable TLS (httpS).
if [ ! -z "$TLS_DOMAIN_NAME" ] ; then
TLS_ENABLED=true
fi
# Enable push notifications.
if [ ! -z "$FCM_CRED_FILE" ] ; then
FCM_PUSH_ENABLED=true
fi
if [ ! -z "$TNPG_AUTH_TOKEN" ] ; then
TNPG_PUSH_ENABLED=true
fi
if [ ! -z "$ICE_SERVERS_FILE" ] ; then
WEBRTC_ENABLED=true
fi
# Generate a new 'working.config' from template and environment
while IFS='' read -r line || [[ -n $line ]] ; do
while [[ "$line" =~ (\$[A-Z_][A-Z_0-9]*) ]] ; do
LHS=${BASH_REMATCH[1]}
RHS="$(eval echo "\"$LHS\"")"
line=${line//$LHS/"$RHS"}
done
echo "$line" >> working.config
done < config.template
fi
# If external static dir is defined, use it.
# Otherwise, fall back to "./static".
if [ ! -z "$EXT_STATIC_DIR" ] ; then
STATIC_DIR=$EXT_STATIC_DIR
else
STATIC_DIR="./static"
fi
# Do not load data when upgrading database.
if [ "$UPGRADE_DB" = "true" ] ; then
SAMPLE_DATA=
fi
# If push notifications are enabled, generate client-side firebase config file.
if [ ! -z "$FCM_PUSH_ENABLED" ] || [ ! -z "$TNPG_PUSH_ENABLED" ] ; then
# Write client config to $STATIC_DIR/firebase-init.js
cat > $STATIC_DIR/firebase-init.js <<- EOM
const FIREBASE_INIT = {
apiKey: "$FCM_API_KEY",
appId: "$FCM_APP_ID",
messagingSenderId: "$FCM_SENDER_ID",
projectId: "$FCM_PROJECT_ID",
messagingVapidKey: "$FCM_VAPID_KEY",
measurementId: "$FCM_MEASUREMENT_ID"
};
EOM
else
# Create an empty firebase-init.js
echo "" > $STATIC_DIR/firebase-init.js
fi
if [ ! -z "$IOS_UNIV_LINKS_APP_ID" ] ; then
# Write config to $STATIC_DIR/apple-app-site-association config file.
# See https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html for details.
cat > $STATIC_DIR/apple-app-site-association <<- EOM
{
"applinks": {
"apps": [],
"details": [
{
"appID": "$IOS_UNIV_LINKS_APP_ID",
"paths": [ "*" ]
}
]
}
}
EOM
fi
# Wait for database if needed.
if [ ! -z "$WAIT_FOR" ] ; then
IFS=':' read -ra DB <<< "$WAIT_FOR"
if [ ${#DB[@]} -ne 2 ]; then
echo "\$WAIT_FOR (${WAIT_FOR}) env var should be in form HOST:PORT"
exit 1
fi
until nc -z -v -w5 ${DB[0]} ${DB[1]}; do echo "waiting for ${WAIT_FOR}..."; sleep 3; done
fi
# Initialize the database if it has not been initialized yet or if data reset/upgrade has been requested.
init_stdout=./init-db-stdout.txt
./init-db \
--reset=${RESET_DB} \
--upgrade=${UPGRADE_DB} \
--config=${CONFIG} \
--data=${SAMPLE_DATA} \
--no_init=${NO_DB_INIT} \
1>${init_stdout}
if [ $? -ne 0 ]; then
echo "./init-db failed. Quitting."
exit 1
fi
# If sample data was provided, try to find Tino password.
if [ ! -z "$SAMPLE_DATA" ] ; then
grep "usr;tino;" $init_stdout > /botdata/tino-password
fi
if [ -s /botdata/tino-password ] ; then
# Convert Tino's authentication credentials into a cookie file.
# /botdata/tino-password could be empty if DB was not updated. In such a case the
# /botdata/.tn-cookie will not be modified.
./credentials.sh /botdata/.tn-cookie < /botdata/tino-password
fi
args=("--config=${CONFIG}" "--static_data=$STATIC_DIR" "--cluster_self=$CLUSTER_SELF" "--pprof_url=$PPROF_URL")
# Run the tinode server.
./tinode "${args[@]}" 2>> /var/log/tinode.log
================================================
FILE: docker-build.sh
================================================
#!/bin/bash
# Build Tinode docker linux/amd64 images.
# You may have to install buildx https://docs.docker.com/buildx/working-with-buildx/
# if your build host and target architectures are different (e.g. building on a Mac
# with Apple silicon).
for line in $@; do
eval "$line"
done
tag=${tag#?}
if [ -z "$tag" ]; then
echo "Must provide tag as 'tag=v1.2.3'"
exit 1
fi
# Convert tag into a version
ver=( ${tag//./ } )
# if version contains a dash, it's not a full releave, i.e. v0.1.15.5-rc1
if [[ ${ver[2]} != *"-"* ]]; then
FULLRELEASE=1
fi
# Use buildx if the current platform is not x86.
buildcmd='build'
if [ `uname -m` != 'x86_64' ]; then
buildcmd='buildx build --platform=linux/amd64'
fi
# If explicit DB is specified, build just one, otherwise build all.
if [ "$db" ]; then
dbtags=( "$db" )
else
dbtags=( mysql postgres mongodb rethinkdb alldbs )
fi
# Build an images for various DB backends
for dbtag in "${dbtags[@]}"
do
if [ "$dbtag" == "alldbs" ]; then
# For alldbs, container name is tinode/tinode.
name="tinode/tinode"
else
# Otherwise, tinode/tinode-$dbtag.
name="tinode/tinode-${dbtag}"
fi
separator=
rmitags="${name}:${ver[0]}.${ver[1]}.${ver[2]}"
buildtags="--tag ${name}:${ver[0]}.${ver[1]}.${ver[2]}"
if [ -n "$FULLRELEASE" ]; then
rmitags="${rmitags} ${name}:latest ${name}:${ver[0]}.${ver[1]}"
buildtags="${buildtags} --tag ${name}:latest --tag ${name}:${ver[0]}.${ver[1]}"
fi
docker rmi ${rmitags}
docker ${buildcmd} --build-arg VERSION=$tag --build-arg TARGET_DB=${dbtag} ${buildtags} docker/tinode
done
if [ "$db" ]; then
exit 0
fi
# Build chatbot image
buildtags="--tag tinode/chatbot:${ver[0]}.${ver[1]}.${ver[2]}"
rmitags="tinode/chatbot:${ver[0]}.${ver[1]}.${ver[2]}"
if [ -n "$FULLRELEASE" ]; then
rmitags="${rmitags} tinode/chatbot:latest tinode/chatbot:${ver[0]}.${ver[1]}"
buildtags="${buildtags} --tag tinode/chatbot:latest --tag tinode/chatbot:${ver[0]}.${ver[1]}"
fi
docker rmi ${rmitags}
docker ${buildcmd} --build-arg VERSION=$tag ${buildtags} docker/chatbot
# Build exporter image
buildtags="--tag tinode/exporter:${ver[0]}.${ver[1]}.${ver[2]}"
rmitags="tinode/exporter:${ver[0]}.${ver[1]}.${ver[2]}"
if [ -n "$FULLRELEASE" ]; then
rmitags="${rmitags} tinode/exporter:latest tinode/exporter:${ver[0]}.${ver[1]}"
buildtags="${buildtags} --tag tinode/exporter:latest --tag tinode/exporter:${ver[0]}.${ver[1]}"
fi
docker rmi ${rmitags}
docker ${buildcmd} --build-arg VERSION=$tag ${buildtags} docker/exporter
================================================
FILE: docker-release.sh
================================================
#!/bin/bash
# Publish Tinode docker images to hub.docker.com
function containerName() {
if [ "$1" == "alldbs" ]; then
# For alldbs, container name is simply tinode.
local name="tinode"
else
# Otherwise, tinode-$dbtag.
local name="tinode-${dbtag}"
fi
echo $name
}
for line in $@; do
eval "$line"
done
tag=${tag#?}
if [ -z "$tag" ]; then
echo "Must provide tag as 'tag=v1.2.3' or 'v1.2.3-abc0'"
exit 1
fi
# Convert tag into a version
ver=( ${tag//./ } )
if [[ ${ver[2]} != *"-"* ]]; then
FULLRELEASE=1
fi
if [ "$db" ]; then
dbtags=( "$db" )
else
dbtags=( mysql postgres mongodb rethinkdb alldbs )
fi
# Read dockerhub login/password from a separate file
source .dockerhub
# Login to docker hub
docker login -u $user -p $pass
# Deploy images for various DB backends
for dbtag in "${dbtags[@]}"
do
name="$(containerName $dbtag)"
# Deploy tagged image
if [ -n "$FULLRELEASE" ]; then
docker push tinode/${name}:latest
docker push tinode/${name}:"${ver[0]}.${ver[1]}"
fi
docker push tinode/${name}:"${ver[0]}.${ver[1]}.${ver[2]}"
done
if [ "$db" ]; then
exit 0
fi
# Deploy chatbot images
if [ -n "$FULLRELEASE" ]; then
docker push tinode/chatbot:latest
docker push tinode/chatbot:"${ver[0]}.${ver[1]}"
fi
docker push tinode/chatbot:"${ver[0]}.${ver[1]}.${ver[2]}"
# Deploy exporter images
if [ -n "$FULLRELEASE" ]; then
docker push tinode/exporter:latest
docker push tinode/exporter:"${ver[0]}.${ver[1]}"
fi
docker push tinode/exporter:"${ver[0]}.${ver[1]}.${ver[2]}"
docker logout
================================================
FILE: docs/API.md
================================================
<!-- TOC depthfrom:1 depthto:6 withlinks:true updateonsave:true orderedlist:false -->
- [Server API](#server-api)
- [How it Works?](#how-it-works)
- [General Considerations](#general-considerations)
- [Connecting to the Server](#connecting-to-the-server)
- [gRPC](#grpc)
- [WebSocket](#websocket)
- [Long Polling](#long-polling)
- [Out of Band Large Files](#out-of-band-large-files)
- [Running Behind a Reverse Proxy](#running-behind-a-reverse-proxy)
- [Users](#users)
- [Authentication](#authentication)
- [Creating an Account](#creating-an-account)
- [Logging in](#logging-in)
- [Changing Authentication Parameters](#changing-authentication-parameters)
- [Resetting a Password, i.e. "Forgot Password"](#resetting-a-password-ie-forgot-password)
- [Suspending a User](#suspending-a-user)
- [Credential Validation](#credential-validation)
- [Access Control](#access-control)
- [Topics](#topics)
- [me Topic](#me-topic)
- [fnd and Tags: Finding Users and Topics](#fnd-and-tags-finding-users-and-topics)
- [Query Language](#query-language)
- [Incremental Updates to Queries](#incremental-updates-to-queries)
- [Query Rewrite](#query-rewrite)
- [Possible Use Cases](#possible-use-cases)
- [Peer to Peer Topics](#peer-to-peer-topics)
- [Group Topics](#group-topics)
- [sys Topic](#sys-topic)
- [Using Server-Issued Message IDs](#using-server-issued-message-ids)
- [User Agent and Presence Notifications](#user-agent-and-presence-notifications)
- [Trusted, Public, Private, Auxiliary Fields](#trusted-public-private-auxiliary-fields)
- [Trusted](#trusted)
- [Public](#public)
- [Private](#private)
- [Auxiliary](#auxiliary)
- [Format of Content](#format-of-content)
- [Out-of-Band Handling of Large Files](#out-of-band-handling-of-large-files)
- [Uploading](#uploading)
- [Downloading](#downloading)
- [Push Notifications](#push-notifications)
- [Tinode Push Gateway](#tinode-push-gateway)
- [Google FCM](#google-fcm)
- [Stdout](#stdout)
- [Video Calls](#video-calls)
- [Link Previews](#link-previews)
- [Messages](#messages)
- [Client to Server Messages](#client-to-server-messages)
- [{hi}](#hi)
- [{acc}](#acc)
- [{login}](#login)
- [{sub}](#sub)
- [{leave}](#leave)
- [{pub}](#pub)
- [{get}](#get)
- [{set}](#set)
- [{del}](#del)
- [{note}](#note)
- [Server to Client Messages](#server-to-client-messages)
- [{data}](#data)
- [{ctrl}](#ctrl)
- [{meta}](#meta)
- [{pres}](#pres)
- [{info}](#info)
<!-- /TOC -->
# Server API
## How it Works?
Tinode is an IM router and a store. Conceptually it loosely follows a [publish-subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) model.
Server connects sessions, users, and topics. Session is a network connection between a client application and the server. User represents a human being who connects to the server with a session. Topic is a named communication channel which routes content between sessions.
Users and topics are assigned unique IDs. User ID is a string with 'usr' prefix followed by base64-URL-encoded pseudo-random 64-bit number, e.g. `usr2il9suCbuko`. Topic IDs are described below.
Clients such as mobile or web applications create sessions by connecting to the server over a websocket or through long polling. Client authentication is required in order to perform most operations. Client authenticates the session by sending a `{login}` packet. See [Authentication](#authentication) section for details. Once authenticated, the client receives a token which is used for authentication later. Multiple simultaneous sessions may be established by the same user. Logging out is not supported (and not needed).
Once the session is established, the user can start interacting with other users through topics. The following
topic types are available:
* `me` is a topic for managing one's profile and receiving notifications about other topics; `me` topic exists for every user.
* `fnd` topic is used for finding other users and topics; `fnd` topic also exists for every user.
* Peer to peer topic is a communication channel strictly between two users. Each participant sees topic name as the ID of the other participant: 'usr' prefix followed by a base64-URL-encoded numeric part of user ID, e.g. `usr2il9suCbuko`.
* Group topic is a channel for multi-user communication. It's named as 'grp' followed by 11 pseudo-random characters, i.e. `grpYiqEXb4QY6s`. Group topics must be explicitly created.
Session joins a topic by sending a `{sub}` packet. Packet `{sub}` serves three functions: creating a new topic, subscribing user to a topic, and attaching session to a topic. See [`{sub}`](#sub) section below for details.
Once the session has joined the topic, the user may start generating content by sending `{pub}` packets. The content is delivered to other attached sessions as `{data}` packets.
The user may query or update topic metadata by sending `{get}` and `{set}` packets.
Changes to topic metadata, such as changes in topic description, or when other users join or leave the topic, is reported to live sessions with `{pres}` (presence) packet. The `{pres}` packet is sent either to the topic being affected or to the `me` topic.
When user's `me` topic comes online (i.e. an authenticated session attaches to `me` topic), a `{pres}` packet is sent to `me` topics of all other users, who have peer to peer subscriptions with the first user.
## General Considerations
Timestamps are always represented as [RFC 3339](http://tools.ietf.org/html/rfc3339)-formatted string with precision up to milliseconds and timezone always set to UTC, e.g. `"2015-10-06T18:07:29.841Z"`.
Whenever base64 encoding is mentioned, it means base64 URL encoding with padding characters stripped, see [RFC 4648](http://tools.ietf.org/html/rfc4648).
The `{data}` packets have server-issued sequential IDs: base-10 numbers starting at 1 and incrementing by one with every message. They are guaranteed to be unique per topic.
In order to connect requests to responses, client may assign message IDs to all packets set to the server. These IDs are strings defined by the client. Client should make them unique at least per session. The client-assigned IDs are not interpreted by the server, they are returned to the client as is.
## Connecting to the Server
There are three ways to access the server over the network: websocket, long polling, and [gRPC](https://grpc.io/).
When the client establishes a connection to the server over HTTP(S), such as over a websocket or long polling, the server offers the following endpoints:
* `/v0/channels` for websocket connections
* `/v0/channels/lp` for long polling
* `/v0/file/u` for file uploads
* `/v0/file/s` for serving files (downloads)
`v0` denotes API version (currently zero). Every HTTP(S) request must include the API key. The server checks for the API key in the following order:
* HTTP header `X-Tinode-APIKey`
* URL query parameter `apikey` (/v0/file/s/abcdefg.jpeg?apikey=...)
* Form value `apikey`
* Cookie `apikey`
A default API key is included with every demo app for convenience. Generate your own key for production using [`keygen` utility](../keygen).
Once the connection is opened, the client must issue a `{hi}` message to the server. Server responds with a `{ctrl}` message which indicates either success or an error. The `params` field of the response contains server's protocol version `"params":{"ver":"0.15"}` and may include other values.
### gRPC
See definition of the gRPC API in the [proto file](../pbx/model.proto). gRPC API has slightly more functionality than the API described in this document: it allows the `root` user to send messages on behalf of other users as well as delete users.
The `bytes` fields in protobuf messages expect JSON-encoded UTF-8 content. For example, a string should be quoted before being converted to bytes as UTF-8: `[]byte("\"some string\"")` (Go), `'"another string"'.encode('utf-8')` (Python 3).
### WebSocket
Messages are sent in text frames, one message per frame. Binary frames are reserved for future use. By default server allows connections with any value in the `Origin` header.
### Long Polling
Long polling works over `HTTP POST` (preferred) or `GET`. In response to client's very first request server sends a `{ctrl}` message containing `sid` (session ID) in `params`. Long polling client must include `sid` in every subsequent request either in the URL or in the request body.
Server allows connections from all origins, i.e. `Access-Control-Allow-Origin: *`
### Out of Band Large Files
Large files are sent out of band using `HTTP POST` as `Content-Type: multipart/form-data`. See [below](#out-of-band-handling-of-large-files) for details.
### Running Behind a Reverse Proxy
Tinode server can be set up to run behind a reverse proxy, such as NGINX. For efficiency it can accept client connections from Unix sockets by setting `listen` and/or `grpc_listen` config parameters to the path of the Unix socket file, e.g. `unix:/run/tinode.sock`. The server may also be configured to read peer's IP address from `X-Forwarded-For` HTTP header by setting `use_x_forwarded_for` config parameter to `true`.
## Users
User is meant to represent a person, an end-user: producer and consumer of messages.
Users are generally assigned one of the two authentication levels: authenticated `auth` or anonymous `anon`. The third level `root` is only accessible over `gRPC` where it permits the `root` to send messages on behalf of other users.
When a connection is first established, the client application can send either an `{acc}` or a `{login}` message which authenticates the user at one the levels.
Each user is assigned a unique ID. The IDs are composed as `usr` followed by base64-encoded 64-bit numeric value, e.g. `usr2il9suCbuko`. Users also have the following properties:
* `created`: timestamp when the user record was created
* `updated`: timestamp of when user's `public` or `trusted` was last updated
* `status`: state of the account
* `username`: unique string used in `basic` authentication; username is not accessible to other users
* `defacs`: object describing user's default access mode for peer to peer conversations with authenticated and anonymous users; see [Access control](#access-control) for details
* `auth`: default access mode for authenticated `auth` users
* `anon`: default access for anonymous `anon` users
* `trusted`: an application-defined object issued by the system administration. Anyone can read it but only system administrators can change it.
* `public`: an application-defined object that describes the user. Anyone can query user for `public` data.
* `private`: an application-defined object that is unique to the current user and accessible only by the user.
* `tags`: [discovery](#fnd-and-tags-finding-users-and-topics) and credentials.
User's account has a state. The following states are defined:
* `ok` (normal): the default state which means the account is not restricted in any way and can be used normally;
* `susp` (suspended): the user is prevented from accessing the account as well as not found through [search](#fnd-and-tags-finding-users-and-topics); the state can be assigned by the administrator and fully reversible.
* `del` (soft-deleted): user is marked as deleted but user's data is retained; un-deleting the user is not currenly supported.
* `undef` (undefined): used internally by authenticators; should not be used elsewhere.
A user may maintain multiple simultaneous connections (sessions) with the server. Each session is tagged with a client-provided `User Agent` string intended to differentiate client software.
Logging out is not supported by design. If an application needs to change the user, it should open a new connection and authenticate it with the new user credentials.
### Authentication
Authentication is conceptually similar to [SASL](https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer): it's provided as a set of adapters each implementing a different authentication method. Authenticators are used during account registration [`{acc}`](#acc) and during [`{login}`](#login). The server comes with the following authentication methods out of the box:
* `token` provides authentication by a cryptographic token.
* `basic` provides authentication by a login-password pair.
* `anonymous` is designed for cases where users are temporary, such as handling customer support requests through chat.
* `rest` is a [meta-method](../server/auth/rest/) which allows use of external authentication systems by means of JSON RPC.
Any other authentication method can be implemented using adapters.
The `token` is intended to be the primary means of authentication. Tokens are designed in such a way that token authentication is light weight. For instance, token authenticator generally does not make any database calls, all processing is done in-memory. All other authentication methods are intended to be used only to obtain or refresh the token. Once the token is obtained, subsequent logins should use it.
The `basic` authentication scheme expects `secret` to be a base64-encoded string of a string composed of a user name followed by a colon `:` followed by a plan text password. User name in the `basic` scheme must not contain the colon character `:` (ASCII 0x3A).
The `anonymous` scheme can be used to create accounts, it cannot be used for logging in: a user creates an account using `anonymous` scheme and obtains a cryptographic token which it uses for subsequent `token` logins. If the token is lost or expired, the user is no longer able to access the account.
Compiled-in authenticator names may be changed by using `logical_names` configuration feature. For example, a custom `rest` authenticator may be exposed as `basic` instead of default one or `token` authenticator could be hidden from users. The feature is activated by providing an array of mappings in the config file: `logical_name:actual_name` to rename or `actual_name:` to hide. For instance, to use a `rest` service for basic authentication use `"logical_names": ["basic:rest"]`.
#### Creating an Account
When a new account is created, the user must inform the server which authentication method will be later used to gain access to this account as well as provide shared secret, if appropriate. Only `basic` and `anonymous` can be used during account creation. The `basic` requires the user to generate and send a unique login and password to the server. The `anonymous` does not exchange secrets.
User may optionally set `{acc login=true}` to use the new account for immediate authentication. When `login=false` (or not set), the new account is created but the authentication status of the session which created the account remains unchanged. When `login=true` the server will attempt to authenticate the session with the new account, the `{ctrl}` response to the `{acc}` request will contain the authentication token on success. This is particularly important for the `anonymous` authentication because that's the only time when the authentication token can be retrieved.
#### Logging in
Logging in is performed by issuing a `{login}` request. Logging in is possible with `basic` and `token` only. Response to any login is a `{ctrl}` message with either a code 200 and a token which can be used in subsequent logins with `token` authentication, or a code 300 request for additional information, such as verifying credentials or responding to a method-dependent challenge in multi-step authentication, or a code 4xx error.
Token has server-configured expiration time so it needs to be periodically refreshed.
#### Changing Authentication Parameters
User may change authentication parameters, such as changing login and password, by issuing an `{acc}` request. Only `basic` authentication currently supports changing parameters:
```js
acc: {
id: "1a2b3", // string, client-provided message id, optional
user: "usr2il9suCbuko", // user being affected by the change, optional
token: "XMg...g1Gp8+BO0=", // authentication token if the session
// is not yet authenticated, optional.
scheme: "basic", // authentication scheme being updated.
secret: base64encode("new_username:new_password") // new parameters
}
```
In order to change just the password, `username` should be left empty, i.e. `secret: base64encode(":new_password")`.
If the session is not authenticated, the request must include a `token`. It can be a regular authentication token obtained during login, or a restricted token received through [Resetting a Password](#resetting-a-password) process. If the session is authenticated, the token must not be included. If the request is authenticated for access level `ROOT`, then the `user` may be set to a valid ID of another user. Otherwise it must be blank (defaulting to the current user) or equal to the ID of the current user.
#### Resetting a Password, i.e. "Forgot Password"
To reset login or password, (or any other authentication secret, if such action is supported by the authenticator), one sends a `{login}` message with the `scheme` set to `reset` and the `secret` containing a base64-encoded string "`authentication scheme to reset secret for`:`reset method`:`reset method value`". Most basic case of resetting a password by email is
```js
login: {
id: "1a2b3",
scheme: "reset",
secret: base64encode("basic:email:jdoe@example.com")
}
```
where `jdoe@example.com` is an earlier validated user's email.
If the email matches the registration, the server will send a message using specified method and address with instructions for resetting the secret. The email contains a restricted security token which the user can include into an `{acc}` request with the new secret as described in [Changing Authentication Parameters](#changing-authentication-parameters).
### Suspending a User
User's account can be suspended by service administrator. Once the account is suspended, the user is no longer able to login and use the service.
Only the `root` user may suspend the account. To suspend the account the root user sends the following message:
```js
acc: {
id: "1a2b3", // string, client-provided message id, optional
user: "usr2il9suCbuko", // user being affected by the change
status: "susp"
}
```
Sending the same message with `status: "ok"` un-suspends the account. A root user may check account status by executing `{get what="desc"}` command against user's `me` topic.
### Credential Validation
Server may be optionally configured to require validation of certain credentials associated with the user accounts and authentication scheme. For instance, it's possible to require user to provide a unique email or a phone number, or to solve a captcha as a condition of account registration.
The server supports verification of email out of the box with just a configuration change. is mostly functional, verification of phone numbers is not functional because a commercial subscription is needed in order to be able to send text messages (SMS).
If certain credentials are required, then user must maintain
gitextract_oztk1vft/
├── .gitattributes
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
├── CONTRIBUTING.md
├── INSTALL.md
├── LICENSE
├── README.md
├── README_ko.md
├── SECURITY.md
├── build-all.sh
├── build-py-grpc.sh
├── chatbot/
│ ├── LICENSE
│ ├── README.md
│ ├── csharp/
│ │ └── README.md
│ └── python/
│ ├── .gitignore
│ ├── README.md
│ ├── basic-cookie.sample
│ ├── chatbot.py
│ ├── quotes.txt
│ ├── requirements.txt
│ ├── setup.py
│ └── token-cookie.sample
├── docker/
│ ├── README.md
│ ├── chatbot/
│ │ └── Dockerfile
│ ├── docker-compose/
│ │ ├── README.md
│ │ ├── cluster.mongodb.yml
│ │ ├── cluster.postgres.yml
│ │ ├── cluster.rethinkdb.yml
│ │ ├── cluster.yml
│ │ ├── single-instance.mongodb.yml
│ │ ├── single-instance.postgres.yml
│ │ ├── single-instance.rethinkdb.yml
│ │ └── single-instance.yml
│ ├── exporter/
│ │ ├── Dockerfile
│ │ └── entrypoint.sh
│ └── tinode/
│ ├── Dockerfile
│ ├── config.template
│ └── entrypoint.sh
├── docker-build.sh
├── docker-release.sh
├── docs/
│ ├── API.md
│ ├── CLA.md
│ ├── call-establishment.md
│ ├── drafty.md
│ ├── faq.md
│ ├── monitoring.md
│ ├── thecard.md
│ └── translations.md
├── go.mod
├── go.sum
├── keygen/
│ ├── README.md
│ └── keygen.go
├── loadtest/
│ ├── LICENSE
│ ├── README.md
│ ├── loadtest.scala
│ ├── tinode.beam
│ ├── tinode.erl
│ ├── tinode.scala
│ ├── tsung.xml
│ └── users.csv
├── monitoring/
│ ├── LICENSE
│ ├── README.md
│ └── exporter/
│ ├── README.md
│ ├── build.sh
│ ├── influxdb_exporter.go
│ ├── main.go
│ ├── prom_exporter.go
│ └── scraper.go
├── pbx/
│ ├── README.md
│ ├── go-generate.sh
│ ├── model.pb.go
│ ├── model.proto
│ ├── model_grpc.pb.go
│ ├── py-generate.sh
│ └── py_fix.py
├── py_grpc/
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── pyproject.toml
│ ├── tinode_grpc/
│ │ ├── __init__.py
│ │ ├── model_pb2.py
│ │ ├── model_pb2.pyi
│ │ └── model_pb2_grpc.py
│ └── version.py
├── rest-auth/
│ ├── README.md
│ ├── auth.py
│ ├── dummy_data.json
│ └── requirements.txt
├── server/
│ ├── .golangci.yml
│ ├── api_key.go
│ ├── auth/
│ │ ├── anon/
│ │ │ └── auth_anon.go
│ │ ├── auth.go
│ │ ├── basic/
│ │ │ └── auth_basic.go
│ │ ├── code/
│ │ │ └── auth_code.go
│ │ ├── mock_auth/
│ │ │ └── mock_auth.go
│ │ ├── rest/
│ │ │ ├── README.md
│ │ │ └── auth_rest.go
│ │ └── token/
│ │ └── auth_token.go
│ ├── calls.go
│ ├── cluster.go
│ ├── cluster_leader.go
│ ├── concurrency/
│ │ ├── goroutinepool.go
│ │ └── simplemutex.go
│ ├── datamodel.go
│ ├── db/
│ │ ├── adapter.go
│ │ ├── common/
│ │ │ ├── common.go
│ │ │ ├── common_test.go
│ │ │ └── test_data/
│ │ │ └── test_data.go
│ │ ├── mongodb/
│ │ │ ├── adapter.go
│ │ │ ├── blank.go
│ │ │ ├── schema.md
│ │ │ └── tests/
│ │ │ ├── mongo_test.go
│ │ │ └── test.conf
│ │ ├── mysql/
│ │ │ ├── adapter.go
│ │ │ ├── blank.go
│ │ │ ├── schema.sql
│ │ │ └── tests/
│ │ │ ├── mysql_test.go
│ │ │ └── test.conf
│ │ ├── postgres/
│ │ │ ├── adapter.go
│ │ │ ├── blank.go
│ │ │ ├── schema.sql
│ │ │ └── tests/
│ │ │ ├── postgres_test.go
│ │ │ └── test.conf
│ │ └── rethinkdb/
│ │ ├── adapter.go
│ │ ├── blank.go
│ │ ├── schema.md
│ │ └── tests/
│ │ ├── rethink_test.go
│ │ └── test.conf
│ ├── drafty/
│ │ ├── drafty.go
│ │ ├── drafty_test.go
│ │ └── grapheme.go
│ ├── hdl_files.go
│ ├── hdl_grpc.go
│ ├── hdl_longpoll.go
│ ├── hdl_websock.go
│ ├── http.go
│ ├── http_pprof.go
│ ├── hub.go
│ ├── init_topic.go
│ ├── logs/
│ │ └── logs.go
│ ├── main.go
│ ├── media/
│ │ ├── fs/
│ │ │ └── filesys.go
│ │ ├── media.go
│ │ ├── media_test.go
│ │ └── s3/
│ │ └── s3.go
│ ├── pbconverter.go
│ ├── plugins.go
│ ├── pres.go
│ ├── push/
│ │ ├── common/
│ │ │ └── typedef.go
│ │ ├── fcm/
│ │ │ ├── README.md
│ │ │ ├── payload.go
│ │ │ └── push_fcm.go
│ │ ├── push.go
│ │ ├── stdout/
│ │ │ ├── README.md
│ │ │ └── push_stdout.go
│ │ └── tnpg/
│ │ ├── README.md
│ │ └── push_tnpg.go
│ ├── push.go
│ ├── ringhash/
│ │ ├── ringhash.go
│ │ └── ringhash_test.go
│ ├── run-cluster.sh
│ ├── sanity-test.sh
│ ├── session.go
│ ├── session_test.go
│ ├── sessionstore.go
│ ├── stats.go
│ ├── store/
│ │ ├── mock_store/
│ │ │ └── mock_store.go
│ │ ├── store.go
│ │ └── types/
│ │ ├── types.go
│ │ ├── uidgen.go
│ │ └── uidgen_test.go
│ ├── templ/
│ │ ├── email-password-reset-en.templ
│ │ ├── email-password-reset-es.templ
│ │ ├── email-password-reset-fr.templ
│ │ ├── email-password-reset-pt.templ
│ │ ├── email-password-reset-ru.templ
│ │ ├── email-password-reset-uk.templ
│ │ ├── email-password-reset-vi.templ
│ │ ├── email-password-reset-zh-TW.templ
│ │ ├── email-password-reset-zh.templ
│ │ ├── email-validation-en.templ
│ │ ├── email-validation-es.templ
│ │ ├── email-validation-fr.templ
│ │ ├── email-validation-pt.templ
│ │ ├── email-validation-ru.templ
│ │ ├── email-validation-uk.templ
│ │ ├── email-validation-vi.templ
│ │ ├── email-validation-zh-TW.templ
│ │ ├── email-validation-zh.templ
│ │ ├── sms-universal-en.templ
│ │ ├── sms-universal-es.templ
│ │ ├── sms-universal-fr.templ
│ │ ├── sms-universal-pt.templ
│ │ ├── sms-universal-ru.templ
│ │ ├── sms-universal-uk.templ
│ │ ├── sms-universal-vi.templ
│ │ ├── sms-universal-zh-TW.templ
│ │ └── sms-universal-zh.templ
│ ├── tinode.conf
│ ├── topic.go
│ ├── topic_proxy.go
│ ├── topic_test.go
│ ├── user.go
│ ├── utils.go
│ ├── utils_test.go
│ └── validate/
│ ├── email/
│ │ └── validate.go
│ ├── tel/
│ │ ├── twilio.go
│ │ └── validate.go
│ └── validator.go
├── tinode-db/
│ ├── README.md
│ ├── credentials.sh
│ ├── data.json
│ ├── gendb.go
│ ├── generate_dataset.py
│ ├── main.go
│ └── tinode.conf
└── tn-cli/
├── CODE-STRUCTURE.md
├── LICENSE
├── README.md
├── client.py
├── commands.py
├── input_handler.py
├── macros.py
├── requirements.txt
├── sample-macro-script.txt
├── sample-script.txt
├── tn-cli.py
├── tn_globals.py
└── utils.py
Showing preview only (302K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3531 symbols across 97 files)
FILE: chatbot/python/chatbot.py
function log (line 54) | def log(*args):
function add_future (line 58) | def add_future(tid, bundle):
function clip_long_string (line 62) | def clip_long_string(obj):
function to_json (line 74) | def to_json(msg):
function exec_future (line 78) | def exec_future(tid, code, text, params):
function add_subscription (line 96) | def add_subscription(topic):
function del_subscription (line 99) | def del_subscription(topic):
function subscription_failed (line 102) | def subscription_failed(topic, errcode):
function login_error (line 111) | def login_error(unused, errcode):
function server_version (line 116) | def server_version(params):
function next_id (line 121) | def next_id():
function next_quote (line 129) | def next_quote():
class Plugin (line 139) | class Plugin(pbx.PluginServicer):
method Account (line 140) | def Account(self, acc_event, context):
function client_generate (line 159) | def client_generate():
function client_post (line 167) | def client_post(msg):
function client_reset (line 170) | def client_reset():
function hello (line 178) | def hello():
function login (line 187) | def login(cookie_file_name, scheme, secret):
function subscribe (line 196) | def subscribe(topic):
function leave (line 205) | def leave(topic):
function publish (line 213) | def publish(topic, text):
function note_read (line 218) | def note_read(topic, seq):
function init_server (line 221) | def init_server(listen):
function init_client (line 232) | def init_client(addr, schema, secret, cookie_file_name, secure, ssl_host):
function client_message_loop (line 252) | def client_message_loop(stream):
function read_auth_cookie (line 292) | def read_auth_cookie(cookie_file_name):
function on_login (line 307) | def on_login(cookie_file_name, params):
function load_quotes (line 334) | def load_quotes(file_name):
function run (line 341) | def run(args):
FILE: chatbot/python/setup.py
function git_version (line 25) | def git_version():
FILE: keygen/keygen.go
function main (line 23) | func main() {
constant APIKEY_VERSION (line 44) | APIKEY_VERSION = 1
constant APIKEY_APPID (line 46) | APIKEY_APPID = 4
constant APIKEY_SEQUENCE (line 48) | APIKEY_SEQUENCE = 2
constant APIKEY_WHO (line 50) | APIKEY_WHO = 1
constant APIKEY_SIGNATURE (line 52) | APIKEY_SIGNATURE = 16
constant APIKEY_LENGTH (line 54) | APIKEY_LENGTH = APIKEY_VERSION + APIKEY_APPID + APIKEY_SEQUENCE + APIKEY...
function generate (line 57) | func generate(sequence, isRoot int, hmacSaltB64 string) int {
function validate (line 111) | func validate(apikey string, hmacSaltB64 string) int {
FILE: monitoring/exporter/influxdb_exporter.go
type InfluxDBExporter (line 15) | type InfluxDBExporter struct
method Push (line 41) | func (e *InfluxDBExporter) Push() error {
function NewInfluxDBExporter (line 25) | func NewInfluxDBExporter(influxDBVersion, pushBaseAddress, organization,
function formPushTargetAddress (line 86) | func formPushTargetAddress(influxDBVersion, baseAddr, organization, buck...
function formAuthorizationHeaderValue (line 110) | func formAuthorizationHeaderValue(influxDBVersion, token string) string {
FILE: monitoring/exporter/main.go
type monitoringService (line 16) | type monitoringService
constant promService (line 19) | promService monitoringService = 1
constant influxService (line 20) | influxService monitoringService = 2
constant minPushInterval (line 25) | minPushInterval = 10
type promHTTPLogger (line 28) | type promHTTPLogger struct
method Println (line 30) | func (l promHTTPLogger) Println(v ...interface{}) {
function parseMetricList (line 34) | func parseMetricList(list string) []string {
function main (line 57) | func main() {
FILE: monitoring/exporter/prom_exporter.go
type PromExporter (line 11) | type PromExporter struct
method Describe (line 214) | func (e *PromExporter) Describe(ch chan<- *prometheus.Desc) {
method Collect (line 251) | func (e *PromExporter) Collect(ch chan<- prometheus.Metric) {
method parseStats (line 265) | func (e *PromExporter) parseStats(ch chan<- prometheus.Metric, stats m...
method parseAndUpdate (line 303) | func (e *PromExporter) parseAndUpdate(ch chan<- prometheus.Metric, des...
method parseAndUpdateHisto (line 313) | func (e *PromExporter) parseAndUpdateHisto(ch chan<- prometheus.Metric...
function NewPromExporter (line 53) | func NewPromExporter(server, namespace string, timeout time.Duration, sc...
function firstError (line 323) | func firstError(errs ...error) error {
FILE: monitoring/exporter/scraper.go
type Scraper (line 12) | type Scraper struct
method CollectRaw (line 33) | func (s *Scraper) CollectRaw() (map[string]interface{}, error) {
method Scrape (line 48) | func (s *Scraper) Scrape() (map[string]interface{}, error) {
method parseStatsRaw (line 61) | func (s *Scraper) parseStatsRaw(stats map[string]interface{}) (map[str...
type histogram (line 22) | type histogram struct
function parseHisto (line 83) | func parseHisto(stats map[string]interface{}, key string) (*histogram, e...
function parseList (line 115) | func parseList(stats map[string]interface{}, path string) ([]float64, er...
function parseNumeric (line 133) | func parseNumeric(stats map[string]interface{}, path string) (float64, e...
function parseMetric (line 147) | func parseMetric(stats map[string]interface{}, path string) (interface{}...
FILE: pbx/model.pb.go
constant _ (line 18) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
constant _ (line 20) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
type AuthLevel (line 24) | type AuthLevel
method Enum (line 49) | func (x AuthLevel) Enum() *AuthLevel {
method String (line 55) | func (x AuthLevel) String() string {
method Descriptor (line 59) | func (AuthLevel) Descriptor() protoreflect.EnumDescriptor {
method Type (line 63) | func (AuthLevel) Type() protoreflect.EnumType {
method Number (line 67) | func (x AuthLevel) Number() protoreflect.EnumNumber {
method EnumDescriptor (line 72) | func (AuthLevel) EnumDescriptor() ([]byte, []int) {
constant AuthLevel_NONE (line 27) | AuthLevel_NONE AuthLevel = 0
constant AuthLevel_ANON (line 28) | AuthLevel_ANON AuthLevel = 10
constant AuthLevel_AUTH (line 29) | AuthLevel_AUTH AuthLevel = 20
constant AuthLevel_ROOT (line 30) | AuthLevel_ROOT AuthLevel = 30
type InfoNote (line 76) | type InfoNote
method Enum (line 105) | func (x InfoNote) Enum() *InfoNote {
method String (line 111) | func (x InfoNote) String() string {
method Descriptor (line 115) | func (InfoNote) Descriptor() protoreflect.EnumDescriptor {
method Type (line 119) | func (InfoNote) Type() protoreflect.EnumType {
method Number (line 123) | func (x InfoNote) Number() protoreflect.EnumNumber {
method EnumDescriptor (line 128) | func (InfoNote) EnumDescriptor() ([]byte, []int) {
constant InfoNote_X1 (line 80) | InfoNote_X1 InfoNote = 0
constant InfoNote_READ (line 81) | InfoNote_READ InfoNote = 1
constant InfoNote_RECV (line 82) | InfoNote_RECV InfoNote = 2
constant InfoNote_KP (line 83) | InfoNote_KP InfoNote = 3
constant InfoNote_CALL (line 84) | InfoNote_CALL InfoNote = 4
type CallEvent (line 132) | type CallEvent
method Enum (line 170) | func (x CallEvent) Enum() *CallEvent {
method String (line 176) | func (x CallEvent) String() string {
method Descriptor (line 180) | func (CallEvent) Descriptor() protoreflect.EnumDescriptor {
method Type (line 184) | func (CallEvent) Type() protoreflect.EnumType {
method Number (line 188) | func (x CallEvent) Number() protoreflect.EnumNumber {
method EnumDescriptor (line 193) | func (CallEvent) EnumDescriptor() ([]byte, []int) {
constant CallEvent_X2 (line 136) | CallEvent_X2 CallEvent = 0
constant CallEvent_ACCEPT (line 137) | CallEvent_ACCEPT CallEvent = 1
constant CallEvent_ANSWER (line 138) | CallEvent_ANSWER CallEvent = 2
constant CallEvent_HANG_UP (line 139) | CallEvent_HANG_UP CallEvent = 3
constant CallEvent_ICE_CANDIDATE (line 140) | CallEvent_ICE_CANDIDATE CallEvent = 4
constant CallEvent_INVITE (line 141) | CallEvent_INVITE CallEvent = 5
constant CallEvent_OFFER (line 142) | CallEvent_OFFER CallEvent = 6
constant CallEvent_RINGING (line 143) | CallEvent_RINGING CallEvent = 7
type RespCode (line 198) | type RespCode
method Enum (line 229) | func (x RespCode) Enum() *RespCode {
method String (line 235) | func (x RespCode) String() string {
method Descriptor (line 239) | func (RespCode) Descriptor() protoreflect.EnumDescriptor {
method Type (line 243) | func (RespCode) Type() protoreflect.EnumType {
method Number (line 247) | func (x RespCode) Number() protoreflect.EnumNumber {
method EnumDescriptor (line 252) | func (RespCode) EnumDescriptor() ([]byte, []int) {
constant RespCode_CONTINUE (line 202) | RespCode_CONTINUE RespCode = 0
constant RespCode_DROP (line 204) | RespCode_DROP RespCode = 1
constant RespCode_RESPOND (line 207) | RespCode_RESPOND RespCode = 2
constant RespCode_REPLACE (line 210) | RespCode_REPLACE RespCode = 3
type Crud (line 256) | type Crud
method Enum (line 278) | func (x Crud) Enum() *Crud {
method String (line 284) | func (x Crud) String() string {
method Descriptor (line 288) | func (Crud) Descriptor() protoreflect.EnumDescriptor {
method Type (line 292) | func (Crud) Type() protoreflect.EnumType {
method Number (line 296) | func (x Crud) Number() protoreflect.EnumNumber {
method EnumDescriptor (line 301) | func (Crud) EnumDescriptor() ([]byte, []int) {
constant Crud_CREATE (line 259) | Crud_CREATE Crud = 0
constant Crud_UPDATE (line 260) | Crud_UPDATE Crud = 1
constant Crud_DELETE (line 261) | Crud_DELETE Crud = 2
type ClientDel_What (line 307) | type ClientDel_What
method Enum (line 339) | func (x ClientDel_What) Enum() *ClientDel_What {
method String (line 345) | func (x ClientDel_What) String() string {
method Descriptor (line 349) | func (ClientDel_What) Descriptor() protoreflect.EnumDescriptor {
method Type (line 353) | func (ClientDel_What) Type() protoreflect.EnumType {
method Number (line 357) | func (x ClientDel_What) Number() protoreflect.EnumNumber {
method EnumDescriptor (line 362) | func (ClientDel_What) EnumDescriptor() ([]byte, []int) {
constant ClientDel_X0 (line 311) | ClientDel_X0 ClientDel_What = 0
constant ClientDel_MSG (line 312) | ClientDel_MSG ClientDel_What = 1
constant ClientDel_TOPIC (line 313) | ClientDel_TOPIC ClientDel_What = 2
constant ClientDel_SUB (line 314) | ClientDel_SUB ClientDel_What = 3
constant ClientDel_USER (line 315) | ClientDel_USER ClientDel_What = 4
constant ClientDel_CRED (line 316) | ClientDel_CRED ClientDel_What = 5
type ServerPres_What (line 366) | type ServerPres_What
method Enum (line 422) | func (x ServerPres_What) Enum() *ServerPres_What {
method String (line 428) | func (x ServerPres_What) String() string {
method Descriptor (line 432) | func (ServerPres_What) Descriptor() protoreflect.EnumDescriptor {
method Type (line 436) | func (ServerPres_What) Type() protoreflect.EnumType {
method Number (line 440) | func (x ServerPres_What) Number() protoreflect.EnumNumber {
method EnumDescriptor (line 445) | func (ServerPres_What) EnumDescriptor() ([]byte, []int) {
constant ServerPres_X3 (line 370) | ServerPres_X3 ServerPres_What = 0
constant ServerPres_ON (line 371) | ServerPres_ON ServerPres_What = 1
constant ServerPres_OFF (line 372) | ServerPres_OFF ServerPres_What = 2
constant ServerPres_UA (line 373) | ServerPres_UA ServerPres_What = 3
constant ServerPres_UPD (line 374) | ServerPres_UPD ServerPres_What = 4
constant ServerPres_GONE (line 375) | ServerPres_GONE ServerPres_What = 5
constant ServerPres_ACS (line 376) | ServerPres_ACS ServerPres_What = 6
constant ServerPres_TERM (line 377) | ServerPres_TERM ServerPres_What = 7
constant ServerPres_MSG (line 378) | ServerPres_MSG ServerPres_What = 8
constant ServerPres_READ (line 379) | ServerPres_READ ServerPres_What = 9
constant ServerPres_RECV (line 380) | ServerPres_RECV ServerPres_What = 10
constant ServerPres_DEL (line 381) | ServerPres_DEL ServerPres_What = 11
constant ServerPres_TAGS (line 382) | ServerPres_TAGS ServerPres_What = 12
constant ServerPres_AUX (line 383) | ServerPres_AUX ServerPres_What = 13
type Unused (line 450) | type Unused struct
method Reset (line 456) | func (x *Unused) Reset() {
method String (line 465) | func (x *Unused) String() string {
method ProtoMessage (line 469) | func (*Unused) ProtoMessage() {}
method ProtoReflect (line 471) | func (x *Unused) ProtoReflect() protoreflect.Message {
method Descriptor (line 484) | func (*Unused) Descriptor() ([]byte, []int) {
type DefaultAcsMode (line 489) | type DefaultAcsMode struct
method Reset (line 498) | func (x *DefaultAcsMode) Reset() {
method String (line 507) | func (x *DefaultAcsMode) String() string {
method ProtoMessage (line 511) | func (*DefaultAcsMode) ProtoMessage() {}
method ProtoReflect (line 513) | func (x *DefaultAcsMode) ProtoReflect() protoreflect.Message {
method Descriptor (line 526) | func (*DefaultAcsMode) Descriptor() ([]byte, []int) {
method GetAuth (line 530) | func (x *DefaultAcsMode) GetAuth() string {
method GetAnon (line 537) | func (x *DefaultAcsMode) GetAnon() string {
type AccessMode (line 545) | type AccessMode struct
method Reset (line 556) | func (x *AccessMode) Reset() {
method String (line 565) | func (x *AccessMode) String() string {
method ProtoMessage (line 569) | func (*AccessMode) ProtoMessage() {}
method ProtoReflect (line 571) | func (x *AccessMode) ProtoReflect() protoreflect.Message {
method Descriptor (line 584) | func (*AccessMode) Descriptor() ([]byte, []int) {
method GetWant (line 588) | func (x *AccessMode) GetWant() string {
method GetGiven (line 595) | func (x *AccessMode) GetGiven() string {
type SetSub (line 603) | type SetSub struct
method Reset (line 614) | func (x *SetSub) Reset() {
method String (line 623) | func (x *SetSub) String() string {
method ProtoMessage (line 627) | func (*SetSub) ProtoMessage() {}
method ProtoReflect (line 629) | func (x *SetSub) ProtoReflect() protoreflect.Message {
method Descriptor (line 642) | func (*SetSub) Descriptor() ([]byte, []int) {
method GetUserId (line 646) | func (x *SetSub) GetUserId() string {
method GetMode (line 653) | func (x *SetSub) GetMode() string {
type ClientCred (line 661) | type ClientCred struct
method Reset (line 676) | func (x *ClientCred) Reset() {
method String (line 685) | func (x *ClientCred) String() string {
method ProtoMessage (line 689) | func (*ClientCred) ProtoMessage() {}
method ProtoReflect (line 691) | func (x *ClientCred) ProtoReflect() protoreflect.Message {
method Descriptor (line 704) | func (*ClientCred) Descriptor() ([]byte, []int) {
method GetMethod (line 708) | func (x *ClientCred) GetMethod() string {
method GetValue (line 715) | func (x *ClientCred) GetValue() string {
method GetResponse (line 722) | func (x *ClientCred) GetResponse() string {
method GetParams (line 729) | func (x *ClientCred) GetParams() map[string][]byte {
type SetDesc (line 737) | type SetDesc struct
method Reset (line 748) | func (x *SetDesc) Reset() {
method String (line 757) | func (x *SetDesc) String() string {
method ProtoMessage (line 761) | func (*SetDesc) ProtoMessage() {}
method ProtoReflect (line 763) | func (x *SetDesc) ProtoReflect() protoreflect.Message {
method Descriptor (line 776) | func (*SetDesc) Descriptor() ([]byte, []int) {
method GetDefaultAcs (line 780) | func (x *SetDesc) GetDefaultAcs() *DefaultAcsMode {
method GetPublic (line 787) | func (x *SetDesc) GetPublic() []byte {
method GetPrivate (line 794) | func (x *SetDesc) GetPrivate() []byte {
method GetTrusted (line 801) | func (x *SetDesc) GetTrusted() []byte {
type SeqRange (line 808) | type SeqRange struct
method Reset (line 817) | func (x *SeqRange) Reset() {
method String (line 826) | func (x *SeqRange) String() string {
method ProtoMessage (line 830) | func (*SeqRange) ProtoMessage() {}
method ProtoReflect (line 832) | func (x *SeqRange) ProtoReflect() protoreflect.Message {
method Descriptor (line 845) | func (*SeqRange) Descriptor() ([]byte, []int) {
method GetLow (line 849) | func (x *SeqRange) GetLow() int32 {
method GetHi (line 856) | func (x *SeqRange) GetHi() int32 {
type GetOpts (line 863) | type GetOpts struct
method Reset (line 884) | func (x *GetOpts) Reset() {
method String (line 893) | func (x *GetOpts) String() string {
method ProtoMessage (line 897) | func (*GetOpts) ProtoMessage() {}
method ProtoReflect (line 899) | func (x *GetOpts) ProtoReflect() protoreflect.Message {
method Descriptor (line 912) | func (*GetOpts) Descriptor() ([]byte, []int) {
method GetIfModifiedSince (line 916) | func (x *GetOpts) GetIfModifiedSince() int64 {
method GetUser (line 923) | func (x *GetOpts) GetUser() string {
method GetTopic (line 930) | func (x *GetOpts) GetTopic() string {
method GetSinceId (line 937) | func (x *GetOpts) GetSinceId() int32 {
method GetBeforeId (line 944) | func (x *GetOpts) GetBeforeId() int32 {
method GetLimit (line 951) | func (x *GetOpts) GetLimit() int32 {
method GetRanges (line 958) | func (x *GetOpts) GetRanges() []*SeqRange {
type GetQuery (line 965) | type GetQuery struct
method Reset (line 979) | func (x *GetQuery) Reset() {
method String (line 988) | func (x *GetQuery) String() string {
method ProtoMessage (line 992) | func (*GetQuery) ProtoMessage() {}
method ProtoReflect (line 994) | func (x *GetQuery) ProtoReflect() protoreflect.Message {
method Descriptor (line 1007) | func (*GetQuery) Descriptor() ([]byte, []int) {
method GetWhat (line 1011) | func (x *GetQuery) GetWhat() string {
method GetDesc (line 1018) | func (x *GetQuery) GetDesc() *GetOpts {
method GetSub (line 1025) | func (x *GetQuery) GetSub() *GetOpts {
method GetData (line 1032) | func (x *GetQuery) GetData() *GetOpts {
type SetQuery (line 1039) | type SetQuery struct
method Reset (line 1056) | func (x *SetQuery) Reset() {
method String (line 1065) | func (x *SetQuery) String() string {
method ProtoMessage (line 1069) | func (*SetQuery) ProtoMessage() {}
method ProtoReflect (line 1071) | func (x *SetQuery) ProtoReflect() protoreflect.Message {
method Descriptor (line 1084) | func (*SetQuery) Descriptor() ([]byte, []int) {
method GetDesc (line 1088) | func (x *SetQuery) GetDesc() *SetDesc {
method GetSub (line 1095) | func (x *SetQuery) GetSub() *SetSub {
method GetTags (line 1102) | func (x *SetQuery) GetTags() []string {
method GetCred (line 1109) | func (x *SetQuery) GetCred() *ClientCred {
method GetAux (line 1116) | func (x *SetQuery) GetAux() map[string][]byte {
type ClientHi (line 1124) | type ClientHi struct
method Reset (line 1138) | func (x *ClientHi) Reset() {
method String (line 1147) | func (x *ClientHi) String() string {
method ProtoMessage (line 1151) | func (*ClientHi) ProtoMessage() {}
method ProtoReflect (line 1153) | func (x *ClientHi) ProtoReflect() protoreflect.Message {
method Descriptor (line 1166) | func (*ClientHi) Descriptor() ([]byte, []int) {
method GetId (line 1170) | func (x *ClientHi) GetId() string {
method GetUserAgent (line 1177) | func (x *ClientHi) GetUserAgent() string {
method GetVer (line 1184) | func (x *ClientHi) GetVer() string {
method GetDeviceId (line 1191) | func (x *ClientHi) GetDeviceId() string {
method GetLang (line 1198) | func (x *ClientHi) GetLang() string {
method GetPlatform (line 1205) | func (x *ClientHi) GetPlatform() string {
method GetBackground (line 1212) | func (x *ClientHi) GetBackground() bool {
type ClientAcc (line 1220) | type ClientAcc struct
method Reset (line 1251) | func (x *ClientAcc) Reset() {
method String (line 1260) | func (x *ClientAcc) String() string {
method ProtoMessage (line 1264) | func (*ClientAcc) ProtoMessage() {}
method ProtoReflect (line 1266) | func (x *ClientAcc) ProtoReflect() protoreflect.Message {
method Descriptor (line 1279) | func (*ClientAcc) Descriptor() ([]byte, []int) {
method GetId (line 1283) | func (x *ClientAcc) GetId() string {
method GetUserId (line 1290) | func (x *ClientAcc) GetUserId() string {
method GetScheme (line 1297) | func (x *ClientAcc) GetScheme() string {
method GetSecret (line 1304) | func (x *ClientAcc) GetSecret() []byte {
method GetLogin (line 1311) | func (x *ClientAcc) GetLogin() bool {
method GetTags (line 1318) | func (x *ClientAcc) GetTags() []string {
method GetDesc (line 1325) | func (x *ClientAcc) GetDesc() *SetDesc {
method GetCred (line 1332) | func (x *ClientAcc) GetCred() []*ClientCred {
method GetToken (line 1339) | func (x *ClientAcc) GetToken() []byte {
method GetState (line 1346) | func (x *ClientAcc) GetState() string {
method GetAuthLevel (line 1353) | func (x *ClientAcc) GetAuthLevel() AuthLevel {
method GetTmpScheme (line 1360) | func (x *ClientAcc) GetTmpScheme() string {
method GetTmpSecret (line 1367) | func (x *ClientAcc) GetTmpSecret() []byte {
type ClientLogin (line 1375) | type ClientLogin struct
method Reset (line 1389) | func (x *ClientLogin) Reset() {
method String (line 1398) | func (x *ClientLogin) String() string {
method ProtoMessage (line 1402) | func (*ClientLogin) ProtoMessage() {}
method ProtoReflect (line 1404) | func (x *ClientLogin) ProtoReflect() protoreflect.Message {
method Descriptor (line 1417) | func (*ClientLogin) Descriptor() ([]byte, []int) {
method GetId (line 1421) | func (x *ClientLogin) GetId() string {
method GetScheme (line 1428) | func (x *ClientLogin) GetScheme() string {
method GetSecret (line 1435) | func (x *ClientLogin) GetSecret() []byte {
method GetCred (line 1442) | func (x *ClientLogin) GetCred() []*ClientCred {
type ClientSub (line 1450) | type ClientSub struct
method Reset (line 1463) | func (x *ClientSub) Reset() {
method String (line 1472) | func (x *ClientSub) String() string {
method ProtoMessage (line 1476) | func (*ClientSub) ProtoMessage() {}
method ProtoReflect (line 1478) | func (x *ClientSub) ProtoReflect() protoreflect.Message {
method Descriptor (line 1491) | func (*ClientSub) Descriptor() ([]byte, []int) {
method GetId (line 1495) | func (x *ClientSub) GetId() string {
method GetTopic (line 1502) | func (x *ClientSub) GetTopic() string {
method GetSetQuery (line 1509) | func (x *ClientSub) GetSetQuery() *SetQuery {
method GetGetQuery (line 1516) | func (x *ClientSub) GetGetQuery() *GetQuery {
type ClientLeave (line 1524) | type ClientLeave struct
method Reset (line 1534) | func (x *ClientLeave) Reset() {
method String (line 1543) | func (x *ClientLeave) String() string {
method ProtoMessage (line 1547) | func (*ClientLeave) ProtoMessage() {}
method ProtoReflect (line 1549) | func (x *ClientLeave) ProtoReflect() protoreflect.Message {
method Descriptor (line 1562) | func (*ClientLeave) Descriptor() ([]byte, []int) {
method GetId (line 1566) | func (x *ClientLeave) GetId() string {
method GetTopic (line 1573) | func (x *ClientLeave) GetTopic() string {
method GetUnsub (line 1580) | func (x *ClientLeave) GetUnsub() bool {
type ClientPub (line 1588) | type ClientPub struct
method Reset (line 1600) | func (x *ClientPub) Reset() {
method String (line 1609) | func (x *ClientPub) String() string {
method ProtoMessage (line 1613) | func (*ClientPub) ProtoMessage() {}
method ProtoReflect (line 1615) | func (x *ClientPub) ProtoReflect() protoreflect.Message {
method Descriptor (line 1628) | func (*ClientPub) Descriptor() ([]byte, []int) {
method GetId (line 1632) | func (x *ClientPub) GetId() string {
method GetTopic (line 1639) | func (x *ClientPub) GetTopic() string {
method GetNoEcho (line 1646) | func (x *ClientPub) GetNoEcho() bool {
method GetHead (line 1653) | func (x *ClientPub) GetHead() map[string][]byte {
method GetContent (line 1660) | func (x *ClientPub) GetContent() []byte {
type ClientGet (line 1668) | type ClientGet struct
method Reset (line 1678) | func (x *ClientGet) Reset() {
method String (line 1687) | func (x *ClientGet) String() string {
method ProtoMessage (line 1691) | func (*ClientGet) ProtoMessage() {}
method ProtoReflect (line 1693) | func (x *ClientGet) ProtoReflect() protoreflect.Message {
method Descriptor (line 1706) | func (*ClientGet) Descriptor() ([]byte, []int) {
method GetId (line 1710) | func (x *ClientGet) GetId() string {
method GetTopic (line 1717) | func (x *ClientGet) GetTopic() string {
method GetQuery (line 1724) | func (x *ClientGet) GetQuery() *GetQuery {
type ClientSet (line 1732) | type ClientSet struct
method Reset (line 1742) | func (x *ClientSet) Reset() {
method String (line 1751) | func (x *ClientSet) String() string {
method ProtoMessage (line 1755) | func (*ClientSet) ProtoMessage() {}
method ProtoReflect (line 1757) | func (x *ClientSet) ProtoReflect() protoreflect.Message {
method Descriptor (line 1770) | func (*ClientSet) Descriptor() ([]byte, []int) {
method GetId (line 1774) | func (x *ClientSet) GetId() string {
method GetTopic (line 1781) | func (x *ClientSet) GetTopic() string {
method GetQuery (line 1788) | func (x *ClientSet) GetQuery() *SetQuery {
type ClientDel (line 1796) | type ClientDel struct
method Reset (line 1814) | func (x *ClientDel) Reset() {
method String (line 1823) | func (x *ClientDel) String() string {
method ProtoMessage (line 1827) | func (*ClientDel) ProtoMessage() {}
method ProtoReflect (line 1829) | func (x *ClientDel) ProtoReflect() protoreflect.Message {
method Descriptor (line 1842) | func (*ClientDel) Descriptor() ([]byte, []int) {
method GetId (line 1846) | func (x *ClientDel) GetId() string {
method GetTopic (line 1853) | func (x *ClientDel) GetTopic() string {
method GetWhat (line 1860) | func (x *ClientDel) GetWhat() ClientDel_What {
method GetDelSeq (line 1867) | func (x *ClientDel) GetDelSeq() []*SeqRange {
method GetUserId (line 1874) | func (x *ClientDel) GetUserId() string {
method GetCred (line 1881) | func (x *ClientDel) GetCred() *ClientCred {
method GetHard (line 1888) | func (x *ClientDel) GetHard() bool {
type ClientNote (line 1896) | type ClientNote struct
method Reset (line 1915) | func (x *ClientNote) Reset() {
method String (line 1924) | func (x *ClientNote) String() string {
method ProtoMessage (line 1928) | func (*ClientNote) ProtoMessage() {}
method ProtoReflect (line 1930) | func (x *ClientNote) ProtoReflect() protoreflect.Message {
method Descriptor (line 1943) | func (*ClientNote) Descriptor() ([]byte, []int) {
method GetTopic (line 1947) | func (x *ClientNote) GetTopic() string {
method GetWhat (line 1954) | func (x *ClientNote) GetWhat() InfoNote {
method GetSeqId (line 1961) | func (x *ClientNote) GetSeqId() int32 {
method GetUnread (line 1968) | func (x *ClientNote) GetUnread() int32 {
method GetEvent (line 1975) | func (x *ClientNote) GetEvent() CallEvent {
method GetPayload (line 1982) | func (x *ClientNote) GetPayload() []byte {
type ClientExtra (line 1989) | type ClientExtra struct
method Reset (line 2000) | func (x *ClientExtra) Reset() {
method String (line 2009) | func (x *ClientExtra) String() string {
method ProtoMessage (line 2013) | func (*ClientExtra) ProtoMessage() {}
method ProtoReflect (line 2015) | func (x *ClientExtra) ProtoReflect() protoreflect.Message {
method Descriptor (line 2028) | func (*ClientExtra) Descriptor() ([]byte, []int) {
method GetAttachments (line 2032) | func (x *ClientExtra) GetAttachments() []string {
method GetOnBehalfOf (line 2039) | func (x *ClientExtra) GetOnBehalfOf() string {
method GetAuthLevel (line 2046) | func (x *ClientExtra) GetAuthLevel() AuthLevel {
type ClientMsg (line 2053) | type ClientMsg struct
method Reset (line 2074) | func (x *ClientMsg) Reset() {
method String (line 2083) | func (x *ClientMsg) String() string {
method ProtoMessage (line 2087) | func (*ClientMsg) ProtoMessage() {}
method ProtoReflect (line 2089) | func (x *ClientMsg) ProtoReflect() protoreflect.Message {
method Descriptor (line 2102) | func (*ClientMsg) Descriptor() ([]byte, []int) {
method GetMessage (line 2106) | func (m *ClientMsg) GetMessage() isClientMsg_Message {
method GetHi (line 2113) | func (x *ClientMsg) GetHi() *ClientHi {
method GetAcc (line 2120) | func (x *ClientMsg) GetAcc() *ClientAcc {
method GetLogin (line 2127) | func (x *ClientMsg) GetLogin() *ClientLogin {
method GetSub (line 2134) | func (x *ClientMsg) GetSub() *ClientSub {
method GetLeave (line 2141) | func (x *ClientMsg) GetLeave() *ClientLeave {
method GetPub (line 2148) | func (x *ClientMsg) GetPub() *ClientPub {
method GetGet (line 2155) | func (x *ClientMsg) GetGet() *ClientGet {
method GetSet (line 2162) | func (x *ClientMsg) GetSet() *ClientSet {
method GetDel (line 2169) | func (x *ClientMsg) GetDel() *ClientDel {
method GetNote (line 2176) | func (x *ClientMsg) GetNote() *ClientNote {
method GetExtra (line 2183) | func (x *ClientMsg) GetExtra() *ClientExtra {
type isClientMsg_Message (line 2190) | type isClientMsg_Message interface
type ClientMsg_Hi (line 2194) | type ClientMsg_Hi struct
method isClientMsg_Message (line 2234) | func (*ClientMsg_Hi) isClientMsg_Message() {}
type ClientMsg_Acc (line 2198) | type ClientMsg_Acc struct
method isClientMsg_Message (line 2236) | func (*ClientMsg_Acc) isClientMsg_Message() {}
type ClientMsg_Login (line 2202) | type ClientMsg_Login struct
method isClientMsg_Message (line 2238) | func (*ClientMsg_Login) isClientMsg_Message() {}
type ClientMsg_Sub (line 2206) | type ClientMsg_Sub struct
method isClientMsg_Message (line 2240) | func (*ClientMsg_Sub) isClientMsg_Message() {}
type ClientMsg_Leave (line 2210) | type ClientMsg_Leave struct
method isClientMsg_Message (line 2242) | func (*ClientMsg_Leave) isClientMsg_Message() {}
type ClientMsg_Pub (line 2214) | type ClientMsg_Pub struct
method isClientMsg_Message (line 2244) | func (*ClientMsg_Pub) isClientMsg_Message() {}
type ClientMsg_Get (line 2218) | type ClientMsg_Get struct
method isClientMsg_Message (line 2246) | func (*ClientMsg_Get) isClientMsg_Message() {}
type ClientMsg_Set (line 2222) | type ClientMsg_Set struct
method isClientMsg_Message (line 2248) | func (*ClientMsg_Set) isClientMsg_Message() {}
type ClientMsg_Del (line 2226) | type ClientMsg_Del struct
method isClientMsg_Message (line 2250) | func (*ClientMsg_Del) isClientMsg_Message() {}
type ClientMsg_Note (line 2230) | type ClientMsg_Note struct
method isClientMsg_Message (line 2252) | func (*ClientMsg_Note) isClientMsg_Message() {}
type ServerCred (line 2255) | type ServerCred struct
method Reset (line 2268) | func (x *ServerCred) Reset() {
method String (line 2277) | func (x *ServerCred) String() string {
method ProtoMessage (line 2281) | func (*ServerCred) ProtoMessage() {}
method ProtoReflect (line 2283) | func (x *ServerCred) ProtoReflect() protoreflect.Message {
method Descriptor (line 2296) | func (*ServerCred) Descriptor() ([]byte, []int) {
method GetMethod (line 2300) | func (x *ServerCred) GetMethod() string {
method GetValue (line 2307) | func (x *ServerCred) GetValue() string {
method GetDone (line 2314) | func (x *ServerCred) GetDone() bool {
type TopicDesc (line 2322) | type TopicDesc struct
method Reset (line 2348) | func (x *TopicDesc) Reset() {
method String (line 2357) | func (x *TopicDesc) String() string {
method ProtoMessage (line 2361) | func (*TopicDesc) ProtoMessage() {}
method ProtoReflect (line 2363) | func (x *TopicDesc) ProtoReflect() protoreflect.Message {
method Descriptor (line 2376) | func (*TopicDesc) Descriptor() ([]byte, []int) {
method GetCreatedAt (line 2380) | func (x *TopicDesc) GetCreatedAt() int64 {
method GetUpdatedAt (line 2387) | func (x *TopicDesc) GetUpdatedAt() int64 {
method GetTouchedAt (line 2394) | func (x *TopicDesc) GetTouchedAt() int64 {
method GetDefacs (line 2401) | func (x *TopicDesc) GetDefacs() *DefaultAcsMode {
method GetAcs (line 2408) | func (x *TopicDesc) GetAcs() *AccessMode {
method GetSeqId (line 2415) | func (x *TopicDesc) GetSeqId() int32 {
method GetReadId (line 2422) | func (x *TopicDesc) GetReadId() int32 {
method GetRecvId (line 2429) | func (x *TopicDesc) GetRecvId() int32 {
method GetDelId (line 2436) | func (x *TopicDesc) GetDelId() int32 {
method GetPublic (line 2443) | func (x *TopicDesc) GetPublic() []byte {
method GetPrivate (line 2450) | func (x *TopicDesc) GetPrivate() []byte {
method GetState (line 2457) | func (x *TopicDesc) GetState() string {
method GetStateAt (line 2464) | func (x *TopicDesc) GetStateAt() int64 {
method GetTrusted (line 2471) | func (x *TopicDesc) GetTrusted() []byte {
method GetIsChan (line 2478) | func (x *TopicDesc) GetIsChan() bool {
method GetOnline (line 2485) | func (x *TopicDesc) GetOnline() bool {
method GetLastSeenTime (line 2492) | func (x *TopicDesc) GetLastSeenTime() int64 {
method GetLastSeenUserAgent (line 2499) | func (x *TopicDesc) GetLastSeenUserAgent() string {
type TopicSub (line 2507) | type TopicSub struct
method Reset (line 2535) | func (x *TopicSub) Reset() {
method String (line 2544) | func (x *TopicSub) String() string {
method ProtoMessage (line 2548) | func (*TopicSub) ProtoMessage() {}
method ProtoReflect (line 2550) | func (x *TopicSub) ProtoReflect() protoreflect.Message {
method Descriptor (line 2563) | func (*TopicSub) Descriptor() ([]byte, []int) {
method GetUpdatedAt (line 2567) | func (x *TopicSub) GetUpdatedAt() int64 {
method GetDeletedAt (line 2574) | func (x *TopicSub) GetDeletedAt() int64 {
method GetOnline (line 2581) | func (x *TopicSub) GetOnline() bool {
method GetAcs (line 2588) | func (x *TopicSub) GetAcs() *AccessMode {
method GetReadId (line 2595) | func (x *TopicSub) GetReadId() int32 {
method GetRecvId (line 2602) | func (x *TopicSub) GetRecvId() int32 {
method GetPublic (line 2609) | func (x *TopicSub) GetPublic() []byte {
method GetTrusted (line 2616) | func (x *TopicSub) GetTrusted() []byte {
method GetPrivate (line 2623) | func (x *TopicSub) GetPrivate() []byte {
method GetUserId (line 2630) | func (x *TopicSub) GetUserId() string {
method GetTopic (line 2637) | func (x *TopicSub) GetTopic() string {
method GetTouchedAt (line 2644) | func (x *TopicSub) GetTouchedAt() int64 {
method GetSeqId (line 2651) | func (x *TopicSub) GetSeqId() int32 {
method GetDelId (line 2658) | func (x *TopicSub) GetDelId() int32 {
method GetLastSeenTime (line 2665) | func (x *TopicSub) GetLastSeenTime() int64 {
method GetLastSeenUserAgent (line 2672) | func (x *TopicSub) GetLastSeenUserAgent() string {
type DelValues (line 2679) | type DelValues struct
method Reset (line 2688) | func (x *DelValues) Reset() {
method String (line 2697) | func (x *DelValues) String() string {
method ProtoMessage (line 2701) | func (*DelValues) ProtoMessage() {}
method ProtoReflect (line 2703) | func (x *DelValues) ProtoReflect() protoreflect.Message {
method Descriptor (line 2716) | func (*DelValues) Descriptor() ([]byte, []int) {
method GetDelId (line 2720) | func (x *DelValues) GetDelId() int32 {
method GetDelSeq (line 2727) | func (x *DelValues) GetDelSeq() []*SeqRange {
type ServerCtrl (line 2735) | type ServerCtrl struct
method Reset (line 2747) | func (x *ServerCtrl) Reset() {
method String (line 2756) | func (x *ServerCtrl) String() string {
method ProtoMessage (line 2760) | func (*ServerCtrl) ProtoMessage() {}
method ProtoReflect (line 2762) | func (x *ServerCtrl) ProtoReflect() protoreflect.Message {
method Descriptor (line 2775) | func (*ServerCtrl) Descriptor() ([]byte, []int) {
method GetId (line 2779) | func (x *ServerCtrl) GetId() string {
method GetTopic (line 2786) | func (x *ServerCtrl) GetTopic() string {
method GetCode (line 2793) | func (x *ServerCtrl) GetCode() int32 {
method GetText (line 2800) | func (x *ServerCtrl) GetText() string {
method GetParams (line 2807) | func (x *ServerCtrl) GetParams() map[string][]byte {
type ServerData (line 2815) | type ServerData struct
method Reset (line 2832) | func (x *ServerData) Reset() {
method String (line 2841) | func (x *ServerData) String() string {
method ProtoMessage (line 2845) | func (*ServerData) ProtoMessage() {}
method ProtoReflect (line 2847) | func (x *ServerData) ProtoReflect() protoreflect.Message {
method Descriptor (line 2860) | func (*ServerData) Descriptor() ([]byte, []int) {
method GetTopic (line 2864) | func (x *ServerData) GetTopic() string {
method GetFromUserId (line 2871) | func (x *ServerData) GetFromUserId() string {
method GetTimestamp (line 2878) | func (x *ServerData) GetTimestamp() int64 {
method GetDeletedAt (line 2885) | func (x *ServerData) GetDeletedAt() int64 {
method GetSeqId (line 2892) | func (x *ServerData) GetSeqId() int32 {
method GetHead (line 2899) | func (x *ServerData) GetHead() map[string][]byte {
method GetContent (line 2906) | func (x *ServerData) GetContent() []byte {
type ServerPres (line 2914) | type ServerPres struct
method Reset (line 2931) | func (x *ServerPres) Reset() {
method String (line 2940) | func (x *ServerPres) String() string {
method ProtoMessage (line 2944) | func (*ServerPres) ProtoMessage() {}
method ProtoReflect (line 2946) | func (x *ServerPres) ProtoReflect() protoreflect.Message {
method Descriptor (line 2959) | func (*ServerPres) Descriptor() ([]byte, []int) {
method GetTopic (line 2963) | func (x *ServerPres) GetTopic() string {
method GetSrc (line 2970) | func (x *ServerPres) GetSrc() string {
method GetWhat (line 2977) | func (x *ServerPres) GetWhat() ServerPres_What {
method GetUserAgent (line 2984) | func (x *ServerPres) GetUserAgent() string {
method GetSeqId (line 2991) | func (x *ServerPres) GetSeqId() int32 {
method GetDelId (line 2998) | func (x *ServerPres) GetDelId() int32 {
method GetDelSeq (line 3005) | func (x *ServerPres) GetDelSeq() []*SeqRange {
method GetTargetUserId (line 3012) | func (x *ServerPres) GetTargetUserId() string {
method GetActorUserId (line 3019) | func (x *ServerPres) GetActorUserId() string {
method GetAcs (line 3026) | func (x *ServerPres) GetAcs() *AccessMode {
type ServerMeta (line 3034) | type ServerMeta struct
method Reset (line 3049) | func (x *ServerMeta) Reset() {
method String (line 3058) | func (x *ServerMeta) String() string {
method ProtoMessage (line 3062) | func (*ServerMeta) ProtoMessage() {}
method ProtoReflect (line 3064) | func (x *ServerMeta) ProtoReflect() protoreflect.Message {
method Descriptor (line 3077) | func (*ServerMeta) Descriptor() ([]byte, []int) {
method GetId (line 3081) | func (x *ServerMeta) GetId() string {
method GetTopic (line 3088) | func (x *ServerMeta) GetTopic() string {
method GetDesc (line 3095) | func (x *ServerMeta) GetDesc() *TopicDesc {
method GetSub (line 3102) | func (x *ServerMeta) GetSub() []*TopicSub {
method GetDel (line 3109) | func (x *ServerMeta) GetDel() *DelValues {
method GetTags (line 3116) | func (x *ServerMeta) GetTags() []string {
method GetCred (line 3123) | func (x *ServerMeta) GetCred() []*ServerCred {
method GetAux (line 3130) | func (x *ServerMeta) GetAux() map[string][]byte {
type ServerInfo (line 3138) | type ServerInfo struct
method Reset (line 3152) | func (x *ServerInfo) Reset() {
method String (line 3161) | func (x *ServerInfo) String() string {
method ProtoMessage (line 3165) | func (*ServerInfo) ProtoMessage() {}
method ProtoReflect (line 3167) | func (x *ServerInfo) ProtoReflect() protoreflect.Message {
method Descriptor (line 3180) | func (*ServerInfo) Descriptor() ([]byte, []int) {
method GetTopic (line 3184) | func (x *ServerInfo) GetTopic() string {
method GetFromUserId (line 3191) | func (x *ServerInfo) GetFromUserId() string {
method GetWhat (line 3198) | func (x *ServerInfo) GetWhat() InfoNote {
method GetSeqId (line 3205) | func (x *ServerInfo) GetSeqId() int32 {
method GetSrc (line 3212) | func (x *ServerInfo) GetSrc() string {
method GetEvent (line 3219) | func (x *ServerInfo) GetEvent() CallEvent {
method GetPayload (line 3226) | func (x *ServerInfo) GetPayload() []byte {
type ServerMsg (line 3234) | type ServerMsg struct
method Reset (line 3253) | func (x *ServerMsg) Reset() {
method String (line 3262) | func (x *ServerMsg) String() string {
method ProtoMessage (line 3266) | func (*ServerMsg) ProtoMessage() {}
method ProtoReflect (line 3268) | func (x *ServerMsg) ProtoReflect() protoreflect.Message {
method Descriptor (line 3281) | func (*ServerMsg) Descriptor() ([]byte, []int) {
method GetMessage (line 3285) | func (m *ServerMsg) GetMessage() isServerMsg_Message {
method GetCtrl (line 3292) | func (x *ServerMsg) GetCtrl() *ServerCtrl {
method GetData (line 3299) | func (x *ServerMsg) GetData() *ServerData {
method GetPres (line 3306) | func (x *ServerMsg) GetPres() *ServerPres {
method GetMeta (line 3313) | func (x *ServerMsg) GetMeta() *ServerMeta {
method GetInfo (line 3320) | func (x *ServerMsg) GetInfo() *ServerInfo {
method GetTopic (line 3328) | func (x *ServerMsg) GetTopic() string {
type isServerMsg_Message (line 3335) | type isServerMsg_Message interface
type ServerMsg_Ctrl (line 3339) | type ServerMsg_Ctrl struct
method isServerMsg_Message (line 3359) | func (*ServerMsg_Ctrl) isServerMsg_Message() {}
type ServerMsg_Data (line 3343) | type ServerMsg_Data struct
method isServerMsg_Message (line 3361) | func (*ServerMsg_Data) isServerMsg_Message() {}
type ServerMsg_Pres (line 3347) | type ServerMsg_Pres struct
method isServerMsg_Message (line 3363) | func (*ServerMsg_Pres) isServerMsg_Message() {}
type ServerMsg_Meta (line 3351) | type ServerMsg_Meta struct
method isServerMsg_Message (line 3365) | func (*ServerMsg_Meta) isServerMsg_Message() {}
type ServerMsg_Info (line 3355) | type ServerMsg_Info struct
method isServerMsg_Message (line 3367) | func (*ServerMsg_Info) isServerMsg_Message() {}
type ServerResp (line 3369) | type ServerResp struct
method Reset (line 3379) | func (x *ServerResp) Reset() {
method String (line 3388) | func (x *ServerResp) String() string {
method ProtoMessage (line 3392) | func (*ServerResp) ProtoMessage() {}
method ProtoReflect (line 3394) | func (x *ServerResp) ProtoReflect() protoreflect.Message {
method Descriptor (line 3407) | func (*ServerResp) Descriptor() ([]byte, []int) {
method GetStatus (line 3411) | func (x *ServerResp) GetStatus() RespCode {
method GetSrvmsg (line 3418) | func (x *ServerResp) GetSrvmsg() *ServerMsg {
method GetClmsg (line 3425) | func (x *ServerResp) GetClmsg() *ClientMsg {
type Session (line 3433) | type Session struct
method Reset (line 3447) | func (x *Session) Reset() {
method String (line 3456) | func (x *Session) String() string {
method ProtoMessage (line 3460) | func (*Session) ProtoMessage() {}
method ProtoReflect (line 3462) | func (x *Session) ProtoReflect() protoreflect.Message {
method Descriptor (line 3475) | func (*Session) Descriptor() ([]byte, []int) {
method GetSessionId (line 3479) | func (x *Session) GetSessionId() string {
method GetUserId (line 3486) | func (x *Session) GetUserId() string {
method GetAuthLevel (line 3493) | func (x *Session) GetAuthLevel() AuthLevel {
method GetRemoteAddr (line 3500) | func (x *Session) GetRemoteAddr() string {
method GetUserAgent (line 3507) | func (x *Session) GetUserAgent() string {
method GetDeviceId (line 3514) | func (x *Session) GetDeviceId() string {
method GetLanguage (line 3521) | func (x *Session) GetLanguage() string {
type ClientReq (line 3528) | type ClientReq struct
method Reset (line 3537) | func (x *ClientReq) Reset() {
method String (line 3546) | func (x *ClientReq) String() string {
method ProtoMessage (line 3550) | func (*ClientReq) ProtoMessage() {}
method ProtoReflect (line 3552) | func (x *ClientReq) ProtoReflect() protoreflect.Message {
method Descriptor (line 3565) | func (*ClientReq) Descriptor() ([]byte, []int) {
method GetMsg (line 3569) | func (x *ClientReq) GetMsg() *ClientMsg {
method GetSess (line 3576) | func (x *ClientReq) GetSess() *Session {
type SearchQuery (line 3583) | type SearchQuery struct
method Reset (line 3592) | func (x *SearchQuery) Reset() {
method String (line 3601) | func (x *SearchQuery) String() string {
method ProtoMessage (line 3605) | func (*SearchQuery) ProtoMessage() {}
method ProtoReflect (line 3607) | func (x *SearchQuery) ProtoReflect() protoreflect.Message {
method Descriptor (line 3620) | func (*SearchQuery) Descriptor() ([]byte, []int) {
method GetUserId (line 3624) | func (x *SearchQuery) GetUserId() string {
method GetQuery (line 3631) | func (x *SearchQuery) GetQuery() string {
type SearchFound (line 3638) | type SearchFound struct
method Reset (line 3650) | func (x *SearchFound) Reset() {
method String (line 3659) | func (x *SearchFound) String() string {
method ProtoMessage (line 3663) | func (*SearchFound) ProtoMessage() {}
method ProtoReflect (line 3665) | func (x *SearchFound) ProtoReflect() protoreflect.Message {
method Descriptor (line 3678) | func (*SearchFound) Descriptor() ([]byte, []int) {
method GetStatus (line 3682) | func (x *SearchFound) GetStatus() RespCode {
method GetQuery (line 3689) | func (x *SearchFound) GetQuery() string {
method GetResult (line 3696) | func (x *SearchFound) GetResult() []*TopicSub {
type TopicEvent (line 3703) | type TopicEvent struct
method Reset (line 3713) | func (x *TopicEvent) Reset() {
method String (line 3722) | func (x *TopicEvent) String() string {
method ProtoMessage (line 3726) | func (*TopicEvent) ProtoMessage() {}
method ProtoReflect (line 3728) | func (x *TopicEvent) ProtoReflect() protoreflect.Message {
method Descriptor (line 3741) | func (*TopicEvent) Descriptor() ([]byte, []int) {
method GetAction (line 3745) | func (x *TopicEvent) GetAction() Crud {
method GetName (line 3752) | func (x *TopicEvent) GetName() string {
method GetDesc (line 3759) | func (x *TopicEvent) GetDesc() *TopicDesc {
type AccountEvent (line 3766) | type AccountEvent struct
method Reset (line 3779) | func (x *AccountEvent) Reset() {
method String (line 3788) | func (x *AccountEvent) String() string {
method ProtoMessage (line 3792) | func (*AccountEvent) ProtoMessage() {}
method ProtoReflect (line 3794) | func (x *AccountEvent) ProtoReflect() protoreflect.Message {
method Descriptor (line 3807) | func (*AccountEvent) Descriptor() ([]byte, []int) {
method GetAction (line 3811) | func (x *AccountEvent) GetAction() Crud {
method GetUserId (line 3818) | func (x *AccountEvent) GetUserId() string {
method GetDefaultAcs (line 3825) | func (x *AccountEvent) GetDefaultAcs() *DefaultAcsMode {
method GetPublic (line 3832) | func (x *AccountEvent) GetPublic() []byte {
method GetTags (line 3839) | func (x *AccountEvent) GetTags() []string {
type SubscriptionEvent (line 3846) | type SubscriptionEvent struct
method Reset (line 3861) | func (x *SubscriptionEvent) Reset() {
method String (line 3870) | func (x *SubscriptionEvent) String() string {
method ProtoMessage (line 3874) | func (*SubscriptionEvent) ProtoMessage() {}
method ProtoReflect (line 3876) | func (x *SubscriptionEvent) ProtoReflect() protoreflect.Message {
method Descriptor (line 3889) | func (*SubscriptionEvent) Descriptor() ([]byte, []int) {
method GetAction (line 3893) | func (x *SubscriptionEvent) GetAction() Crud {
method GetTopic (line 3900) | func (x *SubscriptionEvent) GetTopic() string {
method GetUserId (line 3907) | func (x *SubscriptionEvent) GetUserId() string {
method GetDelId (line 3914) | func (x *SubscriptionEvent) GetDelId() int32 {
method GetReadId (line 3921) | func (x *SubscriptionEvent) GetReadId() int32 {
method GetRecvId (line 3928) | func (x *SubscriptionEvent) GetRecvId() int32 {
method GetMode (line 3935) | func (x *SubscriptionEvent) GetMode() *AccessMode {
method GetPrivate (line 3942) | func (x *SubscriptionEvent) GetPrivate() []byte {
type MessageEvent (line 3949) | type MessageEvent struct
method Reset (line 3958) | func (x *MessageEvent) Reset() {
method String (line 3967) | func (x *MessageEvent) String() string {
method ProtoMessage (line 3971) | func (*MessageEvent) ProtoMessage() {}
method ProtoReflect (line 3973) | func (x *MessageEvent) ProtoReflect() protoreflect.Message {
method Descriptor (line 3986) | func (*MessageEvent) Descriptor() ([]byte, []int) {
method GetAction (line 3990) | func (x *MessageEvent) GetAction() Crud {
method GetMsg (line 3997) | func (x *MessageEvent) GetMsg() *ServerData {
type Auth (line 4004) | type Auth struct
method Reset (line 4013) | func (x *Auth) Reset() {
method String (line 4022) | func (x *Auth) String() string {
method ProtoMessage (line 4026) | func (*Auth) ProtoMessage() {}
method ProtoReflect (line 4028) | func (x *Auth) ProtoReflect() protoreflect.Message {
method Descriptor (line 4041) | func (*Auth) Descriptor() ([]byte, []int) {
method GetScheme (line 4045) | func (x *Auth) GetScheme() string {
method GetSecret (line 4052) | func (x *Auth) GetSecret() string {
type FileMeta (line 4060) | type FileMeta struct
method Reset (line 4071) | func (x *FileMeta) Reset() {
method String (line 4080) | func (x *FileMeta) String() string {
method ProtoMessage (line 4084) | func (*FileMeta) ProtoMessage() {}
method ProtoReflect (line 4086) | func (x *FileMeta) ProtoReflect() protoreflect.Message {
method Descriptor (line 4099) | func (*FileMeta) Descriptor() ([]byte, []int) {
method GetName (line 4103) | func (x *FileMeta) GetName() string {
method GetMimeType (line 4110) | func (x *FileMeta) GetMimeType() string {
method GetEtag (line 4117) | func (x *FileMeta) GetEtag() string {
method GetSize (line 4124) | func (x *FileMeta) GetSize() int64 {
type FileUpReq (line 4132) | type FileUpReq struct
method Reset (line 4149) | func (x *FileUpReq) Reset() {
method String (line 4158) | func (x *FileUpReq) String() string {
method ProtoMessage (line 4162) | func (*FileUpReq) ProtoMessage() {}
method ProtoReflect (line 4164) | func (x *FileUpReq) ProtoReflect() protoreflect.Message {
method Descriptor (line 4177) | func (*FileUpReq) Descriptor() ([]byte, []int) {
method GetId (line 4181) | func (x *FileUpReq) GetId() string {
method GetAuth (line 4188) | func (x *FileUpReq) GetAuth() *Auth {
method GetTopic (line 4195) | func (x *FileUpReq) GetTopic() string {
method GetMeta (line 4202) | func (x *FileUpReq) GetMeta() *FileMeta {
method GetContent (line 4209) | func (x *FileUpReq) GetContent() []byte {
type FileUpResp (line 4217) | type FileUpResp struct
method Reset (line 4233) | func (x *FileUpResp) Reset() {
method String (line 4242) | func (x *FileUpResp) String() string {
method ProtoMessage (line 4246) | func (*FileUpResp) ProtoMessage() {}
method ProtoReflect (line 4248) | func (x *FileUpResp) ProtoReflect() protoreflect.Message {
method Descriptor (line 4261) | func (*FileUpResp) Descriptor() ([]byte, []int) {
method GetId (line 4265) | func (x *FileUpResp) GetId() string {
method GetCode (line 4272) | func (x *FileUpResp) GetCode() int32 {
method GetText (line 4279) | func (x *FileUpResp) GetText() string {
method GetMeta (line 4286) | func (x *FileUpResp) GetMeta() *FileMeta {
method GetRedirUrl (line 4293) | func (x *FileUpResp) GetRedirUrl() string {
type FileDownReq (line 4301) | type FileDownReq struct
method Reset (line 4316) | func (x *FileDownReq) Reset() {
method String (line 4325) | func (x *FileDownReq) String() string {
method ProtoMessage (line 4329) | func (*FileDownReq) ProtoMessage() {}
method ProtoReflect (line 4331) | func (x *FileDownReq) ProtoReflect() protoreflect.Message {
method Descriptor (line 4344) | func (*FileDownReq) Descriptor() ([]byte, []int) {
method GetId (line 4348) | func (x *FileDownReq) GetId() string {
method GetAuth (line 4355) | func (x *FileDownReq) GetAuth() *Auth {
method GetUri (line 4362) | func (x *FileDownReq) GetUri() string {
method GetIfModified (line 4369) | func (x *FileDownReq) GetIfModified() string {
type FileDownResp (line 4377) | type FileDownResp struct
method Reset (line 4395) | func (x *FileDownResp) Reset() {
method String (line 4404) | func (x *FileDownResp) String() string {
method ProtoMessage (line 4408) | func (*FileDownResp) ProtoMessage() {}
method ProtoReflect (line 4410) | func (x *FileDownResp) ProtoReflect() protoreflect.Message {
method Descriptor (line 4423) | func (*FileDownResp) Descriptor() ([]byte, []int) {
method GetId (line 4427) | func (x *FileDownResp) GetId() string {
method GetCode (line 4434) | func (x *FileDownResp) GetCode() int32 {
method GetText (line 4441) | func (x *FileDownResp) GetText() string {
method GetMeta (line 4448) | func (x *FileDownResp) GetMeta() *FileMeta {
method GetRedirUrl (line 4455) | func (x *FileDownResp) GetRedirUrl() string {
method GetContent (line 4462) | func (x *FileDownResp) GetContent() []byte {
function file_model_proto_rawDescGZIP (line 5051) | func file_model_proto_rawDescGZIP() []byte {
function init (line 5226) | func init() { file_model_proto_init() }
function file_model_proto_init (line 5227) | func file_model_proto_init() {
FILE: pbx/model_grpc.pb.go
constant _ (line 19) | _ = grpc.SupportPackageIsVersion7
type NodeClient (line 24) | type NodeClient interface
type nodeClient (line 33) | type nodeClient struct
method MessageLoop (line 41) | func (c *nodeClient) MessageLoop(ctx context.Context, opts ...grpc.Cal...
method LargeFileReceive (line 72) | func (c *nodeClient) LargeFileReceive(ctx context.Context, opts ...grp...
method LargeFileServe (line 106) | func (c *nodeClient) LargeFileServe(ctx context.Context, in *FileDownR...
function NewNodeClient (line 37) | func NewNodeClient(cc grpc.ClientConnInterface) NodeClient {
type Node_MessageLoopClient (line 50) | type Node_MessageLoopClient interface
type nodeMessageLoopClient (line 56) | type nodeMessageLoopClient struct
method Send (line 60) | func (x *nodeMessageLoopClient) Send(m *ClientMsg) error {
method Recv (line 64) | func (x *nodeMessageLoopClient) Recv() (*ServerMsg, error) {
type Node_LargeFileReceiveClient (line 81) | type Node_LargeFileReceiveClient interface
type nodeLargeFileReceiveClient (line 87) | type nodeLargeFileReceiveClient struct
method Send (line 91) | func (x *nodeLargeFileReceiveClient) Send(m *FileUpReq) error {
method CloseAndRecv (line 95) | func (x *nodeLargeFileReceiveClient) CloseAndRecv() (*FileUpResp, erro...
type Node_LargeFileServeClient (line 121) | type Node_LargeFileServeClient interface
type nodeLargeFileServeClient (line 126) | type nodeLargeFileServeClient struct
method Recv (line 130) | func (x *nodeLargeFileServeClient) Recv() (*FileDownResp, error) {
type NodeServer (line 141) | type NodeServer interface
type UnimplementedNodeServer (line 152) | type UnimplementedNodeServer struct
method MessageLoop (line 155) | func (UnimplementedNodeServer) MessageLoop(Node_MessageLoopServer) err...
method LargeFileReceive (line 158) | func (UnimplementedNodeServer) LargeFileReceive(Node_LargeFileReceiveS...
method LargeFileServe (line 161) | func (UnimplementedNodeServer) LargeFileServe(*FileDownReq, Node_Large...
method mustEmbedUnimplementedNodeServer (line 164) | func (UnimplementedNodeServer) mustEmbedUnimplementedNodeServer() {}
type UnsafeNodeServer (line 169) | type UnsafeNodeServer interface
function RegisterNodeServer (line 173) | func RegisterNodeServer(s grpc.ServiceRegistrar, srv NodeServer) {
function _Node_MessageLoop_Handler (line 177) | func _Node_MessageLoop_Handler(srv interface{}, stream grpc.ServerStream...
type Node_MessageLoopServer (line 181) | type Node_MessageLoopServer interface
type nodeMessageLoopServer (line 187) | type nodeMessageLoopServer struct
method Send (line 191) | func (x *nodeMessageLoopServer) Send(m *ServerMsg) error {
method Recv (line 195) | func (x *nodeMessageLoopServer) Recv() (*ClientMsg, error) {
function _Node_LargeFileReceive_Handler (line 203) | func _Node_LargeFileReceive_Handler(srv interface{}, stream grpc.ServerS...
type Node_LargeFileReceiveServer (line 207) | type Node_LargeFileReceiveServer interface
type nodeLargeFileReceiveServer (line 213) | type nodeLargeFileReceiveServer struct
method SendAndClose (line 217) | func (x *nodeLargeFileReceiveServer) SendAndClose(m *FileUpResp) error {
method Recv (line 221) | func (x *nodeLargeFileReceiveServer) Recv() (*FileUpReq, error) {
function _Node_LargeFileServe_Handler (line 229) | func _Node_LargeFileServe_Handler(srv interface{}, stream grpc.ServerStr...
type Node_LargeFileServeServer (line 237) | type Node_LargeFileServeServer interface
type nodeLargeFileServeServer (line 242) | type nodeLargeFileServeServer struct
method Send (line 246) | func (x *nodeLargeFileServeServer) Send(m *FileDownResp) error {
type PluginClient (line 281) | type PluginClient interface
type pluginClient (line 299) | type pluginClient struct
method FireHose (line 307) | func (c *pluginClient) FireHose(ctx context.Context, in *ClientReq, op...
method Find (line 316) | func (c *pluginClient) Find(ctx context.Context, in *SearchQuery, opts...
method Account (line 325) | func (c *pluginClient) Account(ctx context.Context, in *AccountEvent, ...
method Topic (line 334) | func (c *pluginClient) Topic(ctx context.Context, in *TopicEvent, opts...
method Subscription (line 343) | func (c *pluginClient) Subscription(ctx context.Context, in *Subscript...
method Message (line 352) | func (c *pluginClient) Message(ctx context.Context, in *MessageEvent, ...
function NewPluginClient (line 303) | func NewPluginClient(cc grpc.ClientConnInterface) PluginClient {
type PluginServer (line 364) | type PluginServer interface
type UnimplementedPluginServer (line 384) | type UnimplementedPluginServer struct
method FireHose (line 387) | func (UnimplementedPluginServer) FireHose(context.Context, *ClientReq)...
method Find (line 390) | func (UnimplementedPluginServer) Find(context.Context, *SearchQuery) (...
method Account (line 393) | func (UnimplementedPluginServer) Account(context.Context, *AccountEven...
method Topic (line 396) | func (UnimplementedPluginServer) Topic(context.Context, *TopicEvent) (...
method Subscription (line 399) | func (UnimplementedPluginServer) Subscription(context.Context, *Subscr...
method Message (line 402) | func (UnimplementedPluginServer) Message(context.Context, *MessageEven...
method mustEmbedUnimplementedPluginServer (line 405) | func (UnimplementedPluginServer) mustEmbedUnimplementedPluginServer() {}
type UnsafePluginServer (line 410) | type UnsafePluginServer interface
function RegisterPluginServer (line 414) | func RegisterPluginServer(s grpc.ServiceRegistrar, srv PluginServer) {
function _Plugin_FireHose_Handler (line 418) | func _Plugin_FireHose_Handler(srv interface{}, ctx context.Context, dec ...
function _Plugin_Find_Handler (line 436) | func _Plugin_Find_Handler(srv interface{}, ctx context.Context, dec func...
function _Plugin_Account_Handler (line 454) | func _Plugin_Account_Handler(srv interface{}, ctx context.Context, dec f...
function _Plugin_Topic_Handler (line 472) | func _Plugin_Topic_Handler(srv interface{}, ctx context.Context, dec fun...
function _Plugin_Subscription_Handler (line 490) | func _Plugin_Subscription_Handler(srv interface{}, ctx context.Context, ...
function _Plugin_Message_Handler (line 508) | func _Plugin_Message_Handler(srv interface{}, ctx context.Context, dec f...
FILE: py_grpc/tinode_grpc/model_pb2.pyi
class AuthLevel (line 9) | class AuthLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
class InfoNote (line 16) | class InfoNote(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
class CallEvent (line 24) | class CallEvent(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
class RespCode (line 35) | class RespCode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
class Crud (line 42) | class Crud(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
class Unused (line 72) | class Unused(_message.Message):
method __init__ (line 74) | def __init__(self) -> None: ...
class DefaultAcsMode (line 76) | class DefaultAcsMode(_message.Message):
method __init__ (line 82) | def __init__(self, auth: _Optional[str] = ..., anon: _Optional[str] = ...
class AccessMode (line 84) | class AccessMode(_message.Message):
method __init__ (line 90) | def __init__(self, want: _Optional[str] = ..., given: _Optional[str] =...
class SetSub (line 92) | class SetSub(_message.Message):
method __init__ (line 98) | def __init__(self, user_id: _Optional[str] = ..., mode: _Optional[str]...
class ClientCred (line 100) | class ClientCred(_message.Message):
class ParamsEntry (line 102) | class ParamsEntry(_message.Message):
method __init__ (line 108) | def __init__(self, key: _Optional[str] = ..., value: _Optional[bytes...
method __init__ (line 117) | def __init__(self, method: _Optional[str] = ..., value: _Optional[str]...
class SetDesc (line 119) | class SetDesc(_message.Message):
method __init__ (line 129) | def __init__(self, default_acs: _Optional[_Union[DefaultAcsMode, _Mapp...
class SeqRange (line 131) | class SeqRange(_message.Message):
method __init__ (line 137) | def __init__(self, low: _Optional[int] = ..., hi: _Optional[int] = ......
class GetOpts (line 139) | class GetOpts(_message.Message):
method __init__ (line 155) | def __init__(self, if_modified_since: _Optional[int] = ..., user: _Opt...
class GetQuery (line 157) | class GetQuery(_message.Message):
method __init__ (line 167) | def __init__(self, what: _Optional[str] = ..., desc: _Optional[_Union[...
class SetQuery (line 169) | class SetQuery(_message.Message):
class AuxEntry (line 171) | class AuxEntry(_message.Message):
method __init__ (line 177) | def __init__(self, key: _Optional[str] = ..., value: _Optional[bytes...
method __init__ (line 188) | def __init__(self, desc: _Optional[_Union[SetDesc, _Mapping]] = ..., s...
class ClientHi (line 190) | class ClientHi(_message.Message):
method __init__ (line 206) | def __init__(self, id: _Optional[str] = ..., user_agent: _Optional[str...
class ClientAcc (line 208) | class ClientAcc(_message.Message):
method __init__ (line 236) | def __init__(self, id: _Optional[str] = ..., user_id: _Optional[str] =...
class ClientLogin (line 238) | class ClientLogin(_message.Message):
method __init__ (line 248) | def __init__(self, id: _Optional[str] = ..., scheme: _Optional[str] = ...
class ClientSub (line 250) | class ClientSub(_message.Message):
method __init__ (line 260) | def __init__(self, id: _Optional[str] = ..., topic: _Optional[str] = ....
class ClientLeave (line 262) | class ClientLeave(_message.Message):
method __init__ (line 270) | def __init__(self, id: _Optional[str] = ..., topic: _Optional[str] = ....
class ClientPub (line 272) | class ClientPub(_message.Message):
class HeadEntry (line 274) | class HeadEntry(_message.Message):
method __init__ (line 280) | def __init__(self, key: _Optional[str] = ..., value: _Optional[bytes...
method __init__ (line 291) | def __init__(self, id: _Optional[str] = ..., topic: _Optional[str] = ....
class ClientGet (line 293) | class ClientGet(_message.Message):
method __init__ (line 301) | def __init__(self, id: _Optional[str] = ..., topic: _Optional[str] = ....
class ClientSet (line 303) | class ClientSet(_message.Message):
method __init__ (line 311) | def __init__(self, id: _Optional[str] = ..., topic: _Optional[str] = ....
class ClientDel (line 313) | class ClientDel(_message.Message):
class What (line 315) | class What(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
method __init__ (line 343) | def __init__(self, id: _Optional[str] = ..., topic: _Optional[str] = ....
class ClientNote (line 345) | class ClientNote(_message.Message):
method __init__ (line 359) | def __init__(self, topic: _Optional[str] = ..., what: _Optional[_Union...
class ClientExtra (line 361) | class ClientExtra(_message.Message):
method __init__ (line 369) | def __init__(self, attachments: _Optional[_Iterable[str]] = ..., on_be...
class ClientMsg (line 371) | class ClientMsg(_message.Message):
method __init__ (line 394) | def __init__(self, hi: _Optional[_Union[ClientHi, _Mapping]] = ..., ac...
class ServerCred (line 396) | class ServerCred(_message.Message):
method __init__ (line 404) | def __init__(self, method: _Optional[str] = ..., value: _Optional[str]...
class TopicDesc (line 406) | class TopicDesc(_message.Message):
method __init__ (line 444) | def __init__(self, created_at: _Optional[int] = ..., updated_at: _Opti...
class TopicSub (line 446) | class TopicSub(_message.Message):
method __init__ (line 480) | def __init__(self, updated_at: _Optional[int] = ..., deleted_at: _Opti...
class DelValues (line 482) | class DelValues(_message.Message):
method __init__ (line 488) | def __init__(self, del_id: _Optional[int] = ..., del_seq: _Optional[_I...
class ServerCtrl (line 490) | class ServerCtrl(_message.Message):
class ParamsEntry (line 492) | class ParamsEntry(_message.Message):
method __init__ (line 498) | def __init__(self, key: _Optional[str] = ..., value: _Optional[bytes...
method __init__ (line 509) | def __init__(self, id: _Optional[str] = ..., topic: _Optional[str] = ....
class ServerData (line 511) | class ServerData(_message.Message):
class HeadEntry (line 513) | class HeadEntry(_message.Message):
method __init__ (line 519) | def __init__(self, key: _Optional[str] = ..., value: _Optional[bytes...
method __init__ (line 534) | def __init__(self, topic: _Optional[str] = ..., from_user_id: _Optiona...
class ServerPres (line 536) | class ServerPres(_message.Message):
class What (line 538) | class What(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
method __init__ (line 588) | def __init__(self, topic: _Optional[str] = ..., src: _Optional[str] = ...
class ServerMeta (line 590) | class ServerMeta(_message.Message):
class AuxEntry (line 592) | class AuxEntry(_message.Message):
method __init__ (line 598) | def __init__(self, key: _Optional[str] = ..., value: _Optional[bytes...
method __init__ (line 614) | def __init__(self, id: _Optional[str] = ..., topic: _Optional[str] = ....
class ServerInfo (line 616) | class ServerInfo(_message.Message):
method __init__ (line 632) | def __init__(self, topic: _Optional[str] = ..., from_user_id: _Optiona...
class ServerMsg (line 634) | class ServerMsg(_message.Message):
method __init__ (line 648) | def __init__(self, ctrl: _Optional[_Union[ServerCtrl, _Mapping]] = ......
class ServerResp (line 650) | class ServerResp(_message.Message):
method __init__ (line 658) | def __init__(self, status: _Optional[_Union[RespCode, str]] = ..., srv...
class Session (line 660) | class Session(_message.Message):
method __init__ (line 676) | def __init__(self, session_id: _Optional[str] = ..., user_id: _Optiona...
class ClientReq (line 678) | class ClientReq(_message.Message):
method __init__ (line 684) | def __init__(self, msg: _Optional[_Union[ClientMsg, _Mapping]] = ..., ...
class SearchQuery (line 686) | class SearchQuery(_message.Message):
method __init__ (line 692) | def __init__(self, user_id: _Optional[str] = ..., query: _Optional[str...
class SearchFound (line 694) | class SearchFound(_message.Message):
method __init__ (line 702) | def __init__(self, status: _Optional[_Union[RespCode, str]] = ..., que...
class TopicEvent (line 704) | class TopicEvent(_message.Message):
method __init__ (line 712) | def __init__(self, action: _Optional[_Union[Crud, str]] = ..., name: _...
class AccountEvent (line 714) | class AccountEvent(_message.Message):
method __init__ (line 726) | def __init__(self, action: _Optional[_Union[Crud, str]] = ..., user_id...
class SubscriptionEvent (line 728) | class SubscriptionEvent(_message.Message):
method __init__ (line 746) | def __init__(self, action: _Optional[_Union[Crud, str]] = ..., topic: ...
class MessageEvent (line 748) | class MessageEvent(_message.Message):
method __init__ (line 754) | def __init__(self, action: _Optional[_Union[Crud, str]] = ..., msg: _O...
class Auth (line 756) | class Auth(_message.Message):
method __init__ (line 762) | def __init__(self, scheme: _Optional[str] = ..., secret: _Optional[str...
class FileMeta (line 764) | class FileMeta(_message.Message):
method __init__ (line 774) | def __init__(self, name: _Optional[str] = ..., mime_type: _Optional[st...
class FileUpReq (line 776) | class FileUpReq(_message.Message):
method __init__ (line 788) | def __init__(self, id: _Optional[str] = ..., auth: _Optional[_Union[Au...
class FileUpResp (line 790) | class FileUpResp(_message.Message):
method __init__ (line 802) | def __init__(self, id: _Optional[str] = ..., code: _Optional[int] = .....
class FileDownReq (line 804) | class FileDownReq(_message.Message):
method __init__ (line 814) | def __init__(self, id: _Optional[str] = ..., auth: _Optional[_Union[Au...
class FileDownResp (line 816) | class FileDownResp(_message.Message):
method __init__ (line 830) | def __init__(self, id: _Optional[str] = ..., code: _Optional[int] = .....
FILE: py_grpc/tinode_grpc/model_pb2_grpc.py
class NodeStub (line 8) | class NodeStub(object):
method __init__ (line 12) | def __init__(self, channel):
class NodeServicer (line 35) | class NodeServicer(object):
method MessageLoop (line 39) | def MessageLoop(self, request_iterator, context):
method LargeFileReceive (line 46) | def LargeFileReceive(self, request_iterator, context):
method LargeFileServe (line 53) | def LargeFileServe(self, request, context):
function add_NodeServicer_to_server (line 61) | def add_NodeServicer_to_server(servicer, server):
class Node (line 85) | class Node(object):
method MessageLoop (line 90) | def MessageLoop(request_iterator,
method LargeFileReceive (line 107) | def LargeFileReceive(request_iterator,
method LargeFileServe (line 124) | def LargeFileServe(request,
class PluginStub (line 141) | class PluginStub(object):
method __init__ (line 145) | def __init__(self, channel):
class PluginServicer (line 183) | class PluginServicer(object):
method FireHose (line 187) | def FireHose(self, request, context):
method Find (line 196) | def Find(self, request, context):
method Account (line 204) | def Account(self, request, context):
method Topic (line 213) | def Topic(self, request, context):
method Subscription (line 220) | def Subscription(self, request, context):
method Message (line 227) | def Message(self, request, context):
function add_PluginServicer_to_server (line 235) | def add_PluginServicer_to_server(servicer, server):
class Plugin (line 274) | class Plugin(object):
method FireHose (line 279) | def FireHose(request,
method Find (line 296) | def Find(request,
method Account (line 313) | def Account(request,
method Topic (line 330) | def Topic(request,
method Subscription (line 347) | def Subscription(request,
method Message (line 364) | def Message(request,
FILE: py_grpc/version.py
function git_version (line 7) | def git_version():
FILE: rest-auth/auth.py
function parse_secret (line 14) | def parse_secret(ecoded_secret):
function index (line 19) | def index():
function add (line 24) | def add():
function auth (line 28) | def auth():
function checkunique (line 65) | def checkunique():
function xdel (line 69) | def xdel():
function gen (line 73) | def gen():
function link (line 77) | def link():
function upd (line 104) | def upd():
function rtags (line 108) | def rtags():
function not_found (line 113) | def not_found(error):
function not_found (line 117) | def not_found(error):
FILE: server/api_key.go
constant apikeyVersion (line 28) | apikeyVersion = 1
constant apikeyAppID (line 30) | apikeyAppID = 4
constant apikeySequence (line 32) | apikeySequence = 2
constant apikeyWho (line 34) | apikeyWho = 1
constant apikeySignature (line 36) | apikeySignature = 16
constant apikeyLength (line 38) | apikeyLength = apikeyVersion + apikeyAppID + apikeySequence + apikeyWho ...
function checkAPIKey (line 46) | func checkAPIKey(apikey string) (isValid, isRoot bool) {
FILE: server/auth/anon/auth_anon.go
type authenticator (line 16) | type authenticator struct
method Init (line 21) | func (a *authenticator) Init(_ json.RawMessage, name string) error {
method IsInitialized (line 35) | func (a *authenticator) IsInitialized() bool {
method AddRecord (line 41) | func (authenticator) AddRecord(rec *auth.Rec, secret []byte, remoteAdd...
method UpdateRecord (line 50) | func (authenticator) UpdateRecord(rec *auth.Rec, secret []byte, remote...
method Authenticate (line 55) | func (authenticator) Authenticate(secret []byte, remoteAddr string) (*...
method AsTag (line 60) | func (authenticator) AsTag(token string) string {
method IsUnique (line 65) | func (authenticator) IsUnique(secret []byte, remoteAddr string) (bool,...
method GenSecret (line 70) | func (authenticator) GenSecret(rec *auth.Rec) ([]byte, time.Time, erro...
method DelRecords (line 75) | func (authenticator) DelRecords(uid types.Uid) error {
method RestrictedTags (line 80) | func (authenticator) RestrictedTags() ([]string, error) {
method GetResetParams (line 86) | func (authenticator) GetResetParams(uid types.Uid) (map[string]any, er...
method GetRealName (line 93) | func (authenticator) GetRealName() string {
constant realName (line 90) | realName = "anonymous"
function init (line 97) | func init() {
FILE: server/auth/auth.go
type Level (line 14) | type Level
method String (line 29) | func (a Level) String() string {
method MarshalText (line 52) | func (a Level) MarshalText() ([]byte, error) {
method UnmarshalText (line 68) | func (a *Level) UnmarshalText(b []byte) error {
method MarshalJSON (line 88) | func (a Level) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 98) | func (a *Level) UnmarshalJSON(b []byte) error {
constant LevelNone (line 19) | LevelNone Level = iota * 10
constant LevelAnon (line 21) | LevelAnon
constant LevelAuth (line 23) | LevelAuth
constant LevelRoot (line 25) | LevelRoot
function ParseAuthLevel (line 38) | func ParseAuthLevel(name string) Level {
type Feature (line 107) | type Feature
method MarshalText (line 117) | func (f Feature) MarshalText() ([]byte, error) {
method UnmarshalText (line 128) | func (f *Feature) UnmarshalText(b []byte) error {
method String (line 156) | func (f Feature) String() string {
method MarshalJSON (line 165) | func (f Feature) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 175) | func (f *Feature) UnmarshalJSON(b []byte) error {
constant FeatureValidated (line 111) | FeatureValidated Feature = 1 << iota
constant FeatureNoLogin (line 113) | FeatureNoLogin
type Duration (line 183) | type Duration
method UnmarshalJSON (line 186) | func (d *Duration) UnmarshalJSON(b []byte) error {
type Rec (line 208) | type Rec struct
type AuthHandler (line 232) | type AuthHandler interface
FILE: server/auth/basic/auth_basic.go
constant defaultMinLoginLength (line 20) | defaultMinLoginLength = 2
constant defaultMaxLoginLength (line 21) | defaultMaxLoginLength = 32
constant defaultMinPasswordLength (line 23) | defaultMinPasswordLength = 3
type authenticator (line 31) | type authenticator struct
method checkLoginPolicy (line 39) | func (a *authenticator) checkLoginPolicy(uname string) error {
method checkPasswordPolicy (line 48) | func (a *authenticator) checkPasswordPolicy(password string) error {
method Init (line 71) | func (a *authenticator) Init(jsonconf json.RawMessage, name string) er...
method IsInitialized (line 109) | func (a *authenticator) IsInitialized() bool {
method AddRecord (line 114) | func (a *authenticator) AddRecord(rec *auth.Rec, secret []byte, remote...
method UpdateRecord (line 155) | func (a *authenticator) UpdateRecord(rec *auth.Rec, secret []byte, rem...
method Authenticate (line 216) | func (a *authenticator) Authenticate(secret []byte, remoteAddr string)...
method AsTag (line 254) | func (a *authenticator) AsTag(token string) string {
method IsUnique (line 267) | func (a *authenticator) IsUnique(secret []byte, remoteAddr string) (bo...
method GenSecret (line 289) | func (authenticator) GenSecret(rec *auth.Rec) ([]byte, time.Time, erro...
method DelRecords (line 294) | func (a *authenticator) DelRecords(uid types.Uid) error {
method RestrictedTags (line 299) | func (a *authenticator) RestrictedTags() ([]string, error) {
method GetResetParams (line 308) | func (a *authenticator) GetResetParams(uid types.Uid) (map[string]any,...
method GetRealName (line 326) | func (authenticator) GetRealName() string {
function parseSecret (line 56) | func parseSecret(bsecret []byte) (uname, password string, err error) {
constant realName (line 323) | realName = "basic"
function init (line 330) | func init() {
FILE: server/auth/code/auth_code.go
type authenticator (line 20) | type authenticator struct
method Init (line 29) | func (ca *authenticator) Init(jsonconf json.RawMessage, name string) e...
method IsInitialized (line 73) | func (ca *authenticator) IsInitialized() bool {
method AddRecord (line 78) | func (authenticator) AddRecord(rec *auth.Rec, secret []byte, remoteAdd...
method UpdateRecord (line 83) | func (authenticator) UpdateRecord(rec *auth.Rec, secret []byte, remote...
method Authenticate (line 89) | func (ca *authenticator) Authenticate(secret []byte, remoteAddr string...
method GenSecret (line 142) | func (ca *authenticator) GenSecret(rec *auth.Rec) ([]byte, time.Time, ...
method AsTag (line 173) | func (authenticator) AsTag(token string) string {
method IsUnique (line 178) | func (authenticator) IsUnique(secret []byte, remoteAddr string) (bool,...
method DelRecords (line 183) | func (authenticator) DelRecords(uid types.Uid) error {
method RestrictedTags (line 188) | func (authenticator) RestrictedTags() ([]string, error) {
method GetResetParams (line 194) | func (authenticator) GetResetParams(uid types.Uid) (map[string]any, er...
method GetRealName (line 206) | func (authenticator) GetRealName() string {
function sanitizeKey (line 199) | func sanitizeKey(key string) string {
constant realName (line 203) | realName = "code"
function init (line 210) | func init() {
FILE: server/auth/mock_auth/mock_auth.go
type MockAuthHandler (line 18) | type MockAuthHandler struct
method EXPECT (line 36) | func (m *MockAuthHandler) EXPECT() *MockAuthHandlerMockRecorder {
method AddRecord (line 41) | func (m *MockAuthHandler) AddRecord(rec *auth.Rec, secret []byte, remo...
method AsTag (line 56) | func (m *MockAuthHandler) AsTag(token string) string {
method Authenticate (line 70) | func (m *MockAuthHandler) Authenticate(secret []byte, remoteAddr strin...
method DelRecords (line 86) | func (m *MockAuthHandler) DelRecords(uid types.Uid) error {
method GenSecret (line 100) | func (m *MockAuthHandler) GenSecret(rec *auth.Rec) ([]byte, time.Time,...
method GetRealName (line 116) | func (m *MockAuthHandler) GetRealName() string {
method GetResetParams (line 130) | func (m *MockAuthHandler) GetResetParams(uid types.Uid) (map[string]an...
method Init (line 145) | func (m *MockAuthHandler) Init(jsonconf json.RawMessage, name string) ...
method IsInitialized (line 159) | func (m *MockAuthHandler) IsInitialized() bool {
method IsUnique (line 173) | func (m *MockAuthHandler) IsUnique(secret []byte, remoteAddr string) (...
method RestrictedTags (line 188) | func (m *MockAuthHandler) RestrictedTags() ([]string, error) {
method UpdateRecord (line 203) | func (m *MockAuthHandler) UpdateRecord(rec *auth.Rec, secret []byte, r...
type MockAuthHandlerMockRecorder (line 24) | type MockAuthHandlerMockRecorder struct
method AddRecord (line 50) | func (mr *MockAuthHandlerMockRecorder) AddRecord(rec, secret, remoteAd...
method AsTag (line 64) | func (mr *MockAuthHandlerMockRecorder) AsTag(token interface{}) *gomoc...
method Authenticate (line 80) | func (mr *MockAuthHandlerMockRecorder) Authenticate(secret, remoteAddr...
method DelRecords (line 94) | func (mr *MockAuthHandlerMockRecorder) DelRecords(uid interface{}) *go...
method GenSecret (line 110) | func (mr *MockAuthHandlerMockRecorder) GenSecret(rec interface{}) *gom...
method GetRealName (line 124) | func (mr *MockAuthHandlerMockRecorder) GetRealName() *gomock.Call {
method GetResetParams (line 139) | func (mr *MockAuthHandlerMockRecorder) GetResetParams(uid interface{})...
method Init (line 153) | func (mr *MockAuthHandlerMockRecorder) Init(jsonconf, name interface{}...
method IsInitialized (line 167) | func (mr *MockAuthHandlerMockRecorder) IsInitialized() *gomock.Call {
method IsUnique (line 182) | func (mr *MockAuthHandlerMockRecorder) IsUnique(secret, remoteAddr int...
method RestrictedTags (line 197) | func (mr *MockAuthHandlerMockRecorder) RestrictedTags() *gomock.Call {
method UpdateRecord (line 212) | func (mr *MockAuthHandlerMockRecorder) UpdateRecord(rec, secret, remot...
function NewMockAuthHandler (line 29) | func NewMockAuthHandler(ctrl *gomock.Controller) *MockAuthHandler {
FILE: server/auth/rest/auth_rest.go
type authenticator (line 22) | type authenticator struct
method Init (line 78) | func (a *authenticator) Init(jsonconf json.RawMessage, name string) er...
method IsInitialized (line 120) | func (a *authenticator) IsInitialized() bool {
method callEndpoint (line 125) | func (a *authenticator) callEndpoint(endpoint string, rec *auth.Rec, s...
method AddRecord (line 174) | func (a *authenticator) AddRecord(rec *auth.Rec, secret []byte, remote...
method UpdateRecord (line 184) | func (a *authenticator) UpdateRecord(rec *auth.Rec, secret []byte, rem...
method Authenticate (line 190) | func (a *authenticator) Authenticate(secret []byte, remoteAddr string)...
method AsTag (line 237) | func (a *authenticator) AsTag(token string) string {
method IsUnique (line 250) | func (a *authenticator) IsUnique(secret []byte, remoteAddr string) (bo...
method GenSecret (line 260) | func (a *authenticator) GenSecret(rec *auth.Rec) ([]byte, time.Time, e...
method DelRecords (line 270) | func (a *authenticator) DelRecords(uid types.Uid) error {
method RestrictedTags (line 277) | func (a *authenticator) RestrictedTags() ([]string, error) {
method GetResetParams (line 305) | func (authenticator) GetResetParams(uid types.Uid) (map[string]any, er...
method GetRealName (line 313) | func (authenticator) GetRealName() string {
type request (line 38) | type request struct
type newAccount (line 47) | type newAccount struct
type response (line 60) | type response struct
constant realName (line 310) | realName = "rest"
function init (line 317) | func init() {
FILE: server/auth/token/auth_token.go
type authenticator (line 19) | type authenticator struct
method Init (line 42) | func (ta *authenticator) Init(jsonconf json.RawMessage, name string) e...
method IsInitialized (line 80) | func (ta *authenticator) IsInitialized() bool {
method AddRecord (line 85) | func (authenticator) AddRecord(rec *auth.Rec, secret []byte, remoteAdd...
method UpdateRecord (line 90) | func (authenticator) UpdateRecord(rec *auth.Rec, secret []byte, remote...
method Authenticate (line 95) | func (ta *authenticator) Authenticate(token []byte, remoteAddr string)...
method GenSecret (line 144) | func (ta *authenticator) GenSecret(rec *auth.Rec) ([]byte, time.Time, ...
method AsTag (line 170) | func (authenticator) AsTag(token string) string {
method IsUnique (line 175) | func (authenticator) IsUnique(token []byte, remoteAddr string) (bool, ...
method DelRecords (line 180) | func (authenticator) DelRecords(uid types.Uid) error {
method RestrictedTags (line 185) | func (authenticator) RestrictedTags() ([]string, error) {
method GetResetParams (line 191) | func (authenticator) GetResetParams(uid types.Uid) (map[string]any, er...
method GetRealName (line 198) | func (authenticator) GetRealName() string {
type tokenLayout (line 28) | type tokenLayout struct
constant realName (line 195) | realName = "token"
function init (line 202) | func init() {
FILE: server/calls.go
constant constCallEventRinging (line 27) | constCallEventRinging = "ringing"
constant constCallEventAccept (line 29) | constCallEventAccept = "accept"
constant constCallEventOffer (line 31) | constCallEventOffer = "offer"
constant constCallEventAnswer (line 32) | constCallEventAnswer = "answer"
constant constCallEventIceCandidate (line 33) | constCallEventIceCandidate = "ice-candidate"
constant constCallEventHangUp (line 35) | constCallEventHangUp = "hang-up"
constant constCallMsgAccepted (line 39) | constCallMsgAccepted = "accepted"
constant constCallMsgFinished (line 41) | constCallMsgFinished = "finished"
constant constCallMsgDisconnected (line 43) | constCallMsgDisconnected = "disconnected"
constant constCallMsgMissed (line 45) | constCallMsgMissed = "missed"
constant constCallMsgDeclined (line 47) | constCallMsgDeclined = "declined"
type callConfig (line 50) | type callConfig struct
type iceServer (line 62) | type iceServer struct
type callPartyData (line 70) | type callPartyData struct
type videoCall (line 80) | type videoCall struct
method messageHead (line 176) | func (call *videoCall) messageHead(head map[string]any, newState strin...
method infoMessage (line 196) | func (call *videoCall) infoMessage(event string) *ServerComMessage {
function callPartySession (line 94) | func callPartySession(sess *Session) *Session {
function initVideoCalls (line 116) | func initVideoCalls(jsconfig json.RawMessage) error {
method getCallOriginator (line 208) | func (t *Topic) getCallOriginator() (types.Uid, *Session) {
method handleCallInvite (line 222) | func (t *Topic) handleCallInvite(msg *ClientComMessage, asUid types.Uid) {
method handleCallEvent (line 241) | func (t *Topic) handleCallEvent(msg *ClientComMessage) {
method maybeEndCallInProgress (line 377) | func (t *Topic) maybeEndCallInProgress(from string, msg *ClientComMessag...
method terminateCallInProgress (line 433) | func (t *Topic) terminateCallInProgress(callDidTimeout bool) {
FILE: server/cluster.go
constant clusterNetworkTimeout (line 24) | clusterNetworkTimeout = 3 * time.Second
constant clusterDefaultReconnectTime (line 26) | clusterDefaultReconnectTime = 200 * time.Millisecond
constant clusterHashReplicas (line 28) | clusterHashReplicas = 20
constant clusterProxyToMasterBuffer (line 30) | clusterProxyToMasterBuffer = 64
constant clusterProxyToMasterBufferPerNode (line 32) | clusterProxyToMasterBufferPerNode = 16
constant clusterP2MTimeout (line 34) | clusterP2MTimeout = 20 * time.Millisecond
constant clusterRpcCompletionBuffer (line 36) | clusterRpcCompletionBuffer = 64
type ProxyReqType (line 40) | type ProxyReqType
constant ProxyReqNone (line 44) | ProxyReqNone ProxyReqType = iota
constant ProxyReqJoin (line 45) | ProxyReqJoin
constant ProxyReqLeave (line 46) | ProxyReqLeave
constant ProxyReqMeta (line 47) | ProxyReqMeta
constant ProxyReqBroadcast (line 48) | ProxyReqBroadcast
constant ProxyReqBgSession (line 49) | ProxyReqBgSession
constant ProxyReqMeUserAgent (line 50) | ProxyReqMeUserAgent
constant ProxyReqCall (line 51) | ProxyReqCall
type clusterNodeConfig (line 54) | type clusterNodeConfig struct
type clusterConfig (line 59) | type clusterConfig struct
type ClusterNode (line 71) | type ClusterNode struct
method asyncRpcLoop (line 104) | func (n *ClusterNode) asyncRpcLoop() {
method p2mSenderLoop (line 110) | func (n *ClusterNode) p2mSenderLoop() {
method reconnect (line 248) | func (n *ClusterNode) reconnect() {
method call (line 309) | func (n *ClusterNode) call(proc string, req, resp any) error {
method handleRpcResponse (line 331) | func (n *ClusterNode) handleRpcResponse(call *rpc.Call) {
method callAsync (line 345) | func (n *ClusterNode) callAsync(proc string, req, resp any, done chan ...
method proxyToMaster (line 386) | func (n *ClusterNode) proxyToMaster(msg *ClusterReq) error {
method proxyToMasterAsync (line 397) | func (n *ClusterNode) proxyToMasterAsync(msg *ClusterReq) error {
method masterToProxyAsync (line 416) | func (n *ClusterNode) masterToProxyAsync(msg *ClusterResp) error {
method route (line 425) | func (n *ClusterNode) route(msg *ClusterRoute) error {
method stopMultiplexingSession (line 458) | func (n *ClusterNode) stopMultiplexingSession(msess *Session) {
type ClusterSess (line 124) | type ClusterSess struct
type ClusterSessUpdate (line 160) | type ClusterSessUpdate struct
type ClusterReq (line 170) | type ClusterReq struct
type ClusterRoute (line 200) | type ClusterRoute struct
type ClusterResp (line 221) | type ClusterResp struct
type ClusterPing (line 236) | type ClusterPing struct
type Cluster (line 431) | type Cluster struct
method TopicMaster (line 469) | func (c *Cluster) TopicMaster(msg *ClusterReq, rejected *bool) error {
method TopicProxy (line 616) | func (Cluster) TopicProxy(msg *ClusterResp, unused *bool) error {
method Route (line 635) | func (c *Cluster) Route(msg *ClusterRoute, rejected *bool) error {
method UserCacheUpdate (line 669) | func (c *Cluster) UserCacheUpdate(msg *UserCacheReq, rejected *bool) e...
method Ping (line 685) | func (c *Cluster) Ping(ping *ClusterPing, unused *bool) error {
method routeUserReq (line 707) | func (c *Cluster) routeUserReq(req *UserCacheReq) error {
method nodeForTopic (line 783) | func (c *Cluster) nodeForTopic(topic string) *ClusterNode {
method isRemoteTopic (line 799) | func (c *Cluster) isRemoteTopic(topic string) bool {
method genLocalTopicName (line 808) | func (c *Cluster) genLocalTopicName() string {
method isPartitioned (line 824) | func (c *Cluster) isPartitioned() bool {
method makeClusterReq (line 837) | func (c *Cluster) makeClusterReq(reqType ProxyReqType, msg *ClientComM...
method routeToTopicMaster (line 876) | func (c *Cluster) routeToTopicMaster(reqType ProxyReqType, msg *Client...
method routeToTopicIntraCluster (line 901) | func (c *Cluster) routeToTopicIntraCluster(topic string, msg *ServerCo...
method topicProxyGone (line 926) | func (c *Cluster) topicProxyGone(topicName string) error {
method start (line 1046) | func (c *Cluster) start() {
method shutdown (line 1086) | func (c *Cluster) shutdown() {
method rehash (line 1113) | func (c *Cluster) rehash(nodes []string) []string {
method invalidateProxySubs (line 1138) | func (c *Cluster) invalidateProxySubs(forNode string) {
method gcProxySessions (line 1170) | func (c *Cluster) gcProxySessions(activeNodes []string) {
method gcProxySessionsForNode (line 1184) | func (c *Cluster) gcProxySessionsForNode(node string) {
function clusterInit (line 944) | func clusterInit(configString json.RawMessage, self *string) int {
method closeRPC (line 1039) | func (sess *Session) closeRPC() {
method clusterWriteLoop (line 1199) | func (sess *Session) clusterWriteLoop(forTopic string) {
FILE: server/cluster_leader.go
type clusterFailover (line 19) | type clusterFailover struct
type clusterFailoverConfig (line 43) | type clusterFailoverConfig struct
type ClusterHealth (line 55) | type ClusterHealth struct
type ClusterVoteRequest (line 67) | type ClusterVoteRequest struct
type ClusterVoteResponse (line 75) | type ClusterVoteResponse struct
type ClusterVote (line 83) | type ClusterVote struct
method failoverInit (line 88) | func (c *Cluster) failoverInit(config *clusterFailoverConfig) bool {
method Health (line 128) | func (c *Cluster) Health(health *ClusterHealth, unused *bool) error {
method Vote (line 137) | func (c *Cluster) Vote(vreq *ClusterVoteRequest, response *ClusterVoteRe...
method sendHealthChecks (line 151) | func (c *Cluster) sendHealthChecks() {
method electLeader (line 198) | func (c *Cluster) electLeader() {
method run (line 260) | func (c *Cluster) run() {
FILE: server/concurrency/goroutinepool.go
type Task (line 7) | type Task
type GoRoutinePool (line 10) | type GoRoutinePool struct
method Schedule (line 29) | func (p *GoRoutinePool) Schedule(task Task) {
method Stop (line 38) | func (p *GoRoutinePool) Stop() {
method worker (line 46) | func (p *GoRoutinePool) worker(task Task) {
function NewGoRoutinePool (line 20) | func NewGoRoutinePool(numWorkers int) *GoRoutinePool {
FILE: server/concurrency/simplemutex.go
type SimpleMutex (line 4) | type SimpleMutex
method Lock (line 12) | func (s SimpleMutex) Lock() {
method TryLock (line 18) | func (s SimpleMutex) TryLock() bool {
method Unlock (line 28) | func (s SimpleMutex) Unlock() {
function NewSimpleMutex (line 7) | func NewSimpleMutex() SimpleMutex {
FILE: server/datamodel.go
type MsgGetOpts (line 22) | type MsgGetOpts struct
type MsgGetQuery (line 40) | type MsgGetQuery struct
type MsgSetSub (line 54) | type MsgSetSub struct
type MsgSetDesc (line 63) | type MsgSetDesc struct
type MsgCredClient (line 75) | type MsgCredClient struct
type MsgSetQuery (line 87) | type MsgSetQuery struct
type MsgRange (line 102) | type MsgRange struct
type MsgClientHi (line 112) | type MsgClientHi struct
type MsgClientAcc (line 130) | type MsgClientAcc struct
type MsgClientLogin (line 158) | type MsgClientLogin struct
type MsgClientSub (line 170) | type MsgClientSub struct
constant constMsgMetaDesc (line 191) | constMsgMetaDesc = 1 << iota
constant constMsgMetaSub (line 192) | constMsgMetaSub
constant constMsgMetaData (line 193) | constMsgMetaData
constant constMsgMetaTags (line 194) | constMsgMetaTags
constant constMsgMetaDel (line 195) | constMsgMetaDel
constant constMsgMetaCred (line 196) | constMsgMetaCred
constant constMsgMetaAux (line 197) | constMsgMetaAux
constant constMsgDelTopic (line 201) | constMsgDelTopic = iota + 1
constant constMsgDelMsg (line 202) | constMsgDelMsg
constant constMsgDelSub (line 203) | constMsgDelSub
constant constMsgDelUser (line 204) | constMsgDelUser
constant constMsgDelCred (line 205) | constMsgDelCred
function parseMsgClientMeta (line 208) | func parseMsgClientMeta(params string) int {
function parseMsgClientDel (line 234) | func parseMsgClientDel(params string) int {
type MsgDefaultAcsMode (line 253) | type MsgDefaultAcsMode struct
type MsgClientLeave (line 259) | type MsgClientLeave struct
type MsgClientPub (line 266) | type MsgClientPub struct
type MsgClientGet (line 275) | type MsgClientGet struct
type MsgClientSet (line 282) | type MsgClientSet struct
type MsgClientDel (line 289) | type MsgClientDel struct
type MsgClientNote (line 310) | type MsgClientNote struct
type MsgClientExtra (line 326) | type MsgClientExtra struct
type ClientComMessage (line 336) | type ClientComMessage struct
type MsgLastSeenInfo (line 378) | type MsgLastSeenInfo struct
method describe (line 385) | func (src *MsgLastSeenInfo) describe() string {
type MsgCredServer (line 390) | type MsgCredServer struct
type MsgAccessMode (line 400) | type MsgAccessMode struct
method describe (line 409) | func (src *MsgAccessMode) describe() string {
type MsgTopicDesc (line 424) | type MsgTopicDesc struct
method describe (line 458) | func (src *MsgTopicDesc) describe() string {
type MsgTopicSub (line 495) | type MsgTopicSub struct
method describe (line 544) | func (src *MsgTopicSub) describe() string {
type MsgDelValues (line 578) | type MsgDelValues struct
type MsgServerCtrl (line 584) | type MsgServerCtrl struct
method copy (line 595) | func (src *MsgServerCtrl) copy() *MsgServerCtrl {
method describe (line 603) | func (src *MsgServerCtrl) describe() string {
type MsgServerData (line 608) | type MsgServerData struct
method copy (line 620) | func (src *MsgServerData) copy() *MsgServerData {
method describe (line 628) | func (src *MsgServerData) describe() string {
type MsgServerPres (line 642) | type MsgServerPres struct
method copy (line 679) | func (src *MsgServerPres) copy() *MsgServerPres {
method describe (line 687) | func (src *MsgServerPres) describe() string {
type MsgServerMeta (line 721) | type MsgServerMeta struct
method copy (line 742) | func (src *MsgServerMeta) copy() *MsgServerMeta {
method describe (line 750) | func (src *MsgServerMeta) describe() string {
type MsgServerInfo (line 782) | type MsgServerInfo struct
method copy (line 806) | func (src *MsgServerInfo) copy() *MsgServerInfo {
method describe (line 815) | func (src *MsgServerInfo) describe() string {
type ServerComMessage (line 831) | type ServerComMessage struct
method copy (line 860) | func (src *ServerComMessage) copy() *ServerComMessage {
method describe (line 883) | func (src *ServerComMessage) describe() string {
function NoErr (line 907) | func NoErr(id, topic string, ts time.Time) *ServerComMessage {
function NoErrExplicitTs (line 912) | func NoErrExplicitTs(id, topic string, serverTs, incomingReqTs time.Time...
function NoErrReply (line 917) | func NoErrReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
function NoErrParams (line 922) | func NoErrParams(id, topic string, ts time.Time, params any) *ServerComM...
function NoErrParamsExplicitTs (line 928) | func NoErrParamsExplicitTs(id, topic string, serverTs, incomingReqTs tim...
function NoErrParamsReply (line 945) | func NoErrParamsReply(msg *ClientComMessage, ts time.Time, params any) *...
function NoErrCreated (line 950) | func NoErrCreated(id, topic string, ts time.Time) *ServerComMessage {
function NoErrAccepted (line 965) | func NoErrAccepted(id, topic string, ts time.Time) *ServerComMessage {
function NoErrAcceptedExplicitTs (line 971) | func NoErrAcceptedExplicitTs(id, topic string, serverTs, incomingReqTs t...
function NoContentParams (line 985) | func NoContentParams(id, topic string, serverTs, incomingReqTs time.Time...
function NoContentParamsReply (line 1002) | func NoContentParamsReply(msg *ClientComMessage, ts time.Time, params an...
function NoErrEvicted (line 1007) | func NoErrEvicted(id, topic string, ts time.Time) *ServerComMessage {
function NoErrShutdown (line 1020) | func NoErrShutdown(ts time.Time) *ServerComMessage {
function NoErrDeliveredParams (line 1031) | func NoErrDeliveredParams(id, topic string, ts time.Time, params any) *S...
function InfoValidateCredentials (line 1048) | func InfoValidateCredentials(id string, ts time.Time) *ServerComMessage {
function InfoValidateCredentialsExplicitTs (line 1054) | func InfoValidateCredentialsExplicitTs(id string, serverTs, incomingReqT...
function InfoChallenge (line 1068) | func InfoChallenge(id string, ts time.Time, challenge []byte) *ServerCom...
function InfoAuthReset (line 1084) | func InfoAuthReset(id string, ts time.Time) *ServerComMessage {
function InfoUseOther (line 1098) | func InfoUseOther(id, topic, other string, serverTs, incomingReqTs time....
function InfoUseOtherReply (line 1114) | func InfoUseOtherReply(msg *ClientComMessage, other string, ts time.Time...
function InfoAlreadySubscribed (line 1119) | func InfoAlreadySubscribed(id, topic string, ts time.Time) *ServerComMes...
function InfoNotJoined (line 1133) | func InfoNotJoined(id, topic string, ts time.Time) *ServerComMessage {
function InfoNoAction (line 1149) | func InfoNoAction(id, topic string, serverTs, incomingReqTs time.Time) *...
function InfoNoActionReply (line 1165) | func InfoNoActionReply(msg *ClientComMessage, ts time.Time) *ServerComMe...
function InfoNotModified (line 1170) | func InfoNotModified(id, topic string, ts time.Time) *ServerComMessage {
function InfoNotModifiedReply (line 1176) | func InfoNotModifiedReply(msg *ClientComMessage, ts time.Time) *ServerCo...
function InfoNotModifiedExplicitTs (line 1182) | func InfoNotModifiedExplicitTs(id, topic string, serverTs, incomingReqTs...
function InfoFound (line 1197) | func InfoFound(id, topic string, ts time.Time) *ServerComMessage {
function ErrMalformed (line 1214) | func ErrMalformed(id, topic string, ts time.Time) *ServerComMessage {
function ErrMalformedReply (line 1220) | func ErrMalformedReply(msg *ClientComMessage, ts time.Time) *ServerComMe...
function ErrMalformedExplicitTs (line 1225) | func ErrMalformedExplicitTs(id, topic string, serverTs, incomingReqTs ti...
function ErrAuthRequired (line 1240) | func ErrAuthRequired(id, topic string, serverTs, incomingReqTs time.Time...
function ErrAuthRequiredReply (line 1256) | func ErrAuthRequiredReply(msg *ClientComMessage, ts time.Time) *ServerCo...
function ErrAuthFailed (line 1262) | func ErrAuthFailed(id, topic string, serverTs, incomingReqTs time.Time) ...
function ErrAuthUnknownScheme (line 1277) | func ErrAuthUnknownScheme(id, topic string, ts time.Time) *ServerComMess...
function ErrPermissionDenied (line 1292) | func ErrPermissionDenied(id, topic string, ts time.Time) *ServerComMessa...
function ErrPermissionDeniedExplicitTs (line 1298) | func ErrPermissionDeniedExplicitTs(id, topic string, serverTs, incomingR...
function ErrPermissionDeniedReply (line 1314) | func ErrPermissionDeniedReply(msg *ClientComMessage, ts time.Time) *Serv...
function ErrAPIKeyRequired (line 1319) | func ErrAPIKeyRequired(ts time.Time) *ServerComMessage {
function ErrSessionNotFound (line 1330) | func ErrSessionNotFound(ts time.Time) *ServerComMessage {
function ErrTopicNotFound (line 1342) | func ErrTopicNotFound(id, topic string, serverTs, incomingReqTs time.Tim...
function ErrTopicNotFoundReply (line 1359) | func ErrTopicNotFoundReply(msg *ClientComMessage, ts time.Time) *ServerC...
function ErrUserNotFound (line 1365) | func ErrUserNotFound(id, topic string, serverTs, incomingReqTs time.Time...
function ErrUserNotFoundReply (line 1381) | func ErrUserNotFoundReply(msg *ClientComMessage, ts time.Time) *ServerCo...
function ErrNotFound (line 1386) | func ErrNotFound(id, topic string, ts time.Time) *ServerComMessage {
function ErrNotFoundExplicitTs (line 1392) | func ErrNotFoundExplicitTs(id, topic string, serverTs, incomingReqTs tim...
function ErrNotFoundReply (line 1408) | func ErrNotFoundReply(msg *ClientComMessage, ts time.Time) *ServerComMes...
function ErrOperationNotAllowed (line 1413) | func ErrOperationNotAllowed(id, topic string, ts time.Time) *ServerComMe...
function ErrOperationNotAllowedExplicitTs (line 1419) | func ErrOperationNotAllowedExplicitTs(id, topic string, serverTs, incomi...
function ErrOperationNotAllowedReply (line 1435) | func ErrOperationNotAllowedReply(msg *ClientComMessage, ts time.Time) *S...
function ErrInvalidResponse (line 1441) | func ErrInvalidResponse(id, topic string, serverTs, incomingReqTs time.T...
function ErrDisconnected (line 1457) | func ErrDisconnected(id, topic string, ts time.Time) *ServerComMessage {
function ErrAlreadyAuthenticated (line 1473) | func ErrAlreadyAuthenticated(id, topic string, ts time.Time) *ServerComM...
function ErrDuplicateCredential (line 1489) | func ErrDuplicateCredential(id, topic string, serverTs, incomingReqTs ti...
function ErrAttachFirst (line 1504) | func ErrAttachFirst(msg *ClientComMessage, ts time.Time) *ServerComMessa...
function ErrAlreadyExists (line 1519) | func ErrAlreadyExists(id, topic string, ts time.Time) *ServerComMessage {
function ErrCommandOutOfSequence (line 1534) | func ErrCommandOutOfSequence(id, unused string, ts time.Time) *ServerCom...
function ErrGone (line 1548) | func ErrGone(id, topic string, ts time.Time) *ServerComMessage {
function ErrTooLarge (line 1563) | func ErrTooLarge(id, topic string, ts time.Time) *ServerComMessage {
function ErrPolicy (line 1578) | func ErrPolicy(id, topic string, ts time.Time) *ServerComMessage {
function ErrPolicyExplicitTs (line 1584) | func ErrPolicyExplicitTs(id, topic string, serverTs, incomingReqTs time....
function ErrPolicyReply (line 1600) | func ErrPolicyReply(msg *ClientComMessage, ts time.Time) *ServerComMessa...
function ErrCallBusyExplicitTs (line 1605) | func ErrCallBusyExplicitTs(id, topic string, serverTs, incomingReqTs tim...
function ErrCallBusyReply (line 1620) | func ErrCallBusyReply(msg *ClientComMessage, ts time.Time) *ServerComMes...
function ErrUnknown (line 1625) | func ErrUnknown(id, topic string, ts time.Time) *ServerComMessage {
function ErrUnknownExplicitTs (line 1630) | func ErrUnknownExplicitTs(id, topic string, serverTs, incomingReqTs time...
function ErrUnknownReply (line 1645) | func ErrUnknownReply(msg *ClientComMessage, ts time.Time) *ServerComMess...
function ErrNotImplemented (line 1651) | func ErrNotImplemented(id, topic string, serverTs, incomingReqTs time.Ti...
function ErrNotImplementedReply (line 1666) | func ErrNotImplementedReply(msg *ClientComMessage, ts time.Time) *Server...
function ErrClusterUnreachableReply (line 1671) | func ErrClusterUnreachableReply(msg *ClientComMessage, ts time.Time) *Se...
function ErrClusterUnreachableExplicitTs (line 1677) | func ErrClusterUnreachableExplicitTs(id, topic string, serverTs, incomin...
function ErrServiceUnavailableReply (line 1692) | func ErrServiceUnavailableReply(msg *ClientComMessage, ts time.Time) *Se...
function ErrServiceUnavailableExplicitTs (line 1698) | func ErrServiceUnavailableExplicitTs(id, topic string, serverTs, incomin...
function ErrLocked (line 1713) | func ErrLocked(id, topic string, ts time.Time) *ServerComMessage {
function ErrLockedReply (line 1719) | func ErrLockedReply(msg *ClientComMessage, ts time.Time) *ServerComMessa...
function ErrLockedExplicitTs (line 1725) | func ErrLockedExplicitTs(id, topic string, serverTs, incomingReqTs time....
function ErrVersionNotSupported (line 1740) | func ErrVersionNotSupported(id string, ts time.Time) *ServerComMessage {
FILE: server/db/adapter.go
type Adapter (line 14) | type Adapter interface
FILE: server/db/common/common.go
type AuthRecord (line 16) | type AuthRecord struct
function SelectEarliestUpdatedSubs (line 28) | func SelectEarliestUpdatedSubs(subs []t.Subscription, opts *t.QueryOpt, ...
function SelectLatestTime (line 67) | func SelectLatestTime(t1, t2 time.Time) time.Time {
function RangesToSql (line 77) | func RangesToSql(in []t.Range) (string, []any) {
function DisjunctionSql (line 99) | func DisjunctionSql(req [][]string, fieldName string) (string, []any) {
function FilterFoundTags (line 116) | func FilterFoundTags(setTags t.StringSlice, index map[string]struct{}) [...
function ToJSON (line 127) | func ToJSON(src any) []byte {
function FromJSON (line 137) | func FromJSON(src any) any {
function UpdateByMap (line 150) | func UpdateByMap(update map[string]any) (cols []string, args []any) {
function ExtractTags (line 163) | func ExtractTags(update map[string]any) []string {
function EncodeUidString (line 175) | func EncodeUidString(str string) t.Uid {
function DecodeUidString (line 182) | func DecodeUidString(str string) int64 {
FILE: server/db/common/common_test.go
function genTestData (line 13) | func genTestData() []types.Subscription {
function TestSelectEarliestUpdatedSubs (line 42) | func TestSelectEarliestUpdatedSubs(t *testing.T) {
function TestSelectLatestTime (line 101) | func TestSelectLatestTime(t *testing.T) {
function TestRangesToSql (line 124) | func TestRangesToSql(t *testing.T) {
function TestDisjunctionSql (line 162) | func TestDisjunctionSql(t *testing.T) {
function TestFilterFoundTags (line 200) | func TestFilterFoundTags(t *testing.T) {
function TestToJSON (line 232) | func TestToJSON(t *testing.T) {
function TestFromJSON (line 260) | func TestFromJSON(t *testing.T) {
function TestUpdateByMap (line 293) | func TestUpdateByMap(t *testing.T) {
function TestExtractTags (line 348) | func TestExtractTags(t *testing.T) {
FILE: server/db/common/test_data/test_data.go
type TestData (line 11) | type TestData struct
function initUsers (line 26) | func initUsers(now time.Time) []*types.User {
function initCreds (line 61) | func initCreds(now time.Time, users []*types.User) []*types.Credential {
function initAuthRecords (line 104) | func initAuthRecords(now time.Time, users []*types.User) []common.AuthRe...
function initTopics (line 125) | func initTopics(now time.Time, users []*types.User) []*types.Topic {
function initSubs (line 178) | func initSubs(now time.Time, users []*types.User, topics []*types.Topic)...
function initMessages (line 258) | func initMessages(users []*types.User, topics []*types.Topic) []*types.M...
function initDevices (line 307) | func initDevices(now time.Time) []*types.DeviceDef {
function initFileDefs (line 324) | func initFileDefs(now time.Time, users []*types.User) []*types.FileDef {
function initTags (line 352) | func initTags() [][]string {
function InitTestData (line 360) | func InitTestData() *TestData {
FILE: server/db/mongodb/adapter.go
type adapter (line 28) | type adapter struct
method maybeStartTransaction (line 81) | func (a *adapter) maybeStartTransaction(sess mdb.Session) error {
method maybeCommitTransaction (line 88) | func (a *adapter) maybeCommitTransaction(ctx context.Context, sess mdb...
method Open (line 96) | func (a *adapter) Open(jsonconfig json.RawMessage) error {
method Close (line 217) | func (a *adapter) Close() error {
method IsOpen (line 228) | func (a *adapter) IsOpen() bool {
method GetDbVersion (line 233) | func (a *adapter) GetDbVersion() (int, error) {
method updateDbVersion (line 253) | func (a *adapter) updateDbVersion(v int) error {
method CheckDbVersion (line 263) | func (a *adapter) CheckDbVersion() error {
method Version (line 278) | func (a *adapter) Version() int {
method Stats (line 283) | func (a *adapter) Stats() any {
method GetName (line 297) | func (a *adapter) GetName() string {
method SetMaxResults (line 302) | func (a *adapter) SetMaxResults(val int) error {
method CreateDb (line 313) | func (a *adapter) CreateDb(reset bool) error {
method UpgradeDb (line 456) | func (a *adapter) UpgradeDb() error {
method UserCreate (line 592) | func (a *adapter) UserCreate(usr *t.User) error {
method UserGet (line 601) | func (a *adapter) UserGet(id t.Uid) (*t.User, error) {
method UserGetAll (line 618) | func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
method UserDelete (line 647) | func (a *adapter) UserDelete(uid t.Uid, hard bool) error {
method topicStateForUser (line 822) | func (a *adapter) topicStateForUser(uid t.Uid, now time.Time, update a...
method UserUpdate (line 859) | func (a *adapter) UserUpdate(uid t.Uid, update map[string]any) error {
method UserUpdateTags (line 879) | func (a *adapter) UserUpdateTags(uid t.Uid, add, remove, reset []strin...
method UserGetByCred (line 906) | func (a *adapter) UserGetByCred(method, value string) (t.Uid, error) {
method UserUnreadCount (line 926) | func (a *adapter) UserUnreadCount(ids ...t.Uid) (map[t.Uid]int, error) {
method UserGetUnvalidated (line 1005) | func (a *adapter) UserGetUnvalidated(lastUpdatedBefore time.Time, limi...
method CredUpsert (line 1099) | func (a *adapter) CredUpsert(cred *t.Credential) (bool, error) {
method CredGetActive (line 1165) | func (a *adapter) CredGetActive(uid t.Uid, method string) (*t.Credenti...
method CredGetAll (line 1185) | func (a *adapter) CredGetAll(uid t.Uid, method string, validatedOnly b...
method credDel (line 1211) | func (a *adapter) credDel(ctx context.Context, uid t.Uid, method, valu...
method CredDel (line 1250) | func (a *adapter) CredDel(uid t.Uid, method, value string) error {
method CredConfirm (line 1255) | func (a *adapter) CredConfirm(uid t.Uid, method string) error {
method CredFail (line 1272) | func (a *adapter) CredFail(uid t.Uid, method string) error {
method AuthGetUniqueRecord (line 1289) | func (a *adapter) AuthGetUniqueRecord(unique string) (t.Uid, auth.Leve...
method AuthGetRecord (line 1315) | func (a *adapter) AuthGetRecord(uid t.Uid, scheme string) (string, aut...
method AuthAddRecord (line 1341) | func (a *adapter) AuthAddRecord(uid t.Uid, scheme, unique string, auth...
method AuthDelScheme (line 1359) | func (a *adapter) AuthDelScheme(uid t.Uid, scheme string) error {
method authDelAllRecords (line 1367) | func (a *adapter) authDelAllRecords(ctx context.Context, uid t.Uid) (i...
method AuthDelAllRecords (line 1373) | func (a *adapter) AuthDelAllRecords(uid t.Uid) (int, error) {
method AuthUpdRecord (line 1378) | func (a *adapter) AuthUpdRecord(uid t.Uid, scheme, unique string,
method undeleteSubscription (line 1430) | func (a *adapter) undeleteSubscription(sub *t.Subscription) error {
method TopicCreate (line 1447) | func (a *adapter) TopicCreate(topic *t.Topic) error {
method TopicCreateP2P (line 1453) | func (a *adapter) TopicCreateP2P(initiator, invited *t.Subscription) e...
method TopicGet (line 1488) | func (a *adapter) TopicGet(topic string) (*t.Topic, error) {
method TopicsForUser (line 1522) | func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.Q...
method UsersForTopic (line 1709) | func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *...
method topicNamesForUser (line 1830) | func (a *adapter) topicNamesForUser(collection string, filter b.M, fie...
method p2pTopicsForUser (line 1856) | func (a *adapter) p2pTopicsForUser(uid t.Uid) ([]string, error) {
method OwnTopics (line 1866) | func (a *adapter) OwnTopics(uid t.Uid) ([]string, error) {
method ChannelsForUser (line 1873) | func (a *adapter) ChannelsForUser(uid t.Uid) ([]string, error) {
method TopicShare (line 1885) | func (a *adapter) TopicShare(topic string, shares []*t.Subscription) e...
method TopicDelete (line 1919) | func (a *adapter) TopicDelete(topic string, isChan, hard bool) error {
method TopicUpdateOnMessage (line 1955) | func (a *adapter) TopicUpdateOnMessage(topic string, msg *t.Message) e...
method subscriptionCount (line 1959) | func (a *adapter) subscriptionCount(topic string) (int64, error) {
method TopicUpdateSubCnt (line 1968) | func (a *adapter) TopicUpdateSubCnt(topic string) error {
method TopicUpdate (line 1979) | func (a *adapter) TopicUpdate(topic string, update map[string]any) err...
method TopicOwnerChange (line 1987) | func (a *adapter) TopicOwnerChange(topic string, newOwner t.Uid) error {
method topicUpdate (line 1991) | func (a *adapter) topicUpdate(topic string, update map[string]any) err...
method SubscriptionGet (line 2002) | func (a *adapter) SubscriptionGet(topic string, user t.Uid, keepDelete...
method SubsForUser (line 2021) | func (a *adapter) SubsForUser(user t.Uid) ([]t.Subscription, error) {
method SubsForTopic (line 2046) | func (a *adapter) SubsForTopic(topic string, keepDeleted bool, opts *t...
method SubsUpdate (line 2086) | func (a *adapter) SubsUpdate(topic string, user t.Uid, update map[stri...
method SubsDelete (line 2103) | func (a *adapter) SubsDelete(topic string, user t.Uid) error {
method clearUserDellog (line 2152) | func (a *adapter) clearUserDellog(sc mdb.SessionContext, forUser strin...
method subsDelete (line 2181) | func (a *adapter) subsDelete(ctx context.Context, filter b.M, hard boo...
method Find (line 2231) | func (a *adapter) Find(caller, prefPrefix string, req [][]string, opt ...
method FindOne (line 2431) | func (a *adapter) FindOne(tag string) (string, error) {
method MessageSave (line 2467) | func (a *adapter) MessageSave(msg *t.Message) error {
method MessageGetAll (line 2473) | func (a *adapter) MessageGetAll(topic string, forUser t.Uid, opts *t.Q...
method messagesHardDelete (line 2521) | func (a *adapter) messagesHardDelete(topic string) error {
method MessageDeleteList (line 2561) | func (a *adapter) MessageDeleteList(topic string, toDel *t.DelMessage)...
method MessageGetDeleted (line 2659) | func (a *adapter) MessageGetDeleted(topic string, forUser t.Uid, opts ...
method DeviceUpsert (line 2705) | func (a *adapter) DeviceUpsert(uid t.Uid, dev *t.DeviceDef) error {
method deviceInsert (line 2748) | func (a *adapter) deviceInsert(userId string, dev *t.DeviceDef) error {
method DeviceGetAll (line 2763) | func (a *adapter) DeviceGetAll(uids ...t.Uid) (map[t.Uid][]t.DeviceDef...
method DeviceDelete (line 2801) | func (a *adapter) DeviceDelete(uid t.Uid, deviceID string) error {
method FileStartUpload (line 2817) | func (a *adapter) FileStartUpload(fd *t.FileDef) error {
method FileFinishUpload (line 2823) | func (a *adapter) FileFinishUpload(fd *t.FileDef, success bool, size i...
method FileGet (line 2856) | func (a *adapter) FileGet(fid string) (*t.FileDef, error) {
method FileDeleteUnused (line 2872) | func (a *adapter) FileDeleteUnused(olderThan time.Time, limit int) ([]...
method decFileUseCounter (line 2905) | func (a *adapter) decFileUseCounter(ctx context.Context, collection st...
method FileLinkAttachments (line 2927) | func (a *adapter) FileLinkAttachments(topic string, userId, msgId t.Ui...
method PCacheGet (line 3002) | func (a *adapter) PCacheGet(key string) (string, error) {
method PCacheUpsert (line 3015) | func (a *adapter) PCacheUpsert(key string, value string, failOnDuplica...
method PCacheDelete (line 3042) | func (a *adapter) PCacheDelete(key string) error {
method PCacheExpire (line 3048) | func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) ...
method GetTestDB (line 3059) | func (a *adapter) GetTestDB() any {
method isDbInitialized (line 3063) | func (a *adapter) isDbInitialized() bool {
constant adpVersion (line 42) | adpVersion = 116
constant adapterName (line 43) | adapterName = "mongodb"
constant defaultHost (line 45) | defaultHost = "localhost:27017"
constant defaultDatabase (line 46) | defaultDatabase = "tinode"
constant defaultMaxResults (line 48) | defaultMaxResults = 1024
constant defaultMaxMessageResults (line 50) | defaultMaxMessageResults = 100
constant defaultAuthMechanism (line 52) | defaultAuthMechanism = "SCRAM-SHA-256"
constant defaultAuthSource (line 53) | defaultAuthSource = "admin"
type configType (line 57) | type configType struct
function createSystemTopic (line 575) | func createSystemTopic(a *adapter) error {
function rangeToFilter (line 2542) | func rangeToFilter(delRanges []t.Range, filter b.M) b.M {
function GetTestAdapter (line 3074) | func GetTestAdapter() *adapter {
function init (line 3078) | func init() {
function contains (line 3082) | func contains(s []string, e string) bool {
function union (line 3091) | func union(userTags, addTags []string) []string {
function diff (line 3100) | func diff(userTags, removeTags []string) []string {
function normalizeUpdateMap (line 3111) | func normalizeUpdateMap(update map[string]any) map[string]any {
function unmarshalBsonD (line 3123) | func unmarshalBsonD(bsonObj any) any {
function copyBsonMap (line 3145) | func copyBsonMap(mp b.M) b.M {
function isDuplicateErr (line 3152) | func isDuplicateErr(err error) bool {
FILE: server/db/mongodb/tests/mongo_test.go
type configType (line 37) | type configType struct
function TestCreateDb (line 50) | func TestCreateDb(t *testing.T) {
function TestUserCreate (line 57) | func TestUserCreate(t *testing.T) {
function TestCredUpsert (line 72) | func TestCredUpsert(t *testing.T) {
function TestAuthAddRecord (line 119) | func TestAuthAddRecord(t *testing.T) {
function TestTopicCreate (line 135) | func TestTopicCreate(t *testing.T) {
function TestTopicCreateP2P (line 148) | func TestTopicCreateP2P(t *testing.T) {
function TestTopicShare (line 170) | func TestTopicShare(t *testing.T) {
function TestMessageSave (line 176) | func TestMessageSave(t *testing.T) {
function TestFileStartUpload (line 200) | func TestFileStartUpload(t *testing.T) {
function TestUserGet (line 210) | func TestUserGet(t *testing.T) {
function TestUserGetAll (line 226) | func TestUserGetAll(t *testing.T) {
function TestUserGetByCred (line 250) | func TestUserGetByCred(t *testing.T) {
function TestCredGetActive (line 266) | func TestCredGetActive(t *testing.T) {
function TestCredGetAll (line 285) | func TestCredGetAll(t *testing.T) {
function TestAuthGetUniqueRecord (line 310) | func TestAuthGetUniqueRecord(t *testing.T) {
function TestAuthGetRecord (line 333) | func TestAuthGetRecord(t *testing.T) {
function TestTopicGet (line 356) | func TestTopicGet(t *testing.T) {
function TestTopicsForUser (line 374) | func TestTopicsForUser(t *testing.T) {
function TestUsersForTopic (line 418) | func TestUsersForTopic(t *testing.T) {
function TestOwnTopics (line 448) | func TestOwnTopics(t *testing.T) {
function TestSubscriptionGet (line 461) | func TestSubscriptionGet(t *testing.T) {
function TestSubsForUser (line 480) | func TestSubsForUser(t *testing.T) {
function TestSubsForTopic (line 499) | func TestSubsForTopic(t *testing.T) {
function TestFind (line 521) | func TestFind(t *testing.T) {
function TestMessageGetAll (line 532) | func TestMessageGetAll(t *testing.T) {
function TestFileGet (line 556) | func TestFileGet(t *testing.T) {
function TestUserUpdate (line 569) | func TestUserUpdate(t *testing.T) {
function TestUserUpdateTags (line 592) | func TestUserUpdateTags(t *testing.T) {
function TestCredFail (line 630) | func TestCredFail(t *testing.T) {
function TestCredConfirm (line 650) | func TestCredConfirm(t *testing.T) {
function TestAuthUpdRecord (line 675) | func TestAuthUpdRecord(t *testing.T) {
function TestTopicUpdateOnMessage (line 712) | func TestTopicUpdateOnMessage(t *testing.T) {
function TestTopicUpdate (line 734) | func TestTopicUpdate(t *testing.T) {
function TestTopicOwnerChange (line 749) | func TestTopicOwnerChange(t *testing.T) {
function TestSubsUpdate (line 761) | func TestSubsUpdate(t *testing.T) {
function TestSubsDelete (line 785) | func TestSubsDelete(t *testing.T) {
function TestDeviceUpsert (line 797) | func TestDeviceUpsert(t *testing.T) {
function TestMessageAttachments (line 842) | func TestMessageAttachments(t *testing.T) {
function TestFileFinishUpload (line 868) | func TestFileFinishUpload(t *testing.T) {
function TestDeviceGetAll (line 882) | func TestDeviceGetAll(t *testing.T) {
function TestDeviceDelete (line 901) | func TestDeviceDelete(t *testing.T) {
function TestPCacheUpsert (line 929) | func TestPCacheUpsert(t *testing.T) {
function TestPCacheGet (line 947) | func TestPCacheGet(t *testing.T) {
function TestPCacheDelete (line 963) | func TestPCacheDelete(t *testing.T) {
function TestPCacheExpire (line 976) | func TestPCacheExpire(t *testing.T) {
function TestCredDel (line 989) | func TestCredDel(t *testing.T) {
function TestAuthDelScheme (line 1022) | func TestAuthDelScheme(t *testing.T) {
function TestAuthDelAllRecords (line 1026) | func TestAuthDelAllRecords(t *testing.T) {
function TestSubsDelForUser (line 1042) | func TestSubsDelForUser(t *testing.T) {
function TestMessageDeleteList (line 1046) | func TestMessageDeleteList(t *testing.T) {
function TestTopicDelete (line 1126) | func TestTopicDelete(t *testing.T) {
function TestFileDeleteUnused (line 1163) | func TestFileDeleteUnused(t *testing.T) {
function TestUserDelete (line 1175) | func TestUserDelete(t *testing.T) {
function TestUserUnreadCount (line 1199) | func TestUserUnreadCount(t *testing.T) {
function TestMessageGetDeleted (line 1234) | func TestMessageGetDeleted(t *testing.T) {
function mismatchErrorString (line 1250) | func mismatchErrorString(key string, got, want any) string {
function init (line 1254) | func init() {
FILE: server/db/mysql/adapter.go
type adapter (line 27) | type adapter struct
method getContext (line 81) | func (a *adapter) getContext() (context.Context, context.CancelFunc) {
method getContextForTx (line 88) | func (a *adapter) getContextForTx() (context.Context, context.CancelFu...
method Open (line 96) | func (a *adapter) Open(jsonconfig json.RawMessage) error {
method Close (line 182) | func (a *adapter) Close() error {
method IsOpen (line 194) | func (a *adapter) IsOpen() bool {
method GetDbVersion (line 199) | func (a *adapter) GetDbVersion() (int, error) {
method updateDbVersion (line 222) | func (a *adapter) updateDbVersion(v int) error {
method CheckDbVersion (line 235) | func (a *adapter) CheckDbVersion() error {
method Version (line 250) | func (adapter) Version() int {
method Stats (line 255) | func (a *adapter) Stats() any {
method GetName (line 263) | func (a *adapter) GetName() string {
method SetMaxResults (line 268) | func (a *adapter) SetMaxResults(val int) error {
method CreateDb (line 279) | func (a *adapter) CreateDb(reset bool) error {
method UpgradeDb (line 579) | func (a *adapter) UpgradeDb() error {
method UserCreate (line 887) | func (a *adapter) UserCreate(user *t.User) error {
method AuthAddRecord (line 925) | func (a *adapter) AuthAddRecord(uid t.Uid, scheme, unique string, auth...
method AuthDelScheme (line 948) | func (a *adapter) AuthDelScheme(user t.Uid, scheme string) error {
method AuthDelAllRecords (line 958) | func (a *adapter) AuthDelAllRecords(user t.Uid) (int, error) {
method AuthUpdRecord (line 973) | func (a *adapter) AuthUpdRecord(uid t.Uid, scheme, unique string, auth...
method AuthGetRecord (line 1011) | func (a *adapter) AuthGetRecord(uid t.Uid, scheme string) (string, aut...
method AuthGetUniqueRecord (line 1042) | func (a *adapter) AuthGetUniqueRecord(unique string) (t.Uid, auth.Leve...
method UserGet (line 1072) | func (a *adapter) UserGet(uid t.Uid) (*t.User, error) {
method UserGetAll (line 1094) | func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
method UserDelete (line 1136) | func (a *adapter) UserDelete(uid t.Uid, hard bool) error {
method topicStateForUser (line 1286) | func (a *adapter) topicStateForUser(tx *sqlx.Tx, decoded_uid int64, no...
method UserUpdate (line 1317) | func (a *adapter) UserUpdate(uid t.Uid, update map[string]any) error {
method UserUpdateTags (line 1367) | func (a *adapter) UserUpdateTags(uid t.Uid, add, remove, reset []strin...
method UserGetByCred (line 1422) | func (a *adapter) UserGetByCred(method, value string) (t.Uid, error) {
method UserUnreadCount (line 1444) | func (a *adapter) UserUnreadCount(ids ...t.Uid) (map[t.Uid]int, error) {
method UserGetUnvalidated (line 1485) | func (a *adapter) UserGetUnvalidated(lastUpdatedBefore time.Time, limi...
method topicCreate (line 1517) | func (a *adapter) topicCreate(tx *sqlx.Tx, topic *t.Topic) error {
method TopicCreate (line 1532) | func (a *adapter) TopicCreate(topic *t.Topic) error {
method TopicCreateP2P (line 1588) | func (a *adapter) TopicCreateP2P(initiator, invited *t.Subscription) e...
method TopicGet (line 1626) | func (a *adapter) TopicGet(topic string) (*t.Topic, error) {
method TopicsForUser (line 1670) | func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.Q...
method UsersForTopic (line 1893) | func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *...
method topicNamesForUser (line 2012) | func (a *adapter) topicNamesForUser(sqlQuery string, includeChan bool,...
method OwnTopics (line 2045) | func (a *adapter) OwnTopics(uid t.Uid) ([]string, error) {
method ChannelsForUser (line 2051) | func (a *adapter) ChannelsForUser(uid t.Uid) ([]string, error) {
method TopicShare (line 2058) | func (a *adapter) TopicShare(topic string, shares []*t.Subscription) e...
method TopicDelete (line 2091) | func (a *adapter) TopicDelete(topic string, isChan, hard bool) error {
method TopicUpdateOnMessage (line 2148) | func (a *adapter) TopicUpdateOnMessage(topic string, msg *t.Message) e...
method TopicUpdateSubCnt (line 2159) | func (a *adapter) TopicUpdateSubCnt(topic string) error {
method TopicUpdate (line 2172) | func (a *adapter) TopicUpdate(topic string, update map[string]any) err...
method TopicOwnerChange (line 2215) | func (a *adapter) TopicOwnerChange(topic string, newOwner t.Uid) error {
method SubscriptionGet (line 2225) | func (a *adapter) SubscriptionGet(topic string, user t.Uid, keepDelete...
method SubsForUser (line 2253) | func (a *adapter) SubsForUser(forUser t.Uid) ([]t.Subscription, error) {
method SubsForTopic (line 2287) | func (a *adapter) SubsForTopic(topic string, keepDeleted bool, opts *t...
method SubsUpdate (line 2342) | func (a *adapter) SubsUpdate(topic string, user t.Uid, update map[stri...
method SubsDelete (line 2375) | func (a *adapter) SubsDelete(topic string, user t.Uid) error {
method SubsDelForUser (line 2475) | func (a *adapter) SubsDelForUser(user t.Uid, hard bool) error {
method Find (line 2500) | func (a *adapter) Find(caller, promoPrefix string, req [][]string, opt...
method FindOne (line 2614) | func (a *adapter) FindOne(tag string) (string, error) {
method MessageSave (line 2661) | func (a *adapter) MessageSave(msg *t.Message) error {
method MessageGetAll (line 2681) | func (a *adapter) MessageGetAll(topic string, forUser t.Uid, opts *t.Q...
method MessageGetDeleted (line 2750) | func (a *adapter) MessageGetDeleted(topic string, forUser t.Uid, opts ...
method MessageDeleteList (line 2926) | func (a *adapter) MessageDeleteList(topic string, toDel *t.DelMessage)...
method DeviceUpsert (line 2960) | func (a *adapter) DeviceUpsert(uid t.Uid, def *t.DeviceDef) error {
method DeviceGetAll (line 2994) | func (a *adapter) DeviceGetAll(uids ...t.Uid) (map[t.Uid][]t.DeviceDef...
method DeviceDelete (line 3062) | func (a *adapter) DeviceDelete(uid t.Uid, deviceID string) error {
method CredUpsert (line 3096) | func (a *adapter) CredUpsert(cred *t.Credential) (bool, error) {
method CredDel (line 3228) | func (a *adapter) CredDel(uid t.Uid, method, value string) error {
method CredConfirm (line 3252) | func (a *adapter) CredConfirm(uid t.Uid, method string) error {
method CredFail (line 3275) | func (a *adapter) CredFail(uid t.Uid, method string) error {
method CredGetActive (line 3286) | func (a *adapter) CredGetActive(uid t.Uid, method string) (*t.Credenti...
method CredGetAll (line 3307) | func (a *adapter) CredGetAll(uid t.Uid, method string, validatedOnly b...
method FileStartUpload (line 3339) | func (a *adapter) FileStartUpload(fd *t.FileDef) error {
method FileFinishUpload (line 3359) | func (a *adapter) FileFinishUpload(fd *t.FileDef, success bool, size i...
method FileGet (line 3400) | func (a *adapter) FileGet(fid string) (*t.FileDef, error) {
method FileDeleteUnused (line 3429) | func (a *adapter) FileDeleteUnused(olderThan time.Time, limit int) ([]...
method FileLinkAttachments (line 3496) | func (a *adapter) FileLinkAttachments(topic string, userId, msgId t.Ui...
method PCacheGet (line 3568) | func (a *adapter) PCacheGet(key string) (string, error) {
method PCacheUpsert (line 3585) | func (a *adapter) PCacheUpsert(key string, value string, failOnDuplica...
method PCacheDelete (line 3611) | func (a *adapter) PCacheDelete(key string) error {
method PCacheExpire (line 3622) | func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) ...
method GetTestDB (line 3637) | func (a *adapter) GetTestDB() any {
constant adpVersion (line 44) | adpVersion = 116
constant adapterName (line 45) | adapterName = "mysql"
constant defaultDSN (line 47) | defaultDSN = "root:@tcp(localhost:3306)/tinode?parseTime=true"
constant defaultDatabase (line 48) | defaultDatabase = "tinode"
constant defaultMaxResults (line 50) | defaultMaxResults = 1024
constant defaultMaxMessageResults (line 52) | defaultMaxMessageResults = 100
constant txTimeoutMultiplier (line 56) | txTimeoutMultiplier = 1.5
type configType (line 59) | type configType struct
function createSystemTopic (line 836) | func createSystemTopic(tx *sql.Tx) error {
function addTags (line 844) | func addTags(tx *sqlx.Tx, table, keyName string, keyVal any, tags []stri...
function removeTags (line 869) | func removeTags(tx *sqlx.Tx, table, keyName string, keyVal any, tags []s...
function createSubscription (line 1555) | func createSubscription(tx *sqlx.Tx, sub *t.Subscription, undelete bool)...
function subsDelForUser (line 2431) | func subsDelForUser(tx *sqlx.Tx, decoded_uid int64, hard bool) error {
function messageDeleteList (line 2828) | func messageDeleteList(tx *sqlx.Tx, topic string, toDel *t.DelMessage) e...
function deviceHasher (line 2949) | func deviceHasher(deviceID string) string {
function deviceDelete (line 3043) | func deviceDelete(tx *sqlx.Tx, uid t.Uid, deviceID string) error {
function credDel (line 3177) | func credDel(tx *sqlx.Tx, uid t.Uid, method, value string) error {
function isDupe (line 3644) | func isDupe(err error) bool {
function isMissingTable (line 3653) | func isMissingTable(err error) bool {
function isMissingDb (line 3662) | func isMissingDb(err error) bool {
function GetTestAdapter (line 3672) | func GetTestAdapter() *adapter {
function init (line 3676) | func init() {
FILE: server/db/mysql/schema.sql
type kvmeta (line 18) | CREATE TABLE kvmeta(
type users (line 28) | CREATE TABLE users(
type usertags (line 46) | CREATE TABLE usertags(
type devices (line 58) | CREATE TABLE devices(
type auth (line 73) | CREATE TABLE auth(
type topics (line 90) | CREATE TABLE topics(
type messages (line 152) | CREATE TABLE messages(
type dellog (line 170) | CREATE TABLE dellog(
type fileuploads (line 208) | CREATE TABLE fileuploads(
type filemsglinks (line 224) | CREATE TABLE filemsglinks(
FILE: server/db/mysql/tests/mysql_test.go
type configType (line 35) | type configType struct
function TestCreateDb (line 50) | func TestCreateDb(t *testing.T) {
function TestUserCreate (line 59) | func TestUserCreate(t *testing.T) {
function TestCredUpsert (line 79) | func TestCredUpsert(t *testing.T) {
function TestAuthAddRecord (line 126) | func TestAuthAddRecord(t *testing.T) {
function TestTopicCreate (line 142) | func TestTopicCreate(t *testing.T) {
function decodeUid (line 155) | func decodeUid(u string) int64 {
function TestTopicCreateP2P (line 159) | func TestTopicCreateP2P(t *testing.T) {
function TestTopicShare (line 184) | func TestTopicShare(t *testing.T) {
function TestMessageSave (line 211) | func TestMessageSave(t *testing.T) {
function TestFileStartUpload (line 235) | func TestFileStartUpload(t *testing.T) {
function TestUserGet (line 245) | func TestUserGet(t *testing.T) {
function TestUserGetAll (line 267) | func TestUserGetAll(t *testing.T) {
function TestUserGetByCred (line 295) | func TestUserGetByCred(t *testing.T) {
function TestCredGetActive (line 311) | func TestCredGetActive(t *testing.T) {
function TestCredGetAll (line 330) | func TestCredGetAll(t *testing.T) {
function TestAuthGetUniqueRecord (line 355) | func TestAuthGetUniqueRecord(t *testing.T) {
function TestAuthGetRecord (line 377) | func TestAuthGetRecord(t *testing.T) {
function TestTopicGet (line 399) | func TestTopicGet(t *testing.T) {
function TestTopicsForUser (line 417) | func TestTopicsForUser(t *testing.T) {
function TestUsersForTopic (line 459) | func TestUsersForTopic(t *testing.T) {
function TestOwnTopics (line 489) | func TestOwnTopics(t *testing.T) {
function TestSubscriptionGet (line 502) | func TestSubscriptionGet(t *testing.T) {
function TestSubsForUser (line 522) | func TestSubsForUser(t *testing.T) {
function TestSubsForTopic (line 541) | func TestSubsForTopic(t *testing.T) {
function TestFind (line 563) | func TestFind(t *testing.T) {
function TestMessageGetAll (line 574) | func TestMessageGetAll(t *testing.T) {
function TestFileGet (line 598) | func TestFileGet(t *testing.T) {
function TestUserUpdate (line 611) | func TestUserUpdate(t *testing.T) {
function TestUserUpdateTags (line 639) | func TestUserUpdateTags(t *testing.T) {
function TestCredFail (line 691) | func TestCredFail(t *testing.T) {
function TestCredConfirm (line 716) | func TestCredConfirm(t *testing.T) {
function TestAuthUpdRecord (line 741) | func TestAuthUpdRecord(t *testing.T) {
function TestTopicUpdateOnMessage (line 776) | func TestTopicUpdateOnMessage(t *testing.T) {
function TestTopicUpdate (line 802) | func TestTopicUpdate(t *testing.T) {
function TestTopicOwnerChange (line 820) | func TestTopicOwnerChange(t *testing.T) {
function TestSubsUpdate (line 836) | func TestSubsUpdate(t *testing.T) {
function TestSubsDelete (line 868) | func TestSubsDelete(t *testing.T) {
function TestDeviceUpsert (line 884) | func TestDeviceUpsert(t *testing.T) {
function TestFileFinishUpload (line 937) | func TestFileFinishUpload(t *testing.T) {
function TestMessageAttachments (line 950) | func TestMessageAttachments(t *testing.T) {
function TestDeviceGetAll (line 969) | func TestDeviceGetAll(t *testing.T) {
function TestDeviceDelete (line 988) | func TestDeviceDelete(t *testing.T) {
function TestPCacheUpsert (line 1016) | func TestPCacheUpsert(t *testing.T) {
function TestPCacheGet (line 1034) | func TestPCacheGet(t *testing.T) {
function TestPCacheDelete (line 1050) | func TestPCacheDelete(t *testing.T) {
function TestPCacheExpire (line 1063) | func TestPCacheExpire(t *testing.T) {
function TestCredDel (line 1076) | func TestCredDel(t *testing.T) {
function TestAuthDelScheme (line 1103) | func TestAuthDelScheme(t *testing.T) {
function TestAuthDelAllRecords (line 1107) | func TestAuthDelAllRecords(t *testing.T) {
function TestSubsDelForUser (line 1123) | func TestSubsDelForUser(t *testing.T) {
function TestMessageDeleteList (line 1127) | func TestMessageDeleteList(t *testing.T) {
function TestTopicDelete (line 1194) | func TestTopicDelete(t *testing.T) {
function TestFileDeleteUnused (line 1223) | func TestFileDeleteUnused(t *testing.T) {
function TestUserDelete (line 1233) | func TestUserDelete(t *testing.T) {
function TestUserUnreadCount (line 1264) | func TestUserUnreadCount(t *testing.T) {
function TestMessageGetDeleted (line 1297) | func TestMessageGetDeleted(t *testing.T) {
function mismatchErrorString (line 1313) | func mismatchErrorString(key string, got, want any) string {
function init (line 1317) | func init() {
FILE: server/db/postgres/adapter.go
type adapter (line 32) | type adapter struct
method getContext (line 97) | func (a *adapter) getContext() (context.Context, context.CancelFunc) {
method getContextForTx (line 104) | func (a *adapter) getContextForTx() (context.Context, context.CancelFu...
method Open (line 112) | func (a *adapter) Open(jsonconfig json.RawMessage) error {
method Close (line 191) | func (a *adapter) Close() error {
method IsOpen (line 202) | func (a *adapter) IsOpen() bool {
method GetDbVersion (line 207) | func (a *adapter) GetDbVersion() (int, error) {
method updateDbVersion (line 230) | func (a *adapter) updateDbVersion(v int) error {
method CheckDbVersion (line 243) | func (a *adapter) CheckDbVersion() error {
method Version (line 258) | func (adapter) Version() int {
method Stats (line 263) | func (a *adapter) Stats() any {
method GetName (line 271) | func (a *adapter) GetName() string {
method SetMaxResults (line 276) | func (a *adapter) SetMaxResults(val int) error {
method CreateDb (line 287) | func (a *adapter) CreateDb(reset bool) error {
method UpgradeDb (line 595) | func (a *adapter) UpgradeDb() error {
method UserCreate (line 742) | func (a *adapter) UserCreate(user *t.User) error {
method AuthAddRecord (line 781) | func (a *adapter) AuthAddRecord(uid t.Uid, scheme, unique string, auth...
method AuthDelScheme (line 804) | func (a *adapter) AuthDelScheme(user t.Uid, scheme string) error {
method AuthDelAllRecords (line 814) | func (a *adapter) AuthDelAllRecords(user t.Uid) (int, error) {
method AuthUpdRecord (line 830) | func (a *adapter) AuthUpdRecord(uid t.Uid, scheme, unique string, auth...
method AuthGetRecord (line 867) | func (a *adapter) AuthGetRecord(uid t.Uid, scheme string) (string, aut...
method AuthGetUniqueRecord (line 899) | func (a *adapter) AuthGetUniqueRecord(unique string) (t.Uid, auth.Leve...
method UserGet (line 930) | func (a *adapter) UserGet(uid t.Uid) (*t.User, error) {
method UserGetAll (line 958) | func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
method UserDelete (line 996) | func (a *adapter) UserDelete(uid t.Uid, hard bool) error {
method topicStateForUser (line 1146) | func (a *adapter) topicStateForUser(ctx context.Context, tx pgx.Tx, de...
method UserUpdate (line 1179) | func (a *adapter) UserUpdate(uid t.Uid, update map[string]any) error {
method UserUpdateTags (line 1246) | func (a *adapter) UserUpdateTags(uid t.Uid, add, remove, reset []strin...
method UserGetByCred (line 1308) | func (a *adapter) UserGetByCred(method, value string) (t.Uid, error) {
method UserUnreadCount (line 1330) | func (a *adapter) UserUnreadCount(ids ...t.Uid) (map[t.Uid]int, error) {
method UserGetUnvalidated (line 1371) | func (a *adapter) UserGetUnvalidated(lastUpdatedBefore time.Time, limi...
method topicCreate (line 1407) | func (a *adapter) topicCreate(ctx context.Context, tx pgx.Tx, topic *t...
method TopicCreate (line 1422) | func (a *adapter) TopicCreate(topic *t.Topic) error {
method TopicCreateP2P (line 1488) | func (a *adapter) TopicCreateP2P(initiator, invited *t.Subscription) e...
method TopicGet (line 1525) | func (a *adapter) TopicGet(topic string) (*t.Topic, error) {
method TopicsForUser (line 1572) | func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.Q...
method UsersForTopic (line 1809) | func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *...
method topicNamesForUser (line 1931) | func (a *adapter) topicNamesForUser(sqlQuery string, includeChan bool,...
method OwnTopics (line 1964) | func (a *adapter) OwnTopics(uid t.Uid) ([]string, error) {
method ChannelsForUser (line 1970) | func (a *adapter) ChannelsForUser(uid t.Uid) ([]string, error) {
method TopicShare (line 1977) | func (a *adapter) TopicShare(topic string, shares []*t.Subscription) e...
method TopicDelete (line 2009) | func (a *adapter) TopicDelete(topic string, isChan, hard bool) error {
method TopicUpdateOnMessage (line 2065) | func (a *adapter) TopicUpdateOnMessage(topic string, msg *t.Message) e...
method TopicUpdateSubCnt (line 2076) | func (a *adapter) TopicUpdateSubCnt(topic string) error {
method TopicUpdate (line 2087) | func (a *adapter) TopicUpdate(topic string, update map[string]any) err...
method TopicOwnerChange (line 2130) | func (a *adapter) TopicOwnerChange(topic string, newOwner t.Uid) error {
method SubscriptionGet (line 2140) | func (a *adapter) SubscriptionGet(topic string, user t.Uid, keepDelete...
method SubsForUser (line 2173) | func (a *adapter) SubsForUser(forUser t.Uid) ([]t.Subscription, error) {
method SubsForTopic (line 2213) | func (a *adapter) SubsForTopic(topic string, keepDeleted bool, opts *t...
method SubsUpdate (line 2273) | func (a *adapter) SubsUpdate(topic string, user t.Uid, update map[stri...
method SubsDelete (line 2307) | func (a *adapter) SubsDelete(topic string, user t.Uid) error {
method SubsDelForUser (line 2406) | func (a *adapter) SubsDelForUser(user t.Uid, hard bool) error {
method Find (line 2432) | func (a *adapter) Find(caller, promoPrefix string, req [][]string, opt...
method FindOne (line 2553) | func (a *adapter) FindOne(tag string) (string, error) {
method MessageSave (line 2600) | func (a *adapter) MessageSave(msg *t.Message) error {
method MessageGetAll (line 2619) | func (a *adapter) MessageGetAll(topic string, forUser t.Uid, opts *t.Q...
method MessageGetDeleted (line 2687) | func (a *adapter) MessageGetDeleted(topic string, forUser t.Uid, opts ...
method MessageDeleteList (line 2874) | func (a *adapter) MessageDeleteList(topic string, toDel *t.DelMessage)...
method DeviceUpsert (line 2906) | func (a *adapter) DeviceUpsert(uid t.Uid, def *t.DeviceDef) error {
method DeviceGetAll (line 2939) | func (a *adapter) DeviceGetAll(uids ...t.Uid) (map[t.Uid][]t.DeviceDef...
method DeviceDelete (line 3006) | func (a *adapter) DeviceDelete(uid t.Uid, deviceID string) error {
method CredUpsert (line 3040) | func (a *adapter) CredUpsert(cred *t.Credential) (bool, error) {
method CredDel (line 3173) | func (a *adapter) CredDel(uid t.Uid, method, value string) error {
method CredConfirm (line 3197) | func (a *adapter) CredConfirm(uid t.Uid, method string) error {
method CredFail (line 3220) | func (a *adapter) CredFail(uid t.Uid, method string) error {
method CredGetActive (line 3231) | func (a *adapter) CredGetActive(uid t.Uid, method string) (*t.Credenti...
method CredGetAll (line 3253) | func (a *adapter) CredGetAll(uid t.Uid, method string, validatedOnly b...
method FileStartUpload (line 3296) | func (a *adapter) FileStartUpload(fd *t.FileDef) error {
method FileFinishUpload (line 3314) | func (a *adapter) FileFinishUpload(fd *t.FileDef, success bool, size i...
method FileGet (line 3355) | func (a *adapter) FileGet(fid string) (*t.FileDef, error) {
method FileDeleteUnused (line 3385) | func (a *adapter) FileDeleteUnused(olderThan time.Time, limit int) ([]...
method FileLinkAttachments (line 3453) | func (a *adapter) FileLinkAttachments(topic string, userId, msgId t.Ui...
method PCacheGet (line 3526) | func (a *adapter) PCacheGet(key string) (string, error) {
method PCacheUpsert (line 3543) | func (a *adapter) PCacheUpsert(key string, value string, failOnDuplica...
method PCacheDelete (line 3568) | func (a *adapter) PCacheDelete(key string) error {
method PCacheExpire (line 3579) | func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) ...
method GetTestDB (line 3594) | func (a *adapter) GetTestDB() any {
constant adpVersion (line 50) | adpVersion = 116
constant adapterName (line 51) | adapterName = "postgres"
constant defaultMaxResults (line 53) | defaultMaxResults = 1024
constant defaultMaxMessageResults (line 55) | defaultMaxMessageResults = 100
constant txTimeoutMultiplier (line 59) | txTimeoutMultiplier = 1.5
type configType (line 62) | type configType struct
function createSystemTopic (line 699) | func createSystemTopic(tx pgx.Tx) error {
function addTags (line 707) | func addTags(ctx context.Context, tx pgx.Tx, table, keyName string, keyV...
function removeTags (line 729) | func removeTags(ctx context.Context, tx pgx.Tx, table, keyName string, k...
function tempFetchTags (line 1229) | func tempFetchTags(ctx context.Context, tx pgx.Tx, decoded_uid int64) ([...
function createSubscription (line 1445) | func createSubscription(ctx context.Context, tx pgx.Tx, sub *t.Subscript...
function subsDelForUser (line 2361) | func subsDelForUser(ctx context.Context, tx pgx.Tx, decoded_uid int64, h...
function messageDeleteList (line 2765) | func messageDeleteList(ctx context.Context, tx pgx.Tx, topic string, toD...
function deviceHasher (line 2897) | func deviceHasher(deviceID string) string {
function deviceDelete (line 2988) | func deviceDelete(ctx context.Context, tx pgx.Tx, uid t.Uid, deviceID st...
function credDel (line 3121) | func credDel(ctx context.Context, tx pgx.Tx, uid t.Uid, method, value st...
function isDupe (line 3601) | func isDupe(err error) bool {
function isMissingTable (line 3610) | func isMissingTable(err error) bool {
function isMissingDb (line 3619) | func isMissingDb(err error) bool {
function setConnStr (line 3629) | func setConnStr(c configType) (string, error) {
function expandQuery (line 3653) | func expandQuery(query string, args ...any) (string, []any) {
function flattenSlice (line 3665) | func flattenSlice(slice []any) []any {
function rebindWithStart (line 3682) | func rebindWithStart(query string, startAt int) string {
function GetTestAdapter (line 3702) | func GetTestAdapter() *adapter {
function init (line 3706) | func init() {
FILE: server/db/postgres/tests/postgres_test.go
type configType (line 36) | type configType struct
function TestCreateDb (line 52) | func TestCreateDb(t *testing.T) {
function TestUserCreate (line 61) | func TestUserCreate(t *testing.T) {
function TestCredUpsert (line 78) | func TestCredUpsert(t *testing.T) {
function TestAuthAddRecord (line 125) | func TestAuthAddRecord(t *testing.T) {
function TestTopicCreate (line 141) | func TestTopicCreate(t *testing.T) {
function decodeUid (line 161) | func decodeUid(u string) int64 {
function encodeUid (line 165) | func encodeUid(u string) types.Uid {
function TestTopicCreateP2P (line 170) | func TestTopicCreateP2P(t *testing.T) {
function TestTopicShare (line 199) | func TestTopicShare(t *testing.T) {
function TestMessageSave (line 226) | func TestMessageSave(t *testing.T) {
function TestFileStartUpload (line 250) | func TestFileStartUpload(t *testing.T) {
function TestUserGet (line 260) | func TestUserGet(t *testing.T) {
function TestUserGetAll (line 280) | func TestUserGetAll(t *testing.T) {
function TestUserGetByCred (line 306) | func TestUserGetByCred(t *testing.T) {
function TestCredGetActive (line 322) | func TestCredGetActive(t *testing.T) {
function TestCredGetAll (line 341) | func TestCredGetAll(t *testing.T) {
function TestAuthGetUniqueRecord (line 366) | func TestAuthGetUniqueRecord(t *testing.T) {
function TestAuthGetRecord (line 388) | func TestAuthGetRecord(t *testing.T) {
function TestTopicGet (line 410) | func TestTopicGet(t *testing.T) {
function TestTopicsForUser (line 428) | func TestTopicsForUser(t *testing.T) {
function TestUsersForTopic (line 470) | func TestUsersForTopic(t *testing.T) {
function TestOwnTopics (line 500) | func TestOwnTopics(t *testing.T) {
function TestChannelsForUser (line 513) | func TestChannelsForUser(t *testing.T) {
function TestSubscriptionGet (line 525) | func TestSubscriptionGet(t *testing.T) {
function TestSubsForUser (line 545) | func TestSubsForUser(t *testing.T) {
function TestSubsForTopic (line 564) | func TestSubsForTopic(t *testing.T) {
function TestFind (line 586) | func TestFind(t *testing.T) {
function TestFindOne (line 596) | func TestFindOne(t *testing.T) {
function TestMessageGetAll (line 617) | func TestMessageGetAll(t *testing.T) {
function TestFileGet (line 640) | func TestFileGet(t *testing.T) {
function TestUserUpdate (line 651) | func TestUserUpdate(t *testing.T) {
function TestUserUpdateTags (line 679) | func TestUserUpdateTags(t *testing.T) {
function TestUserGetUnvalidated (line 722) | func TestUserGetUnvalidated(t *testing.T) {
function TestCredFail (line 735) | func TestCredFail(t *testing.T) {
function TestCredConfirm (line 760) | func TestCredConfirm(t *testing.T) {
function TestAuthUpdRecord (line 785) | func TestAuthUpdRecord(t *testing.T) {
function TestTopicUpdateOnMessage (line 820) | func TestTopicUpdateOnMessage(t *testing.T) {
function TestTopicUpdate (line 846) | func TestTopicUpdate(t *testing.T) {
function TestTopicUpdateSubCnt (line 864) | func TestTopicUpdateSubCnt(t *testing.T) {
function TestTopicOwnerChange (line 883) | func TestTopicOwnerChange(t *testing.T) {
function TestSubsUpdate (line 899) | func TestSubsUpdate(t *testing.T) {
function TestSubsDelete (line 931) | func TestSubsDelete(t *testing.T) {
function TestSubsDelForUser (line 947) | func TestSubsDelForUser(t *testing.T) {
function TestDeviceUpsert (line 951) | func TestDeviceUpsert(t *testing.T) {
function TestMessageAttachments (line 996) | func TestMessageAttachments(t *testing.T) {
function TestFileFinishUpload (line 1014) | func TestFileFinishUpload(t *testing.T) {
function TestDeviceGetAll (line 1028) | func TestDeviceGetAll(t *testing.T) {
function TestDeviceDelete (line 1045) | func TestDeviceDelete(t *testing.T) {
function TestPCacheUpsert (line 1075) | func TestPCacheUpsert(t *testing.T) {
function TestPCacheGet (line 1093) | func TestPCacheGet(t *testing.T) {
function TestPCacheDelete (line 1109) | func TestPCacheDelete(t *testing.T) {
function TestPCacheExpire (line 1122) | func TestPCacheExpire(t *testing.T) {
function TestCredDel (line 1135) | func TestCredDel(t *testing.T) {
function TestAuthDelScheme (line 1163) | func TestAuthDelScheme(t *testing.T) {
function TestAuthDelAllRecords (line 1177) | func TestAuthDelAllRecords(t *testing.T) {
function TestMessageDeleteList (line 1193) | func TestMessageDeleteList(t *testing.T) {
function TestTopicDelete (line 1260) | func TestTopicDelete(t *testing.T) {
function TestFileDeleteUnused (line 1289) | func TestFileDeleteUnused(t *testing.T) {
function TestUserDelete (line 1299) | func TestUserDelete(t *testing.T) {
function TestUserUnreadCount (line 1329) | func TestUserUnreadCount(t *testing.T) {
function TestMessageGetDeleted (line 1362) | func TestMessageGetDeleted(t *testing.T) {
function mismatchErrorString (line 1378) | func mismatchErrorString(key string, got, want any) string {
function init (line 1382) | func init() {
FILE: server/db/rethinkdb/adapter.go
type adapter (line 24) | type adapter struct
method Open (line 66) | func (a *adapter) Open(jsonconfig json.RawMessage) error {
method Close (line 142) | func (a *adapter) Close() error {
method IsOpen (line 155) | func (a *adapter) IsOpen() bool {
method GetDbVersion (line 160) | func (a *adapter) GetDbVersion() (int, error) {
method updateDbVersion (line 188) | func (a *adapter) updateDbVersion(v int) error {
method CheckDbVersion (line 198) | func (a *adapter) CheckDbVersion() error {
method Version (line 213) | func (adapter) Version() int {
method Stats (line 218) | func (a *adapter) Stats() any {
method GetName (line 238) | func (a *adapter) GetName() string {
method SetMaxResults (line 243) | func (a *adapter) SetMaxResults(val int) error {
method CreateDb (line 254) | func (a *adapter) CreateDb(reset bool) error {
method UpgradeDb (line 404) | func (a *adapter) UpgradeDb() error {
method UserCreate (line 585) | func (a *adapter) UserCreate(user *t.User) error {
method AuthAddRecord (line 591) | func (a *adapter) AuthAddRecord(uid t.Uid, scheme, unique string, auth...
method AuthDelScheme (line 612) | func (a *adapter) AuthDelScheme(uid t.Uid, scheme string) error {
method AuthDelAllRecords (line 621) | func (a *adapter) AuthDelAllRecords(uid t.Uid) (int, error) {
method AuthUpdRecord (line 627) | func (a *adapter) AuthUpdRecord(uid t.Uid, scheme, unique string, auth...
method AuthGetRecord (line 687) | func (a *adapter) AuthGetRecord(uid t.Uid, scheme string) (string, aut...
method AuthGetUniqueRecord (line 717) | func (a *adapter) AuthGetUniqueRecord(unique string) (t.Uid, auth.Leve...
method UserGet (line 745) | func (a *adapter) UserGet(uid t.Uid) (*t.User, error) {
method UserGetAll (line 765) | func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
method UserDelete (line 795) | func (a *adapter) UserDelete(uid t.Uid, hard bool) error {
method clearUserDellog (line 965) | func (a *adapter) clearUserDellog(uid t.Uid, topics []any) error {
method topicNamesForUser (line 1022) | func (a *adapter) topicNamesForUser(query rdb.Term, includeChan bool) ...
method p2pTopicsForUser (line 1050) | func (a *adapter) p2pTopicsForUser(uid t.Uid) ([]any, error) {
method topicStateForUser (line 1058) | func (a *adapter) topicStateForUser(uid t.Uid, now time.Time, update a...
method UserUpdate (line 1106) | func (a *adapter) UserUpdate(uid t.Uid, update map[string]any) error {
method UserUpdateTags (line 1121) | func (a *adapter) UserUpdateTags(uid t.Uid, add, remove, reset []strin...
method UserGetByCred (line 1164) | func (a *adapter) UserGetByCred(method, value string) (t.Uid, error) {
method UserUnreadCount (line 1187) | func (a *adapter) UserUnreadCount(ids ...t.Uid) (map[t.Uid]int, error) {
method UserGetUnvalidated (line 1244) | func (a *adapter) UserGetUnvalidated(lastUpdatedBefore time.Time, limi...
method TopicCreate (line 1296) | func (a *adapter) TopicCreate(topic *t.Topic) error {
method TopicCreateP2P (line 1302) | func (a *adapter) TopicCreateP2P(initiator, invited *t.Subscription) e...
method TopicGet (line 1343) | func (a *adapter) TopicGet(topic string) (*t.Topic, error) {
method TopicsForUser (line 1397) | func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.Q...
method UsersForTopic (line 1576) | func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *...
method OwnTopics (line 1685) | func (a *adapter) OwnTopics(uid t.Uid) ([]string, error) {
method ChannelsForUser (line 1701) | func (a *adapter) ChannelsForUser(uid t.Uid) ([]string, error) {
method TopicShare (line 1722) | func (a *adapter) TopicShare(topic string, shares []*t.Subscription) e...
method TopicDelete (line 1753) | func (a *adapter) TopicDelete(topic string, isChan, hard bool) error {
method TopicUpdateOnMessage (line 1784) | func (a *adapter) TopicUpdateOnMessage(topic string, msg *t.Message) e...
method TopicUpdateSubCnt (line 1797) | func (a *adapter) TopicUpdateSubCnt(topic string) error {
method TopicUpdate (line 1822) | func (a *adapter) TopicUpdate(topic string, update map[string]any) err...
method TopicOwnerChange (line 1831) | func (a *adapter) TopicOwnerChange(topic string, newOwner t.Uid) error {
method SubscriptionGet (line 1838) | func (a *adapter) SubscriptionGet(topic string, user t.Uid, keepDelete...
method SubsForUser (line 1864) | func (a *adapter) SubsForUser(forUser t.Uid) ([]t.Subscription, error) {
method SubsForTopic (line 1887) | func (a *adapter) SubsForTopic(topic string, keepDeleted bool, opts *t...
method SubsUpdate (line 1925) | func (a *adapter) SubsUpdate(topic string, user t.Uid, update map[stri...
method SubsDelete (line 1939) | func (a *adapter) SubsDelete(topic string, user t.Uid) error {
method subsDelForTopic (line 2011) | func (a *adapter) subsDelForTopic(topic string, isChan, hard bool) err...
method subsDelForUser (line 2034) | func (a *adapter) subsDelForUser(user t.Uid, hard bool) error {
method Find (line 2078) | func (a *adapter) Find(caller, promoPrefix string, req [][]string, opt...
method FindOne (line 2191) | func (a *adapter) FindOne(tag string) (string, error) {
method MessageSave (line 2221) | func (a *adapter) MessageSave(msg *t.Message) error {
method MessageGetAll (line 2227) | func (a *adapter) MessageGetAll(topic string, forUser t.Uid, opts *t.Q...
method MessageGetDeleted (line 2280) | func (a *adapter) MessageGetDeleted(topic string, forUser t.Uid, opts ...
method messagesHardDelete (line 2341) | func (a *adapter) messagesHardDelete(topic string) error {
method MessageDeleteList (line 2391) | func (a *adapter) MessageDeleteList(topic string, toDel *t.DelMessage)...
method DeviceUpsert (line 2489) | func (a *adapter) DeviceUpsert(uid t.Uid, def *t.DeviceDef) error {
method DeviceGetAll (line 2533) | func (a *adapter) DeviceGetAll(uids ...t.Uid) (map[t.Uid][]t.DeviceDef...
method DeviceDelete (line 2577) | func (a *adapter) DeviceDelete(uid t.Uid, deviceID string) error {
method CredUpsert (line 2600) | func (a *adapter) CredUpsert(cred *t.Credential) (bool, error) {
method CredDel (line 2667) | func (a *adapter) CredDel(uid t.Uid, method, value string) error {
method credGetActive (line 2707) | func (a *adapter) credGetActive(uid t.Uid, method string) (*t.Credenti...
method CredConfirm (line 2730) | func (a *adapter) CredConfirm(uid t.Uid, method string) error {
method CredFail (line 2756) | func (a *adapter) CredFail(uid t.Uid, method string) error {
method CredGetActive (line 2769) | func (a *adapter) CredGetActive(uid t.Uid, method string) (*t.Credenti...
method CredGetAll (line 2774) | func (a *adapter) CredGetAll(uid t.Uid, method string, validatedOnly b...
method FileStartUpload (line 2803) | func (a *adapter) FileStartUpload(fd *t.FileDef) error {
method FileFinishUpload (line 2809) | func (a *adapter) FileFinishUpload(fd *t.FileDef, success bool, size i...
method FileGet (line 2838) | func (a *adapter) FileGet(fid string) (*t.FileDef, error) {
method FileLinkAttachments (line 2859) | func (a *adapter) FileLinkAttachments(topic string, userId, msgId t.Ui...
method FileDeleteUnused (line 2947) | func (a *adapter) FileDeleteUnused(olderThan time.Time, limit int) ([]...
method decFileUseCounter (line 2979) | func (a *adapter) decFileUseCounter(query rdb.Term) error {
method PCacheGet (line 3010) | func (a *adapter) PCacheGet(key string) (string, error) {
method PCacheUpsert (line 3030) | func (a *adapter) PCacheUpsert(key string, value string, failOnDuplica...
method PCacheDelete (line 3058) | func (a *adapter) PCacheDelete(key string) error {
method PCacheExpire (line 3064) | func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) ...
method GetTestDB (line 3078) | func (a *adapter) GetTestDB() any {
constant adpVersion (line 35) | adpVersion = 116
constant adapterName (line 36) | adapterName = "rethinkdb"
constant defaultHost (line 38) | defaultHost = "localhost:28015"
constant defaultDatabase (line 39) | defaultDatabase = "tinode"
constant defaultMaxResults (line 41) | defaultMaxResults = 1024
constant defaultMaxMessageResults (line 43) | defaultMaxMessageResults = 100
type configType (line 47) | type configType struct
function createSystemTopic (line 570) | func createSystemTopic(a *adapter) error {
function rangeToQuery (line 2367) | func rangeToQuery(delRanges []t.Range, topic string, query rdb.Term) rdb...
function deviceHasher (line 2478) | func deviceHasher(deviceID string) string {
function isNoResults (line 3084) | func isNoResults(err error) bool {
function isMissingDb (line 3092) | func isMissingDb(err error) bool {
function GetTestAdapter (line 3103) | func GetTestAdapter() *adapter {
function init (line 3107) | func init() {
FILE: server/db/rethinkdb/tests/rethink_test.go
type configType (line 33) | type configType struct
function TestCreateDb (line 48) | func TestCreateDb(t *testing.T) {
function TestUserCreate (line 57) | func TestUserCreate(t *testing.T) {
function TestCredUpsert (line 79) | func TestCredUpsert(t *testing.T) {
function TestAuthAddRecord (line 126) | func TestAuthAddRecord(t *testing.T) {
function TestTopicCreate (line 142) | func TestTopicCreate(t *testing.T) {
function TestTopicCreateP2P (line 163) | func TestTopicCreateP2P(t *testing.T) {
function TestTopicShare (line 191) | func TestTopicShare(t *testing.T) {
function TestMessageSave (line 206) | func TestMessageSave(t *testing.T) {
function TestFileStartUpload (line 230) | func TestFileStartUpload(t *testing.T) {
function TestUserGet (line 240) | func TestUserGet(t *testing.T) {
function TestUserGetAll (line 260) | func TestUserGetAll(t *testing.T) {
function TestUserGetByCred (line 286) | func TestUserGetByCred(t *testing.T) {
function TestCredGetActive (line 302) | func TestCredGetActive(t *testing.T) {
function TestCredGetAll (line 321) | func TestCredGetAll(t *testing.T) {
function TestUserGetUnvalidated (line 346) | func TestUserGetUnvalidated(t *testing.T) {
function TestAuthGetUniqueRecord (line 359) | func TestAuthGetUniqueRecord(t *testing.T) {
function TestAuthGetRecord (line 381) | func TestAuthGetRecord(t *testing.T) {
function TestTopicGet (line 403) | func TestTopicGet(t *testing.T) {
function TestTopicsForUser (line 421) | func TestTopicsForUser(t *testing.T) {
function TestUsersForTopic (line 463) | func TestUsersForTopic(t *testing.T) {
function TestOwnTopics (line 493) | func TestOwnTopics(t *testing.T) {
function TestChannelsForUser (line 506) | func TestChannelsForUser(t *testing.T) {
function TestSubscriptionGet (line 518) | func TestSubscriptionGet(t *testing.T) {
function TestSubsForUser (line 538) | func TestSubsForUser(t *testing.T) {
function TestSubsForTopic (line 557) | func TestSubsForTopic(t *testing.T) {
function TestFind (line 579) | func TestFind(t *testing.T) {
function TestFindOne (line 590) | func TestFindOne(t *testing.T) {
function TestMessageGetAll (line 611) | func TestMessageGetAll(t *testing.T) {
function TestFileGet (line 634) | func TestFileGet(t *testing.T) {
function TestUserUpdate (line 647) | func TestUserUpdate(t *testing.T) {
function TestUserUpdateTags (line 679) | func TestUserUpdateTags(t *testing.T) {
function TestCredFail (line 713) | func TestCredFail(t *testing.T) {
function TestCredConfirm (line 744) | func TestCredConfirm(t *testing.T) {
function TestAuthUpdRecord (line 784) | func TestAuthUpdRecord(t *testing.T) {
function TestTopicUpdateOnMessage (line 826) | func TestTopicUpdateOnMessage(t *testing.T) {
function TestTopicUpdate (line 858) | func TestTopicUpdate(t *testing.T) {
function TestTopicUpdateSubCnt (line 882) | func TestTopicUpdateSubCnt(t *testing.T) {
function TestTopicOwnerChange (line 906) | func TestTopicOwnerChange(t *testing.T) {
function TestSubsUpdate (line 928) | func TestSubsUpdate(t *testing.T) {
function TestSubsDelete (line 972) | func TestSubsDelete(t *testing.T) {
function TestDeviceUpsert (line 994) | func TestDeviceUpsert(t *testing.T) {
function TestMessageAttachments (line 1069) | func TestMessageAttachments(t *testing.T) {
function TestFileFinishUpload (line 1107) | func TestFileFinishUpload(t *testing.T) {
function TestDeviceGetAll (line 1121) | func TestDeviceGetAll(t *testing.T) {
function TestDeviceDelete (line 1138) | func TestDeviceDelete(t *testing.T) {
function TestPCacheUpsert (line 1183) | func TestPCacheUpsert(t *testing.T) {
function TestPCacheGet (line 1201) | func TestPCacheGet(t *testing.T) {
function TestPCacheDelete (line 1217) | func TestPCacheDelete(t *testing.T) {
function TestPCacheExpire (line 1230) | func TestPCacheExpire(t *testing.T) {
function TestCredDel (line 1243) | func TestCredDel(t *testing.T) {
function TestAuthDelScheme (line 1282) | func TestAuthDelScheme(t *testing.T) {
function TestAuthDelAllRecords (line 1296) | func TestAuthDelAllRecords(t *testing.T) {
function TestMessageDeleteList (line 1312) | func TestMessageDeleteList(t *testing.T) {
function TestTopicDelete (line 1412) | func TestTopicDelete(t *testing.T) {
function TestFileDeleteUnused (line 1448) | func TestFileDeleteUnused(t *testing.T) {
function TestUserDelete (line 1460) | func TestUserDelete(t *testing.T) {
function TestMessageGetDeleted (line 1497) | func TestMessageGetDeleted(t *testing.T) {
function TestUserUnreadCount (line 1512) | func TestUserUnreadCount(t *testing.T) {
function mismatchErrorString (line 1546) | func mismatchErrorString(key string, got, want any) string {
function init (line 1550) | func init() {
FILE: server/drafty/drafty.go
constant maxDataSize (line 13) | maxDataSize = 128
constant maxDataCount (line 15) | maxDataCount = 8
type style (line 23) | type style struct
type entity (line 30) | type entity struct
type document (line 35) | type document struct
type span (line 44) | type span struct
method styleToSpan (line 134) | func (s *span) styleToSpan(in *style) error {
type node (line 52) | type node struct
type previewState (line 58) | type previewState struct
function Preview (line 68) | func Preview(content any, length int) (string, error) {
type plainTextState (line 103) | type plainTextState struct
function PlainText (line 109) | func PlainText(content any) (string, error) {
type spanfmt (line 153) | type spanfmt struct
type formatter (line 169) | type formatter
function toTree (line 173) | func toTree(drafty *document) (*node, error) {
function forEach (line 237) | func forEach(g *graphemes, start, end int, spans []*span) ([]*node, erro...
function plainTextFormatter (line 283) | func plainTextFormatter(n *node, ctx any) error {
function previewFormatter (line 339) | func previewFormatter(n *node, ctx any) error {
function nullableMapGet (line 412) | func nullableMapGet(data map[string]any, key string) (string, bool) {
function decodeAsDrafty (line 421) | func decodeAsDrafty(content any) (*document, error) {
function decodeAsStyle (line 475) | func decodeAsStyle(content any) (*style, error) {
function decodeAsEntity (line 513) | func decodeAsEntity(content any) (*entity, error) {
function copyLight (line 541) | func copyLight(in any) map[string]any {
function intFromNumeric (line 570) | func intFromNumeric(num any) (int, error) {
function getVariableTypeSize (line 593) | func getVariableTypeSize(x any) int {
function isFixedLengthType (line 605) | func isFixedLengthType(x any) bool {
FILE: server/drafty/drafty_test.go
function TestPlainText (line 98) | func TestPlainText(t *testing.T) {
function TestPreview (line 141) | func TestPreview(t *testing.T) {
FILE: server/drafty/grapheme.go
type graphemes (line 8) | type graphemes struct
method length (line 33) | func (g *graphemes) length() int {
method string (line 41) | func (g *graphemes) string() string {
method slice (line 49) | func (g *graphemes) slice(start, end int) *graphemes {
method append (line 69) | func (g *graphemes) append(other *graphemes) *graphemes {
function prepareGraphemes (line 18) | func prepareGraphemes(str string) *graphemes {
FILE: server/hdl_files.go
function largeFileServeHTTP (line 37) | func largeFileServeHTTP(wrt http.ResponseWriter, req *http.Request) {
function largeFileReceiveHTTP (line 170) | func largeFileReceiveHTTP(wrt http.ResponseWriter, req *http.Request) {
method LargeFileServe (line 353) | func (*grpcNodeServer) LargeFileServe(req *pbx.FileDownReq, stream pbx.N...
method LargeFileReceive (line 447) | func (*grpcNodeServer) LargeFileReceive(stream pbx.Node_LargeFileReceive...
function largeFileRunGarbageCollection (line 580) | func largeFileRunGarbageCollection(period time.Duration, blockSize int) ...
function authFileRequest (line 604) | func authFileRequest(authMethod, secret, sid, remoteAddr string) (types....
FILE: server/hdl_grpc.go
type grpcNodeServer (line 25) | type grpcNodeServer struct
method MessageLoop (line 38) | func (*grpcNodeServer) MessageLoop(stream pbx.Node_MessageLoopServer) ...
method closeGrpc (line 29) | func (sess *Session) closeGrpc() {
method sendMessageGrpc (line 76) | func (sess *Session) sendMessageGrpc(msg any) bool {
method writeGrpcLoop (line 89) | func (sess *Session) writeGrpcLoop() {
function grpcWrite (line 139) | func grpcWrite(sess *Session, msg any) error {
function serveGrpc (line 147) | func serveGrpc(addr string, kaEnabled bool, tlsConf *tls.Config) (*grpc....
FILE: server/hdl_longpoll.go
method sendMessageLp (line 22) | func (sess *Session) sendMessageLp(wrt http.ResponseWriter, msg any) bool {
method writeOnce (line 37) | func (sess *Session) writeOnce(wrt http.ResponseWriter, req *http.Reques...
function lpWrite (line 91) | func lpWrite(wrt http.ResponseWriter, msg any) error {
method readOnce (line 97) | func (sess *Session) readOnce(wrt http.ResponseWriter, req *http.Request...
function serveLongPoll (line 124) | func serveLongPoll(wrt http.ResponseWriter, req *http.Request) {
FILE: server/hdl_websock.go
constant writeWait (line 24) | writeWait = 10 * time.Second
constant pongWait (line 27) | pongWait = idleSessionTimeout
constant pingPeriod (line 30) | pingPeriod = (pongWait * 9) / 10
method closeWS (line 33) | func (sess *Session) closeWS() {
method readLoop (line 39) | func (sess *Session) readLoop() {
method sendMessage (line 67) | func (sess *Session) sendMessage(msg any) bool {
method writeLoop (line 84) | func (sess *Session) writeLoop() {
function wsWrite (line 148) | func wsWrite(ws *websocket.Conn, mt int, msg any) error {
function serveWebSocket (line 168) | func serveWebSocket(wrt http.ResponseWriter, req *http.Request) {
FILE: server/http.go
function listenAndServe (line 31) | func listenAndServe(addr string, mux *http.ServeMux, tlfConf *tls.Config...
function signalHandler (line 154) | func signalHandler() <-chan bool {
type errorResponseWriter (line 174) | type errorResponseWriter struct
method WriteHeader (line 179) | func (w *errorResponseWriter) WriteHeader(status int) {
method Write (line 189) | func (w *errorResponseWriter) Write(p []byte) (n int, err error) {
function httpErrorHandler (line 204) | func httpErrorHandler(h http.Handler) http.Handler {
function serve404 (line 212) | func serve404(wrt http.ResponseWriter, req *http.Request) {
function tlsRedirect (line 226) | func tlsRedirect(toPort string) http.HandlerFunc {
function optionalHttpHeaders (line 264) | func optionalHttpHeaders(handler http.Handler) http.Handler {
function cacheControlHandler (line 289) | func cacheControlHandler(maxAge int, handler http.Handler) http.Handler {
function getAPIKey (line 301) | func getAPIKey(req *http.Request) string {
function getHttpAuth (line 330) | func getHttpAuth(req *http.Request) (method, secret string) {
function getRemoteAddr (line 368) | func getRemoteAddr(req *http.Request) string {
type debugSession (line 383) | type debugSession struct
type debugTopic (line 393) | type debugTopic struct
type debugCachedUser (line 403) | type debugCachedUser struct
type debugDump (line 410) | type debugDump struct
function serveStatus (line 419) | func serveStatus(wrt http.ResponseWriter, req *http.Request) {
FILE: server/http_pprof.go
function servePprof (line 20) | func servePprof(mux *http.ServeMux, serveAt string) {
function profileHandler (line 31) | func profileHandler(wrt http.ResponseWriter, req *http.Request) {
function servePprofError (line 47) | func servePprofError(wrt http.ResponseWriter, status int, txt string) {
FILE: server/hub.go
type topicUnreg (line 33) | type topicUnreg struct
type userStatusReq (line 48) | type userStatusReq struct
type Hub (line 56) | type Hub struct
method topicGet (line 89) | func (h *Hub) topicGet(name string) *Topic {
method topicPut (line 96) | func (h *Hub) topicPut(name string, t *Topic) {
method topicDel (line 101) | func (h *Hub) topicDel(name string) {
method run (line 151) | func (h *Hub) run() {
method topicsStateForUser (line 350) | func (h *Hub) topicsStateForUser(uid types.Uid, suspended bool) {
method topicUnreg (line 392) | func (h *Hub) topicUnreg(sess *Session, topic string, msg *ClientComMe...
method stopTopicsForUser (line 567) | func (h *Hub) stopTopicsForUser(uid types.Uid, reason int, alldone cha...
function newHub (line 106) | func newHub() *Hub {
function replyOfflineTopicGetDesc (line 605) | func replyOfflineTopicGetDesc(sess *Session, msg *ClientComMessage) {
function replyOfflineTopicGetSub (line 720) | func replyOfflineTopicGetSub(sess *Session, msg *ClientComMessage) {
function replyOfflineTopicSetSub (line 778) | func replyOfflineTopicSetSub(sess *Session, msg *ClientComMessage) {
FILE: server/init_topic.go
function topicInit (line 21) | func topicInit(t *Topic, join *ClientComMessage, h *Hub) {
function initTopicMe (line 138) | func initTopicMe(t *Topic, sreg *ClientComMessage) error {
function initTopicFnd (line 188) | func initTopicFnd(t *Topic, sreg *ClientComMessage) error {
function initTopicP2P (line 227) | func initTopicP2P(t *Topic, sreg *ClientComMessage) error {
function initTopicNewGrp (line 502) | func initTopicNewGrp(t *Topic, sreg *ClientComMessage, isChan bool) error {
function initTopicGrp (line 628) | func initTopicGrp(t *Topic) error {
function initTopicSys (line 675) | func initTopicSys(t *Topic) error {
function initTopicSlf (line 709) | func initTopicSlf(t *Topic, sreg *ClientComMessage) error {
method loadSubscribers (line 817) | func (t *Topic) loadSubscribers() error {
FILE: server/logs/logs.go
function parseFlags (line 19) | func parseFlags(logFlags string) int {
function Init (line 50) | func Init(output io.Writer, logFlags string) {
FILE: server/main.go
constant currentVersion (line 65) | currentVersion = "0.25"
constant minSupportedVersion (line 67) | minSupportedVersion = "0.20"
constant idleSessionTimeout (line 70) | idleSessionTimeout = time.Second * 55
constant idleMasterTopicTimeout (line 72) | idleMasterTopicTimeout = time.Second * 4
constant idleProxyTopicTimeout (line 74) | idleProxyTopicTimeout = time.Second * 2
constant defaultMaxMessageSize (line 77) | defaultMaxMessageSize = 1 << 19
constant defaultMaxSubscriberCount (line 81) | defaultMaxSubscriberCount = 256
constant defaultMaxTagCount (line 84) | defaultMaxTagCount = 16
constant minTagLength (line 87) | minTagLength = 2
constant maxTagLength (line 89) | maxTagLength = 96
constant uaTimerDelay (line 92) | uaTimerDelay = time.Second * 5
constant defaultMaxDeleteCount (line 95) | defaultMaxDeleteCount = 1024
constant defaultApiPath (line 98) | defaultApiPath = "/"
constant defaultStaticMount (line 101) | defaultStaticMount = "/"
constant defaultStaticPath (line 104) | defaultStaticPath = "static"
constant defaultCountryCode (line 108) | defaultCountryCode = "US"
constant defaultCallEstablishmentTimeout (line 111) | defaultCallEstablishmentTimeout = 30
type credValidator (line 130) | type credValidator struct
type validatorConfig (line 220) | type validatorConfig struct
type accountGcConfig (line 230) | type accountGcConfig struct
type mediaConfig (line 241) | type mediaConfig struct
type configType (line 255) | type configType struct
function main (line 334) | func main() {
FILE: server/media/fs/filesys.go
constant defaultServeURL (line 26) | defaultServeURL = "/v0/file/s/"
constant defaultCacheControl (line 27) | defaultCacheControl = "max-age=86400"
constant handlerName (line 29) | handlerName = "fs"
type fileConfig (line 32) | type fileConfig struct
type fshandler (line 40) | type fshandler struct
method Init (line 46) | func (fh *fshandler) Init(jsconf string) error {
method Headers (line 74) | func (fh *fshandler) Headers(method string, url *url.URL, headers http...
method Upload (line 112) | func (fh *fshandler) Upload(fdef *types.FileDef, file io.Reader) (stri...
method Download (line 155) | func (fh *fshandler) Download(url string) (*types.FileDef, media.ReadS...
method Delete (line 180) | func (fh *fshandler) Delete(locations []string) error {
method GetIdFromUrl (line 192) | func (fh *fshandler) GetIdFromUrl(url string) types.Uid {
method getFileRecord (line 197) | func (fh *fshandler) getFileRecord(fid types.Uid) (*types.FileDef, err...
function etagFromPath (line 208) | func etagFromPath(path string) string {
function init (line 215) | func init() {
FILE: server/media/media.go
type ReadSeekCloser (line 19) | type ReadSeekCloser interface
type Handler (line 26) | type Handler interface
type AllowedOrigin (line 48) | type AllowedOrigin struct
function GetIdFromUrl (line 58) | func GetIdFromUrl(url, serveUrl string) types.Uid {
function ParseCORSAllow (line 69) | func ParseCORSAllow(allowed []string) ([]AllowedOrigin, error) {
function matchCORSOrigin (line 104) | func matchCORSOrigin(allowed []AllowedOrigin, origin string) string {
function matchCORSMethod (line 164) | func matchCORSMethod(allowMethods []string, method string) bool {
function CORSHandler (line 175) | func CORSHandler(method string, reqHeader http.Header, allowedOrigins []...
FILE: server/media/media_test.go
function TestMatchCORSOrigin (line 8) | func TestMatchCORSOrigin(t *testing.T) {
function containsSubstring (line 219) | func containsSubstring(str, substr string) bool {
FILE: server/media/s3/s3.go
constant defaultServeURL (line 31) | defaultServeURL = "/v0/file/s/"
constant defaultCacheControl (line 32) | defaultCacheControl = "no-cache, must-revalidate"
constant handlerName (line 34) | handlerName = "s3"
constant defaultPresignDuration (line 36) | defaultPresignDuration = 120
type awsconfig (line 39) | type awsconfig struct
type awshandler (line 53) | type awshandler struct
method Init (line 74) | func (ah *awshandler) Init(jsconf string) error {
method Headers (line 171) | func (ah *awshandler) Headers(method string, url *url.URL, headers htt...
method Upload (line 237) | func (ah *awshandler) Upload(fdef *types.FileDef, file io.Reader) (str...
method Download (line 277) | func (ah *awshandler) Download(url string) (*types.FileDef, media.Read...
method Delete (line 282) | func (ah *awshandler) Delete(locations []string) error {
method GetIdFromUrl (line 298) | func (ah *awshandler) GetIdFromUrl(url string) types.Uid {
method getFileRecord (line 303) | func (ah *awshandler) getFileRecord(fid types.Uid) (*types.FileDef, er...
type readerCounter (line 60) | type readerCounter struct
method Read (line 67) | func (rc *readerCounter) Read(buf []byte) (int, error) {
function init (line 314) | func init() {
FILE: server/pbconverter.go
function pbServCtrlSerializeBasic (line 14) | func pbServCtrlSerializeBasic(ctrl *MsgServerCtrl) *pbx.ServerCtrl {
function pbServCtrlSerialize (line 31) | func pbServCtrlSerialize(ctrl *MsgServerCtrl) *pbx.ServerMsg_Ctrl {
function pbServDataSerialize (line 37) | func pbServDataSerialize(data *MsgServerData) *pbx.ServerMsg_Data {
function pbServPresSerialize (line 51) | func pbServPresSerialize(pres *MsgServerPres) *pbx.ServerMsg_Pres {
function pbServInfoSerialize (line 99) | func pbServInfoSerialize(info *MsgServerInfo) *pbx.ServerMsg_Info {
function pbServMetaSerialize (line 113) | func pbServMetaSerialize(meta *MsgServerMeta) *pbx.ServerMsg_Meta {
function pbServSerialize (line 129) | func pbServSerialize(msg *ServerComMessage) *pbx.ServerMsg {
function pbServDeserialize (line 148) | func pbServDeserialize(pkt *pbx.ServerMsg) *ServerComMessage {
function pbCliSerialize (line 240) | func pbCliSerialize(msg *ClientComMessage) *pbx.ClientMsg {
function pbCliDeserialize (line 391) | func pbCliDeserialize(pkt *pbx.ClientMsg) *ClientComMessage {
function interfaceMapToByteMap (line 505) | func interfaceMapToByteMap(in map[string]any) map[string][]byte {
function byteMapToInterfaceMap (line 515) | func byteMapToInterfaceMap(in map[string][]byte) map[string]any {
function interfaceToBytes (line 525) | func interfaceToBytes(in any) []byte {
function bytesToInterface (line 533) | func bytesToInterface(in []byte) any {
function timeToInt64 (line 544) | func timeToInt64(ts *time.Time) int64 {
function int64ToTime (line 551) | func int64ToTime(ts int64) *time.Time {
function pbGetQuerySerialize (line 559) | func pbGetQuerySerialize(in *MsgGetQuery) *pbx.GetQuery {
function pbGetQueryDeserialize (line 601) | func pbGetQueryDeserialize(in *pbx.GetQuery) *MsgGetQuery {
function pbSetDescSerialize (line 641) | func pbSetDescSerialize(in *MsgSetDesc) *pbx.SetDesc {
function pbSetDescDeserialize (line 658) | func pbSetDescDeserialize(in *pbx.SetDesc) *MsgSetDesc {
function pbSetQuerySerialize (line 680) | func pbSetQuerySerialize(in *MsgSetQuery) *pbx.SetQuery {
function pbSetQueryDeserialize (line 703) | func pbSetQueryDeserialize(in *pbx.SetQuery) *MsgSetQuery {
function pbInfoNoteWhatSerialize (line 748) | func pbInfoNoteWhatSerialize(what string) pbx.InfoNote {
function pbInfoNoteWhatDeserialize (line 765) | func pbInfoNoteWhatDeserialize(what pbx.InfoNote) string {
function pbCallEventSerialize (line 781) | func pbCallEventSerialize(event string) pbx.CallEvent {
function pbCallEventDeserialize (line 806) | func pbCallEventDeserialize(event pbx.CallEvent) string {
function pbAccessModeSerialize (line 828) | func pbAccessModeSerialize(acs *MsgAccessMode) *pbx.AccessMode {
function pbAccessModeDeserialize (line 839) | func pbAccessModeDeserialize(acs *pbx.AccessMode) *MsgAccessMode {
function pbDefaultAcsSerialize (line 850) | func pbDefaultAcsSerialize(defacs *MsgDefaultAcsMode) *pbx.DefaultAcsMode {
function pbDefaultAcsDeserialize (line 861) | func pbDefaultAcsDeserialize(defacs *pbx.DefaultAcsMode) *MsgDefaultAcsM...
function pbTopicDescSerialize (line 878) | func pbTopicDescSerialize(desc *MsgTopicDesc) *pbx.TopicDesc {
function pbTopicDescDeserialize (line 906) | func pbTopicDescDeserialize(desc *pbx.TopicDesc) *MsgTopicDesc {
function pbTopicSerializeToDesc (line 937) | func pbTopicSerializeToDesc(topic *Topic) *pbx.TopicDesc {
function pbTopicSubSliceSerialize (line 955) | func pbTopicSubSliceSerialize(subs []MsgTopicSub) []*pbx.TopicSub {
function pbTopicSubSerialize (line 967) | func pbTopicSubSerialize(sub *MsgTopicSub) *pbx.TopicSub {
function pbTopicSubSliceDeserialize (line 991) | func pbTopicSubSliceDeserialize(subs []*pbx.TopicSub) []MsgTopicSub {
function pbSubSliceDeserialize (line 1026) | func pbSubSliceDeserialize(subs []*pbx.TopicSub) []types.Subscription {
function pbDelQuerySerialize (line 1057) | func pbDelQuerySerialize(in []MsgRange) []*pbx.SeqRange {
function pbDelQueryDeserialize (line 1070) | func pbDelQueryDeserialize(in []*pbx.SeqRange) []MsgRange {
function pbDelValuesSerialize (line 1084) | func pbDelValuesSerialize(in *MsgDelValues) *pbx.DelValues {
function pbDelValuesDeserialize (line 1095) | func pbDelValuesDeserialize(in *pbx.DelValues) *MsgDelValues {
function pbClientCredSerialize (line 1106) | func pbClientCredSerialize(in *MsgCredClient) *pbx.ClientCred {
function pbClientCredsSerialize (line 1119) | func pbClientCredsSerialize(in []MsgCredClient) []*pbx.ClientCred {
function pbClientCredDeserialize (line 1132) | func pbClientCredDeserialize(in *pbx.ClientCred) *MsgCredClient {
function pbClientCredsDeserialize (line 1145) | func pbClientCredsDeserialize(in []*pbx.ClientCred) []MsgCredClient {
function pbServerCredsSerialize (line 1158) | func pbServerCredsSerialize(in []*MsgCredServer) []*pbx.ServerCred {
function pbServerCredsDeserialize (line 1174) | func pbServerCredsDeserialize(in []*pbx.ServerCred) []*MsgCredServer {
FILE: server/plugins.go
constant plgHi (line 18) | plgHi = 1 << iota
constant plgAcc (line 19) | plgAcc
constant plgLogin (line 20) | plgLogin
constant plgSub (line 21) | plgSub
constant plgLeave (line 22) | plgLeave
constant plgPub (line 23) | plgPub
constant plgGet (line 24) | plgGet
constant plgSet (line 25) | plgSet
constant plgDel (line 26) | plgDel
constant plgNote (line 27) | plgNote
constant plgData (line 28) | plgData
constant plgMeta (line 29) | plgMeta
constant plgPres (line 30) | plgPres
constant plgInfo (line 31) | plgInfo
constant plgClientMask (line 33) | plgClientMask = plgHi | plgAcc | plgLogin | plgSub | plgLeave | plgPub |...
constant plgServerMask (line 34) | plgServerMask = plgData | plgMeta | plgPres | plgInfo
constant plgActCreate (line 38) | plgActCreate = 1 << iota
constant plgActUpd (line 39) | plgActUpd
constant plgActDel (line 40) | plgActDel
constant plgActMask (line 42) | plgActMask = plgActCreate | plgActUpd | plgActDel
constant plgTopicMe (line 46) | plgTopicMe = 1 << iota
constant plgTopicFnd (line 47) | plgTopicFnd
constant plgTopicP2P (line 48) | plgTopicP2P
constant plgTopicGrp (line 49) | plgTopicGrp
constant plgTopicSys (line 50) | plgTopicSys
constant plgTopicSlf (line 51) | plgTopicSlf
constant plgTopicNew (line 52) | plgTopicNew
constant plgTopicNch (line 53) | plgTopicNch
constant plgTopicCatMask (line 55) | plgTopicCatMask = plgTopicMe | plgTopicFnd | plgTopicP2P | plgTopicGrp |...
constant plgFilterByTopicType (line 59) | plgFilterByTopicType = 1 << iota
constant plgFilterByPacket (line 60) | plgFilterByPacket
constant plgFilterByAction (line 61) | plgFilterByAction
type PluginFilter (line 74) | type PluginFilter struct
function ParsePluginFilter (line 81) | func ParsePluginFilter(s *string, filterBy int) (*PluginFilter, error) {
type pluginRPCFilterConfig (line 185) | type pluginRPCFilterConfig struct
type pluginConfig (line 202) | type pluginConfig struct
type Plugin (line 219) | type Plugin struct
function pluginsInit (line 238) | func pluginsInit(configString json.RawMessage) {
function pluginsShutdown (line 324) | func pluginsShutdown() {
function pluginGenerateClientReq (line 334) | func pluginGenerateClientReq(sess *Session, msg *ClientComMessage) *pbx....
function pluginFireHose (line 353) | func pluginFireHose(sess *Session, msg *ClientComMessage) (*ClientComMes...
function pluginFind (line 427) | func pluginFind(user types.Uid, query string) (string, []types.Subscript...
function pluginAccount (line 476) | func pluginAccount(user *types.User, action int) {
function pluginTopic (line 516) | func pluginTopic(topic *Topic, action int) {
function pluginSubscription (line 551) | func pluginSubscription(sub *types.Subscription, action int) {
function pluginMessage (line 598) | func pluginMessage(data *MsgServerData, action int) {
function pluginDoFiltering (line 633) | func pluginDoFiltering(filter *PluginFilter, msg *ClientComMessage) bool {
function pluginActionToCrud (line 706) | func pluginActionToCrud(action int) pbx.Crud {
function pluginIDAndTopic (line 719) | func pluginIDAndTopic(msg *ClientComMessage) (string, string) {
FILE: server/pres.go
type presParams (line 13) | type presParams struct
method packAcs (line 38) | func (p *presParams) packAcs() *MsgAccessMode {
type presFilters (line 27) | type presFilters struct
method addToPerSubs (line 46) | func (t *Topic) addToPerSubs(topic string, online, enabled bool) {
method loadContacts (line 69) | func (t *Topic) loadContacts(uid types.Uid) error {
method procPresReq (line 97) | func (t *Topic) procPresReq(fromUserID, what string, wantReply bool) str...
function notifyOnOrSkip (line 229) | func notifyOnOrSkip(topic, what string, online bool) string {
method presUsersOfInterest (line 254) | func (t *Topic) presUsersOfInterest(what, ua string) {
function presUsersOfInterestOffline (line 286) | func presUsersOfInterestOffline(uid types.Uid, subs []types.Subscription...
method presSubsOnline (line 316) | func (t *Topic) presSubsOnline(what, src string, params *presParams, fil...
method userIsPresencer (line 349) | func (t *Topic) userIsPresencer(uid types.Uid) bool {
method presSubsOnlineDirect (line 368) | func (t *Topic) presSubsOnlineDirect(what string, params *presParams, fi...
method presTermDirect (line 412) | func (s *Session) presTermDirect(subs []string) {
method presSubsOffline (line 432) | func (t *Topic) presSubsOffline(what string, params *presParams,
method infoSubsOffline (line 479) | func (t *Topic) infoSubsOffline(from types.Uid, what string, seq int, sk...
method infoCallSubsOffline (line 504) | func (t *Topic) infoCallSubsOffline(from string, target types.Uid, event...
function presSubsOfflineOffline (line 535) | func presSubsOfflineOffline(topic string, cat types.TopicCat, subs []typ...
method presSingleUserOffline (line 587) | func (t *Topic) presSingleUserOffline(uid types.Uid, mode types.AccessMode,
function presSingleUserOfflineOffline (line 632) | func presSingleUserOfflineOffline(uid types.Uid, original, what string, ...
method presPubMessageCount (line 663) | func (t *Topic) presPubMessageCount(uid types.Uid, mode types.AccessMode...
method presPubMessageDelete (line 684) | func (t *Topic) presPubMessageDelete(uid types.Uid, mode types.AccessMod...
function presOfflineFilter (line 708) | func presOfflineFilter(mode types.AccessMode, what string, pf *presFilte...
FILE: server/push.go
method channelSubUnsub (line 18) | func (t *Topic) channelSubUnsub(uid types.Uid, sub bool) {
method pushForData (line 27) | func (t *Topic) pushForData(fromUid types.Uid, data *MsgServerData, msgM...
method preparePushForSubReceipt (line 87) | func (t *Topic) preparePushForSubReceipt(fromUid types.Uid, now time.Tim...
method pushForP2PSub (line 111) | func (t *Topic) pushForP2PSub(fromUid, toUid types.Uid, want, given type...
method pushForGroupSub (line 122) | func (t *Topic) pushForGroupSub(fromUid types.Uid, now time.Time) *push....
function pushForChanDelete (line 146) | func pushForChanDelete(topicName string, now time.Time) *push.Receipt {
method pushForReadRcpt (line 163) | func (t *Topic) pushForReadRcpt(uid types.Uid, seq int, now time.Time) *...
function sendPush (line 188) | func sendPush(rcpt *push.Receipt) {
FILE: server/push/common/typedef.go
type Payload (line 14) | type Payload struct
method getStringAttr (line 51) | func (cp Payload) getStringAttr(field string) string {
method getIntAttr (line 62) | func (cp Payload) getIntAttr(field string) int {
type Config (line 42) | type Config struct
method GetStringField (line 73) | func (cc *Config) GetStringField(what, field string) string {
method GetIntField (line 86) | func (cc *Config) GetIntField(what, field string) int {
type AndroidVisibilityType (line 101) | type AndroidVisibilityType
constant AndroidVisibilityUnspecified (line 105) | AndroidVisibilityUnspecified AndroidVisibilityType = "VISIBILITY_UNSPECI...
constant AndroidVisibilityPrivate (line 109) | AndroidVisibilityPrivate AndroidVisibilityType = "PRIVATE"
constant AndroidVisibilityPublic (line 112) | AndroidVisibilityPublic AndroidVisibilityType = "PUBLIC"
constant AndroidVisibilitySecret (line 115) | AndroidVisibilitySecret AndroidVisibilityType = "SECRET"
type AndroidNotificationPriorityType (line 120) | type AndroidNotificationPriorityType
constant AndroidNotificationPriorityUnspecified (line 124) | AndroidNotificationPriorityUnspecified AndroidNotificationPriorityType =...
constant AndroidNotificationPriorityMin (line 128) | AndroidNotificationPriorityMin AndroidNotificationPriorityType = "PRIORI...
constant AndroidNotificationPriorityLow (line 132) | AndroidNotificationPriorityLow AndroidNotificationPriorityType = "PRIORI...
constant AndroidNotificationPriorityDefault (line 136) | AndroidNotificationPriorityDefault AndroidNotificationPriorityType = "PR...
constant AndroidNotificationPriorityHigh (line 141) | AndroidNotificationPriorityHigh AndroidNotificationPriorityType = "PRIOR...
constant AndroidNotificationPriorityMax (line 145) | AndroidNotificationPriorityMax AndroidNotificationPriorityType = "PRIORI...
type AndroidPriorityType (line 150) | type AndroidPriorityType
constant AndroidPriorityNormal (line 157) | AndroidPriorityNormal AndroidPriorityType = "NORMAL"
constant AndroidPriorityHigh (line 166) | AndroidPriorityHigh AndroidPriorityType = "HIGH"
type InterruptionLevelType (line 170) | type InterruptionLevelType
constant InterruptionLevelPassive (line 174) | InterruptionLevelPassive InterruptionLevelType = "passive"
constant InterruptionLevelActive (line 177) | InterruptionLevelActive InterruptionLevelType = "active"
constant InterruptionLevelTimeSensitive (line 180) | InterruptionLevelTimeSensitive InterruptionLevelType = "time-sensitive"
constant InterruptionLevelCritical (line 185) | InterruptionLevelCritical InterruptionLevelType = "critical"
constant HeaderApnsID (line 194) | HeaderApnsID = "apns-id"
constant HeaderApnsExpiration (line 202) | HeaderApnsExpiration = "apns-expiration"
constant HeaderApnsPriority (line 212) | HeaderApnsPriority = "apns-priority"
constant HeaderApnsTopic (line 221) | HeaderApnsTopic = "apns-topic"
constant HeaderApnsCollapseID (line 226) | HeaderApnsCollapseID = "apns-collapse-id"
constant HeaderApnsPushType (line 231) | HeaderApnsPushType = "apns-push-type"
type ApnsPushTypeType (line 234) | type ApnsPushTypeType
constant ApnsPushTypeAlert (line 241) | ApnsPushTypeAlert ApnsPushTypeType = "alert"
constant ApnsPushTypeBackground (line 246) | ApnsPushTypeBackground ApnsPushTypeType = "background"
constant ApnsPushTypeLocation (line 252) | ApnsPushTypeLocation ApnsPushTypeType = "location"
constant ApnsPushTypeVoip (line 259) | ApnsPushTypeVoip ApnsPushTypeType = "voip"
constant ApnsPushTypeFileprovider (line 264) | ApnsPushTypeFileprovider ApnsPushTypeType = "fileprovider"
type Aps (line 269) | type Aps struct
type ApsAlert (line 283) | type ApsAlert struct
constant ErrorUnspecified (line 301) | ErrorUnspecified = "UNSPECIFIED_ERROR"
constant ErrorInvalidArgument (line 319) | ErrorInvalidArgument = "INVALID_ARGUMENT"
constant ErrorUnregistered (line 333) | ErrorUnregistered = "UNREGISTERED"
constant ErrorSend
Condensed preview — 230 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,796K chars).
[
{
"path": ".gitattributes",
"chars": 40,
"preview": "model_pb2.py binary\nmodel.pb.go binary\n\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 1698,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve Tinode\ntitle: ''\nlabels: 'bug'\nassignees: ''\n\n---\n\n**If y"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 28,
"preview": "blank_issues_enabled: false\n"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 721,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: 'feature request'\nassignees: ''\n\n---"
},
{
"path": "CONTRIBUTING.md",
"chars": 1750,
"preview": "# Contributing\n\nWe're happy you want to contribute! You can help us in different ways:\n\n- [Open an issue](https://github"
},
{
"path": "INSTALL.md",
"chars": 12302,
"preview": "# Installing Tinode\n\nThe config file [`tinode.conf`](./server/tinode.conf) contains extensive instructions on configurin"
},
{
"path": "LICENSE",
"chars": 35147,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "README.md",
"chars": 13922,
"preview": "# Tinode Instant Messaging Server\n\n<img src=\"docs/logo.svg\" align=\"left\" width=128 height=128> Instant messaging full st"
},
{
"path": "README_ko.md",
"chars": 8113,
"preview": "# Tinode 인스턴트 메시징 서버\n\n## This document is outdated. For up to date info use [README.md](./README.md)\n\n\n<img src=\"docs/lo"
},
{
"path": "SECURITY.md",
"chars": 664,
"preview": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease report a vulnerability to `security@tinode.co`.\n\n## Do NOT to re"
},
{
"path": "build-all.sh",
"chars": 5763,
"preview": "#!/bin/bash\n\n# This script builds and archives binaries and supporting files for mac, linux, and windows.\n# If directory"
},
{
"path": "build-py-grpc.sh",
"chars": 329,
"preview": "#!/bin/bash\n\necho \"Packaging python tinode-grpc...\"\n\npushd ./pbx > /dev/null\n\n# Generate grpc bindings from the proto fi"
},
{
"path": "chatbot/LICENSE",
"chars": 115,
"preview": "The code in this folder and nested folders is licensed under Apache 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0\n"
},
{
"path": "chatbot/README.md",
"chars": 199,
"preview": "# Tinode ChatBot Examples\n\n* [Python chatbot](python/)\n* [Karuha](https://github.com/Visecy/Karuha) - third party chatbo"
},
{
"path": "chatbot/csharp/README.md",
"chars": 109,
"preview": "# Tinode Chatbot Example for .Net or .NetCore\n\nMoved to a separate repo: https://github.com/tinode/csharpbot\n"
},
{
"path": "chatbot/python/.gitignore",
"chars": 10,
"preview": ".tn-cookie"
},
{
"path": "chatbot/python/README.md",
"chars": 5621,
"preview": "# Tinode Chatbot\n\nThis is a simple chatbot for Tinode using [gRPC API](../../pbx/). It's written in Python as a demonstr"
},
{
"path": "chatbot/python/basic-cookie.sample",
"chars": 48,
"preview": "{\"schema\": \"basic\", \"secret\": \"alice:alice123\"}\n"
},
{
"path": "chatbot/python/chatbot.py",
"chars": 13641,
"preview": "\"\"\"Python implementation of a Tinode chatbot.\"\"\"\n\n# For compatibility between python 2 and 3\nfrom __future__ import prin"
},
{
"path": "chatbot/python/quotes.txt",
"chars": 19339,
"preview": "\u0007login: \u001b\n$3,000,000\n1 bulls, 3 cows\nA Cray is the best machine for simulating the performance of a Cray.\nA consistent i"
},
{
"path": "chatbot/python/requirements.txt",
"chars": 118,
"preview": "futures>=3.2.0; python_version<'3'\ngrpcio>=1.40.0\ntinode-grpc>=0.20.0b3\nimportlib-metadata>=1.0; python_version<'3.8'\n"
},
{
"path": "chatbot/python/setup.py",
"chars": 929,
"preview": "import setuptools\nfrom subprocess import Popen, PIPE\n\nwith open('README.md', 'r') as fh:\n long_description = fh.read("
},
{
"path": "chatbot/python/token-cookie.sample",
"chars": 133,
"preview": "{\"schema\": \"token\", \"secret\": \"mtXWlt9ERZCKsw9aFAABAFGGCnxinE8ruLE21t6SQfck4uBKCIy44kerjmOh4h1+\", \"expires\": \"2017-11-18"
},
{
"path": "docker/README.md",
"chars": 17478,
"preview": "# Using Docker to run Tinode\n\nAll images are available at https://hub.docker.com/r/tinode/\n\n1. [Install Docker](https://"
},
{
"path": "docker/chatbot/Dockerfile",
"chars": 1203,
"preview": "# Dockerfile builds an image with a chatbot (Tino) for Tinode.\n\nFROM python:3.13-slim\n\nARG VERSION=0.25\nARG LOGIN_AS=\nAR"
},
{
"path": "docker/docker-compose/README.md",
"chars": 2913,
"preview": "# Docker compose for end-to-end setup.\n\nThese reference docker-compose files will run Tinode with the MySql backend eith"
},
{
"path": "docker/docker-compose/cluster.mongodb.yml",
"chars": 1034,
"preview": "version: '3.8'\n\nx-mongodb-tinode-env-vars: &mongodb-tinode-env-vars\n \"STORE_USE_ADAPTER\": \"mongodb\"\n\nservices:\n db:\n "
},
{
"path": "docker/docker-compose/cluster.postgres.yml",
"chars": 467,
"preview": "version: '3.8'\n\nx-postgres-tinode-env-vars: &postgres-tinode-env-vars\n \"STORE_USE_ADAPTER\": \"postgres\"\n\nservices:\n db:"
},
{
"path": "docker/docker-compose/cluster.rethinkdb.yml",
"chars": 501,
"preview": "version: '3.8'\n\nx-rethinkdb-tinode-env-vars: &rethinkdb-tinode-env-vars\n \"STORE_USE_ADAPTER\": \"rethinkdb\"\n\nservices:\n "
},
{
"path": "docker/docker-compose/cluster.yml",
"chars": 5288,
"preview": "# Reference configuration for a simple 3-node Tinode cluster.\n# Includes:\n# * Mysql database\n# * 3 Tinode servers\n# * 3 "
},
{
"path": "docker/docker-compose/single-instance.mongodb.yml",
"chars": 902,
"preview": "version: '3.8'\n\nx-mongodb-tinode-env-vars: &mongodb-tinode-env-vars\n \"STORE_USE_ADAPTER\": \"mongodb\"\n\nservices:\n db:\n "
},
{
"path": "docker/docker-compose/single-instance.postgres.yml",
"chars": 333,
"preview": "version: '3.8'\n\nx-postgres-tinode-env-vars: &postgres-tinode-env-vars\n \"STORE_USE_ADAPTER\": \"postgres\"\n\nservices:\n db:"
},
{
"path": "docker/docker-compose/single-instance.rethinkdb.yml",
"chars": 365,
"preview": "version: '3.8'\n\nx-rethinkdb-tinode-env-vars: &rethinkdb-tinode-env-vars\n \"STORE_USE_ADAPTER\": \"rethinkdb\"\n\nservices:\n "
},
{
"path": "docker/docker-compose/single-instance.yml",
"chars": 3243,
"preview": "# Reference configuration for a simple Tinode server.\n# Includes:\n# * Mysql database\n# * Tinode server\n# * Tinode export"
},
{
"path": "docker/exporter/Dockerfile",
"chars": 778,
"preview": "FROM alpine:3.14\n\nARG VERSION=0.16.4\nENV VERSION=$VERSION\n\nLABEL maintainer=\"Tinode Team <info@tinode.co>\"\nLABEL name=\"T"
},
{
"path": "docker/exporter/entrypoint.sh",
"chars": 2241,
"preview": "#!/bin/bash\n\n# Check if environment variables (provided as argument list) are set.\nfunction check_vars() {\n local varna"
},
{
"path": "docker/tinode/Dockerfile",
"chars": 5107,
"preview": "# Docker file builds an image with a tinode chat server.\n#\n# In order to run the image you have to link it to a running "
},
{
"path": "docker/tinode/config.template",
"chars": 4599,
"preview": "{\n\t\"listen\": \":6060\",\n\t\"api_path\": \"/\",\n\t\"cache_control\": 39600,\n\t\"static_mount\": \"/\",\n\t\"grpc_listen\": \":16060\",\n\t\"grpc_"
},
{
"path": "docker/tinode/entrypoint.sh",
"chars": 3773,
"preview": "#!/bin/bash\n\n# If EXT_CONFIG is set, use it as a config file.\nif [ ! -z \"$EXT_CONFIG\" ] ; then\n\tCONFIG=\"$EXT_CONFIG\"\n\n\t#"
},
{
"path": "docker-build.sh",
"chars": 2531,
"preview": "#!/bin/bash\n\n# Build Tinode docker linux/amd64 images.\n# You may have to install buildx https://docs.docker.com/buildx/w"
},
{
"path": "docker-release.sh",
"chars": 1557,
"preview": "#!/bin/bash\n\n# Publish Tinode docker images to hub.docker.com\n\nfunction containerName() {\n if [ \"$1\" == \"alldbs\" ]; the"
},
{
"path": "docs/API.md",
"chars": 92469,
"preview": "<!-- TOC depthfrom:1 depthto:6 withlinks:true updateonsave:true orderedlist:false -->\n\n- [Server API](#server-api)\n - ["
},
{
"path": "docs/CLA.md",
"chars": 5786,
"preview": "# Tinode Individual Contributor License Agreement\n\nIn order to clarify the intellectual property license granted with Co"
},
{
"path": "docs/call-establishment.md",
"chars": 5099,
"preview": "# Video Call Establishment Flow\n\nTinode supports peer to peer video calls over [WebRTC](https://webrtc.org/). The diagra"
},
{
"path": "docs/drafty.md",
"chars": 15764,
"preview": "# Drafty: Rich Message Format\n\nDrafty is a text format used by Tinode to style messages. The intent of Drafty is to be e"
},
{
"path": "docs/faq.md",
"chars": 9125,
"preview": "# Frequently Asked Questions\n\n### Q: Where can I find server logs when running in Docker?<br/>\n**A**: The log is in the "
},
{
"path": "docs/monitoring.md",
"chars": 1242,
"preview": "# Monitoring Tinode server\n\nTinode server can optionally expose runtime statistics as a json document at a configurable "
},
{
"path": "docs/thecard.md",
"chars": 2940,
"preview": "# theCard: Person/Topic Description Format\n\nTinode uses `theCard` to store and transmit descriptions of people and topic"
},
{
"path": "docs/translations.md",
"chars": 4634,
"preview": "# Localizing Tinode\n\n**IMPORTANT!** Please use `devel` branches for translations.\n\n## Server\n\nThe server sends emails or"
},
{
"path": "go.mod",
"chars": 5044,
"preview": "module github.com/tinode/chat\n\ngo 1.24.0\n\nrequire (\n\tfirebase.google.com/go v3.13.0+incompatible\n\tgithub.com/aws/aws-sdk"
},
{
"path": "go.sum",
"chars": 44912,
"preview": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX"
},
{
"path": "keygen/README.md",
"chars": 1657,
"preview": "# keygen: API key generator\n\nA command-line utility to generate an API key for [Tinode server](../server/)\n\n**Parameters"
},
{
"path": "keygen/keygen.go",
"chars": 4568,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/hmac\"\n\t\"crypto/md5\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"flag"
},
{
"path": "loadtest/LICENSE",
"chars": 92,
"preview": "Code in this folder is licensed under Apache 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0\n"
},
{
"path": "loadtest/README.md",
"chars": 3420,
"preview": "# Tinode Load Testing\n\nContent of this directory is for running rudimentary load tests of Tinode server. You need this o"
},
{
"path": "loadtest/loadtest.scala",
"chars": 4157,
"preview": "package tinode\n\nimport java.util.Base64\nimport java.util.concurrent.ConcurrentHashMap\n\nimport scala.collection.JavaConve"
},
{
"path": "loadtest/tinode.erl",
"chars": 1397,
"preview": "%% Support module for Tinode load testing with Tsung.\n%% Compile using erlc then copy resulting .beam to\n%% /usr/local/l"
},
{
"path": "loadtest/tinode.scala",
"chars": 4641,
"preview": "package tinode\n\nimport java.util.Base64\nimport java.util.concurrent.ConcurrentHashMap\n\nimport scala.collection.JavaConve"
},
{
"path": "loadtest/tsung.xml",
"chars": 6041,
"preview": "<?xml version=\"1.0\"?>\n<!DOCTYPE tsung SYSTEM \"/usr/local/share/tsung/tsung-1.0.dtd\" []>\n<tsung loglevel=\"notice\" version"
},
{
"path": "loadtest/users.csv",
"chars": 98,
"preview": "username,password\nalice,alice123\nbob,bob123\ncarol,carol123\ndave,dave123\neve,eve123\nfrank,frank123\n"
},
{
"path": "monitoring/LICENSE",
"chars": 111,
"preview": "Code in this folder and nested folders is licensed under Apache 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0\n"
},
{
"path": "monitoring/README.md",
"chars": 266,
"preview": "# Monitoring Support\n\nThis directory contains code related to monitoring Tinode server. Supported monitoring services ar"
},
{
"path": "monitoring/exporter/README.md",
"chars": 3575,
"preview": "# Tinode Metric Exporter\n\nThis is a simple service which reads JSON monitoring data exposed by Tinode server using [expv"
},
{
"path": "monitoring/exporter/build.sh",
"chars": 1726,
"preview": "#!/bin/bash\n\n# This scripts build and archives binaries and supporting files.\n\n# Supported OSs: mac (darwin), windows, l"
},
{
"path": "monitoring/exporter/influxdb_exporter.go",
"chars": 3151,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\n// InfluxDBExporter col"
},
{
"path": "monitoring/exporter/main.go",
"chars": 6905,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prome"
},
{
"path": "monitoring/exporter/prom_exporter.go",
"chars": 10531,
"preview": "package main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\n// PromExporter collects met"
},
{
"path": "monitoring/exporter/scraper.go",
"chars": 4230,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// Scraper collects metrics from a ti"
},
{
"path": "pbx/README.md",
"chars": 912,
"preview": "# Protocol Buffer and gRPC definitions\n\nDefinitions for Tinode [gRPC](https://grpc.io/) client and plugins.\n\nTinode gRPC"
},
{
"path": "pbx/go-generate.sh",
"chars": 135,
"preview": "#!/bin/bash\nprotoc --go_out=../pbx --go_opt=paths=source_relative --go-grpc_out=../pbx --go-grpc_opt=paths=source_relati"
},
{
"path": "pbx/model.pb.go",
"chars": 185186,
"preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.27.1\n// \tprotoc v3.21.12\n// so"
},
{
"path": "pbx/model.proto",
"chars": 14296,
"preview": "syntax = \"proto3\";\npackage pbx;\noption go_package = \"github.com/tinode/chat/pbx\";\n\n// This is the methods that needs to "
},
{
"path": "pbx/model_grpc.pb.go",
"chars": 18407,
"preview": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.2.0\n// - protoc "
},
{
"path": "pbx/py-generate.sh",
"chars": 542,
"preview": "#!/bin/bash\n\n# Generate python gRPC bindings for Tinode. A command line parameter v=XX will use specified python version"
},
{
"path": "pbx/py_fix.py",
"chars": 392,
"preview": "# grpc-tools generates python 2 file which does not work with\n# python3 packaging system. This is a fix.\n\nmodel_pb2_grpc"
},
{
"path": "py_grpc/.gitignore",
"chars": 126,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n/build/\n/dist/\n/tinode_grpc/GIT_VERSION\n*.egg"
},
{
"path": "py_grpc/LICENSE",
"chars": 96,
"preview": "The code in this folder is licensed under Apache 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0\n"
},
{
"path": "py_grpc/README.md",
"chars": 1209,
"preview": "# Generated Protocol Buffer and gRPC files for [Tinode](https://github.com/tinode)\n\nGenerated Python code for [gRPC](htt"
},
{
"path": "py_grpc/pyproject.toml",
"chars": 1430,
"preview": "[build-system]\nrequires = [\"setuptools>=45\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"tinode_"
},
{
"path": "py_grpc/tinode_grpc/__init__.py",
"chars": 66,
"preview": "from . import model_pb2 as pb\nfrom . import model_pb2_grpc as pbx\n"
},
{
"path": "py_grpc/tinode_grpc/model_pb2.py",
"chars": 22217,
"preview": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler. DO NOT EDIT!\n# source: model.proto\n\"\"\"Generated pr"
},
{
"path": "py_grpc/tinode_grpc/model_pb2.pyi",
"chars": 35743,
"preview": "from google.protobuf.internal import containers as _containers\nfrom google.protobuf.internal import enum_type_wrapper as"
},
{
"path": "py_grpc/tinode_grpc/model_pb2_grpc.py",
"chars": 14973,
"preview": "# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!\n\"\"\"Client and server classes corresponding to prot"
},
{
"path": "py_grpc/version.py",
"chars": 722,
"preview": "# Convert git tag like \"v0.15.5-rc5-3-g2084bd63\" to PEP 440 version like \"0.15.5rc5.post3\"\n\nfrom subprocess import check"
},
{
"path": "rest-auth/README.md",
"chars": 415,
"preview": "# Example of a REST authenticator server.\n\nThis is an example of a server-side [REST authenticator](../server/auth/rest/"
},
{
"path": "rest-auth/auth.py",
"chars": 4064,
"preview": "#!/usr/bin/python\n\n# Sample Tinode REST/JSON-RPC authentication service.\n# See https://github.com/tinode/chat/rest-auth "
},
{
"path": "rest-auth/dummy_data.json",
"chars": 7478,
"preview": "{\n \"alice\": {\n \"anon\": \"N\",\n \"auth\": \"JRWPA\",\n \"authlvl\": \"auth\",\n \"features\": \"V\",\n \"password\": \"alice1"
},
{
"path": "rest-auth/requirements.txt",
"chars": 13,
"preview": "flask>=1.1.0\n"
},
{
"path": "server/.golangci.yml",
"chars": 800,
"preview": "linters-settings:\n govet:\n check-shadowing: true\n disable:\n - composites\n golint:\n min-confidence: 0\n g"
},
{
"path": "server/api_key.go",
"chars": 1992,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Authenticati"
},
{
"path": "server/auth/anon/auth_anon.go",
"chars": 2848,
"preview": "// Package anon provides authentication without credentials. Most useful for customer support.\n// Anonymous authenticati"
},
{
"path": "server/auth/auth.go",
"chars": 7954,
"preview": "// Package auth provides interfaces and types required for implementing an authenticaor.\npackage auth\n\nimport (\n\t\"encodi"
},
{
"path": "server/auth/basic/auth_basic.go",
"chars": 8438,
"preview": "// Package basic is an authenticator by login-password.\npackage basic\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"regexp\"\n\t\"s"
},
{
"path": "server/auth/code/auth_code.go",
"chars": 6123,
"preview": "// Package code implements temporary no-login authentication by short numeric code.\npackage code\n\nimport (\n\t\"crypto/rand"
},
{
"path": "server/auth/mock_auth/mock_auth.go",
"chars": 7600,
"preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: auth/auth.go\n\n// Package mock_auth is a generated GoMock package.\n"
},
{
"path": "server/auth/rest/README.md",
"chars": 11309,
"preview": "# REST or JSON-RPC authenticator\n\nThis authenticator permits authentication of Tinode users and creation of Tinode accou"
},
{
"path": "server/auth/rest/auth_rest.go",
"chars": 9237,
"preview": "// Package rest provides authentication by calling a separate process over REST API (technically JSON RPC, not REST).\npa"
},
{
"path": "server/auth/token/auth_token.go",
"chars": 5817,
"preview": "// Package token implements authentication by HMAC-signed security token.\npackage token\n\nimport (\n\t\"bytes\"\n\t\"crypto/hmac"
},
{
"path": "server/calls.go",
"chars": 14164,
"preview": "/******************************************************************************\n *\n * Description :\n * Video call ha"
},
{
"path": "server/cluster.go",
"chars": 34451,
"preview": "package main\n\nimport (\n\t\"encoding/gob\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net\"\n\t\"net/rpc\"\n\t\"sort\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"tim"
},
{
"path": "server/cluster_leader.go",
"chars": 10121,
"preview": "package main\n\nimport (\n\t\"math/rand\"\n\t\"net/rpc\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/tinode/chat/server/logs\"\n)\n\n// Cluster meth"
},
{
"path": "server/concurrency/goroutinepool.go",
"chars": 1422,
"preview": "// Package concurrency is a very simple implementation of a mutex with channels.\n// Provides TryLock functionality absen"
},
{
"path": "server/concurrency/simplemutex.go",
"chars": 620,
"preview": "package concurrency\n\n// SimpleMutex is a channel used for locking.\ntype SimpleMutex chan struct{}\n\n// NewSimpleMutex cre"
},
{
"path": "server/datamodel.go",
"chars": 54079,
"preview": "package main\n\n/******************************************************************************\n *\n * Description :\n *\n *"
},
{
"path": "server/db/adapter.go",
"chars": 10496,
"preview": "// Package adapter contains the interfaces to be implemented by the database adapter\npackage adapter\n\nimport (\n\t\"encodin"
},
{
"path": "server/db/common/common.go",
"chars": 5024,
"preview": "// Package common contains utility methods used by all adapters.\npackage common\n\nimport (\n\t\"encoding/json\"\n\t\"sort\"\n\t\"str"
},
{
"path": "server/db/common/common_test.go",
"chars": 12870,
"preview": "package common\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/tinode/chat/server/stor"
},
{
"path": "server/db/common/test_data/test_data.go",
"chars": 9042,
"preview": "package test_data\n\nimport (\n\t\"time\"\n\n\t\"github.com/tinode/chat/server/auth\"\n\t\"github.com/tinode/chat/server/db/common\"\n\t\""
},
{
"path": "server/db/mongodb/adapter.go",
"chars": 88602,
"preview": "//go:build mongodb\n\n// Package mongodb is a database adapter for MongoDB.\npackage mongodb\n\nimport (\n\t\"context\"\n\t\"crypto/"
},
{
"path": "server/db/mongodb/blank.go",
"chars": 207,
"preview": "//go:build !mongodb\n// +build !mongodb\n\n// This file is needed for conditional compilation. It's used when\n// the build "
},
{
"path": "server/db/mongodb/schema.md",
"chars": 8600,
"preview": "# MongoDB Database Schema\n\n## Database `tinode`\n\n### Table `users`\nStores user accounts\n\nFields:\n* `_id` user id, primar"
},
{
"path": "server/db/mongodb/tests/mongo_test.go",
"chars": 35497,
"preview": "// To test another db backend:\n// 1) Create GetAdapter function inside your db backend adapter package (like one inside "
},
{
"path": "server/db/mongodb/tests/test.conf",
"chars": 243,
"preview": "{\n \"reset_db_data\": true,\n \"adapters\": {\n \"mongodb\": {\n \"database\": \"tinode_test\",\n //\"replica_set\": \"rs0"
},
{
"path": "server/db/mysql/adapter.go",
"chars": 97579,
"preview": "//go:build mysql\n// +build mysql\n\n// Package mysql is a database adapter for MySQL.\npackage mysql\n\nimport (\n\t\"context\"\n\t"
},
{
"path": "server/db/mysql/blank.go",
"chars": 200,
"preview": "//go:build !mysql\n// +build !mysql\n\n// This file is needed for conditional compilation. It's used when\n// the build tag "
},
{
"path": "server/db/mysql/schema.sql",
"chars": 6075,
"preview": "# THIS SCHEMA FILE IS FOR REFERENCE/DOCUMENTATION ONLY!\n# DO NOT USE IT TO INITIALIZE THE DATABASE.\n# Read installation "
},
{
"path": "server/db/mysql/tests/mysql_test.go",
"chars": 36304,
"preview": "// To test another db backend:\n// 1) Create GetAdapter function inside your db backend adapter package (like one inside "
},
{
"path": "server/db/mysql/tests/test.conf",
"chars": 229,
"preview": "{\n \"reset_db_data\": true,\n \"adapters\": {\n \"mysql\": {\n\t\t\t\t\"dsn\": \"root@tcp(localhost:3306)/tinode_test?parseTime=tru"
},
{
"path": "server/db/postgres/adapter.go",
"chars": 100059,
"preview": "//go:build postgres\n// +build postgres\n\n// Package postgres is a database adapter for PostgreSQL.\npackage postgres\n\nimpo"
},
{
"path": "server/db/postgres/blank.go",
"chars": 212,
"preview": "//go:build !postgres\n// +build !postgres\n\n// This file is needed for conditional compilation. It's used when\n// the buil"
},
{
"path": "server/db/postgres/schema.sql",
"chars": 114,
"preview": "# The MySQL and PostrgreSQL schemas are identical save for differences in SQL flavors.\n# SEE ../mysql/schema.sql.\n"
},
{
"path": "server/db/postgres/tests/postgres_test.go",
"chars": 37978,
"preview": "// To test another db backend:\n// 1) Create GetAdapter function inside your db backend adapter package (like one inside "
},
{
"path": "server/db/postgres/tests/test.conf",
"chars": 196,
"preview": "{\n \"reset_db_data\": true,\n \"adapters\": {\n \"postgres\": {\n\t\t\t\t\"User\": \"postgres\",\n\t\t\t\t\"Passwd\": \"postgres\",\n\t\t\t\t\"Host"
},
{
"path": "server/db/rethinkdb/adapter.go",
"chars": 89713,
"preview": "//go:build rethinkdb\n\n// Package rethinkdb is a database adapter for RethinkDB.\npackage rethinkdb\n\nimport (\n\t\"encoding/j"
},
{
"path": "server/db/rethinkdb/blank.go",
"chars": 216,
"preview": "//go:build !rethinkdb\n// +build !rethinkdb\n\n// This file is needed for conditional compilation. It's used when\n// the bu"
},
{
"path": "server/db/rethinkdb/schema.md",
"chars": 9303,
"preview": "# RethinkDB Database Schema\n\n## Database `tinode`\n\n### Table `users`\nStores user accounts\n\nFields:\n* `Id` user id, prima"
},
{
"path": "server/db/rethinkdb/tests/rethink_test.go",
"chars": 40596,
"preview": "package tests\n\n// To test another db backend:\n// 1) Create GetAdapter function inside your db backend adapter package (l"
},
{
"path": "server/db/rethinkdb/tests/test.conf",
"chars": 255,
"preview": "{\n \"reset_db_data\": true,\n \"adapters\": {\n \"rethinkdb\": {\n\t\t\t\t// Address(es) of RethinkDB node(s): either a string o"
},
{
"path": "server/drafty/drafty.go",
"chars": 13124,
"preview": "// Package drafty contains utilities for conversion from Drafty to plain text.\npackage drafty\n\nimport (\n\t\"encoding/json\""
},
{
"path": "server/drafty/drafty_test.go",
"chars": 6205,
"preview": "package drafty\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n)\n\nvar validInputs = []string{\n\t`\"This is a plain text string.\"`,\n\t"
},
{
"path": "server/drafty/grapheme.go",
"chars": 1885,
"preview": "package drafty\n\nimport (\n\t\"github.com/rivo/uniseg\"\n)\n\n// graphemes is a container holding lengths of grapheme clusters i"
},
{
"path": "server/hdl_files.go",
"chars": 18664,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Handler of"
},
{
"path": "server/hdl_grpc.go",
"chars": 4614,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Handler of"
},
{
"path": "server/hdl_longpoll.go",
"chars": 5675,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Handler of"
},
{
"path": "server/hdl_websock.go",
"chars": 5299,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Handler of"
},
{
"path": "server/http.go",
"chars": 12890,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Web server i"
},
{
"path": "server/http_pprof.go",
"chars": 1436,
"preview": "// Debug tooling. Dumps named profile in response to HTTP request at\n// \t\thttp(s)://<host-name>/<configured-path>/<profi"
},
{
"path": "server/hub.go",
"chars": 27534,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Main hub f"
},
{
"path": "server/init_topic.go",
"chars": 23205,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Topic init"
},
{
"path": "server/logs/logs.go",
"chars": 1225,
"preview": "// Package logs exposes info, warning and error loggers.\npackage logs\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"strings\"\n)\n\nvar (\n\t// Inf"
},
{
"path": "server/main.go",
"chars": 28453,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Setup & init"
},
{
"path": "server/media/fs/filesys.go",
"chars": 5984,
"preview": "// Package fs implements github.com/tinode/chat/server/media interface by storing media objects in a single\n// directory"
},
{
"path": "server/media/media.go",
"chars": 6076,
"preview": "// Package media defines an interface which must be implemented by media upload/download handlers.\npackage media\n\nimport"
},
{
"path": "server/media/media_test.go",
"chars": 5958,
"preview": "package media\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestMatchCORSOrigin(t *testing.T) {\n\tcases := []struct {\n\t\tallowed"
},
{
"path": "server/media/s3/s3.go",
"chars": 9668,
"preview": "// Package s3 implements media interface by storing media objects in Amazon S3 bucket.\npackage s3\n\nimport (\n\t\"encoding/j"
},
{
"path": "server/pbconverter.go",
"chars": 28204,
"preview": "// Converts between protobuf structs and Go representation of packets\n\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"time\"\n\n"
},
{
"path": "server/plugins.go",
"chars": 19086,
"preview": "// External services contacted through RPC\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"strings\"\n\t\"tim"
},
{
"path": "server/pres.go",
"chars": 20047,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/tinode/chat/server/logs\"\n\t\"github.com/tinode/chat/serve"
},
{
"path": "server/push/common/typedef.go",
"chars": 26752,
"preview": "package common\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/tinode/chat/server/push\"\n\t\"google.golang"
},
{
"path": "server/push/fcm/README.md",
"chars": 2347,
"preview": "# FCM push adapter\n\nThis adapter sends push notifications to mobile clients and web browsers using [Google FCM](https://"
},
{
"path": "server/push/fcm/payload.go",
"chars": 11605,
"preview": "package fcm\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"strconv\"\n\t\"time\"\n\n\tfcmv1 \"google.golang.org/api/fcm/v1\"\n\n\t\"github.com"
},
{
"path": "server/push/fcm/push_fcm.go",
"chars": 8054,
"preview": "// Package fcm implements push notification plugin for Google FCM backend.\n// Push notifications for Android, iOS and we"
},
{
"path": "server/push/push.go",
"chars": 5594,
"preview": "// Package push contains interfaces to be implemented by push notification plugins.\npackage push\n\nimport (\n\t\"encoding/js"
},
{
"path": "server/push/stdout/README.md",
"chars": 221,
"preview": "# `stdout` push adapter\n\nThis is an adapter which logs push notifications to `STDOUT` where they can be redirected to fi"
},
{
"path": "server/push/stdout/push_stdout.go",
"chars": 2186,
"preview": "// Package stdout is a sample implementation of a push plugin.\n// If enabled, it writes every notification to stdout.\npa"
},
{
"path": "server/push/tnpg/README.md",
"chars": 1695,
"preview": "# TNPG: Push Gateway\n\nThis is a push notifications adapter which communicates with Tinode Push Gateway (TNPG).\n\nTNPG is "
},
{
"path": "server/push/tnpg/push_tnpg.go",
"chars": 10669,
"preview": "// Package tnpg implements push notification plugin for Tinode Push Gateway.\npackage tnpg\n\nimport (\n\t\"bytes\"\n\t\"compress/"
},
{
"path": "server/push.go",
"chars": 7153,
"preview": "/******************************************************************************\n *\n * Description:\n * Push notificat"
},
{
"path": "server/ringhash/ringhash.go",
"chars": 2980,
"preview": "// Package ringhash implementats a consistent ring hash:\n// https://en.wikipedia.org/wiki/Consistent_hashing\npackage rin"
},
{
"path": "server/ringhash/ringhash_test.go",
"chars": 4215,
"preview": "package ringhash_test\n\nimport (\n\t\"fmt\"\n\t\"hash/crc32\"\n\t\"hash/fnv\"\n\t\"testing\"\n\n\t\"github.com/tinode/chat/server/ringhash\"\n)"
},
{
"path": "server/run-cluster.sh",
"chars": 2115,
"preview": "#!/bin/bash\n\n# Start/stop test cluster on localhost. This is NOT a production script. Use it for reference only.\n\n# Name"
},
{
"path": "server/sanity-test.sh",
"chars": 3173,
"preview": "#!/bin/bash\n\nBINARY_PATH=$GOPATH/bin\nTINODE_BINARY=$BINARY_PATH/server\n\n# Kills and removes any running containers.\nclea"
},
{
"path": "server/session.go",
"chars": 40627,
"preview": "/******************************************************************************\n *\n * Description :\n *\n * Handling of "
},
{
"path": "server/session_test.go",
"chars": 26491,
"preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/tinode/chat"
},
{
"path": "server/sessionstore.go",
"chars": 6873,
"preview": "/******************************************************************************\n *\n * Description:\n *\n * Session manag"
},
{
"path": "server/stats.go",
"chars": 3920,
"preview": "// Logic related to expvar handling: reporting live stats such as\n// session and topic counts, memory usage etc.\n// The "
},
{
"path": "server/store/mock_store/mock_store.go",
"chars": 54721,
"preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: store/store.go\n\n// Package mock_store is a generated GoMock packag"
},
{
"path": "server/store/store.go",
"chars": 37795,
"preview": "// Package store provides methods for registering and accessing database adapters.\npackage store\n\nimport (\n\t\"encoding/js"
},
{
"path": "server/store/types/types.go",
"chars": 38866,
"preview": "// Package types provides data types for persisting objects in the databases.\npackage types\n\nimport (\n\t\"database/sql/dri"
},
{
"path": "server/store/types/uidgen.go",
"chars": 2855,
"preview": "package types\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"errors\"\n\n\tsf \"github.com/tinode/snowflake\"\n\t\"golang.org/"
},
{
"path": "server/store/types/uidgen_test.go",
"chars": 16694,
"preview": "package types\n\nimport (\n\t\"encoding/base64\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestUidGeneratorInit(t *testing.T) {\n\tug := &UidGe"
},
{
"path": "server/templ/email-password-reset-en.templ",
"chars": 1852,
"preview": "{{/*\n ENGLISH\n\n This template defines contents of the password reset email.\n\n See explanation in ./email-validation-e"
},
{
"path": "server/templ/email-password-reset-es.templ",
"chars": 1892,
"preview": "{{/*\n SPANISH\n\n This template defines contents of the password reset email in spanish.\n\n See explanation in ./email-v"
},
{
"path": "server/templ/email-password-reset-fr.templ",
"chars": 2066,
"preview": "{{/*\n FRENCH\n\n This template defines contents of the password reset email.\n\n See explanation in ./email-validation-en"
},
{
"path": "server/templ/email-password-reset-pt.templ",
"chars": 1803,
"preview": "{{/*\n PORTUGUESE\n\n This template defines contents of the password reset e-mail in portuguese.\n\n See explanation in ./"
},
{
"path": "server/templ/email-password-reset-ru.templ",
"chars": 1906,
"preview": "{{/*\n RUSSIAN\n\n Password reset email.\n\n See explanation in ./email-validation-en.templ\n*/}}\n\n\n{{define \"subject\" -}}\n"
},
{
"path": "server/templ/email-password-reset-uk.templ",
"chars": 1872,
"preview": "{{/*\n UKRAINIAN\n\n Password reset email.\n\n See explanation in ./email-validation-en.templ\n*/}}\n\n\n{{define \"subject\" -}"
},
{
"path": "server/templ/email-password-reset-vi.templ",
"chars": 1833,
"preview": "{{/*\n VIETNAMESE\n\n This template defines contents of the password reset email.\n\n See explanation in ./email-validatio"
},
{
"path": "server/templ/email-password-reset-zh-TW.templ",
"chars": 1193,
"preview": "{{/*\n 繁體中文\n\n 此模板定義密碼重設電子郵件的內容。\n\n 說明請參見 ./email-validation-en.templ\n*/}}\n\n\n{{define \"subject\" -}}\n重設 Tinode 密碼\n{{- end"
},
{
"path": "server/templ/email-password-reset-zh.templ",
"chars": 1135,
"preview": "{{/*\n CHINESE\n\n 定义重置密码文案的模版。\n\n 参阅 ./email-validation-zh.templ\n*/}}\n\n\n{{define \"subject\" -}}\n重置 Tinode 密码\n{{- end}}\n\n{"
},
{
"path": "server/templ/email-validation-en.templ",
"chars": 1799,
"preview": "{{/*\n ENGLISH\n\n This template defines content of the email sent to users as a request to confirm registration email ad"
},
{
"path": "server/templ/email-validation-es.templ",
"chars": 1255,
"preview": "{{/*\n SPANISH\n\n See explanation in ./email-validation-en.templ\n*/}}\n\n{{define \"subject\" -}}\nRegistro Tinode: Correo de"
},
{
"path": "server/templ/email-validation-fr.templ",
"chars": 1391,
"preview": "{{/*\n FRENCH\n\n See explanation in ./email-validation-en.templ\n*/}}\n\n{{define \"subject\" -}}\nTinode enregistrement : con"
},
{
"path": "server/templ/email-validation-pt.templ",
"chars": 1247,
"preview": "{{/*\n PORTUGUESE\n\n See explanation in ./email-validation-en.templ\n*/}}\n\n{{define \"subject\" -}}\nRegistro Tinode: E-mail"
},
{
"path": "server/templ/email-validation-ru.templ",
"chars": 1376,
"preview": "{{/*\n RUSSIAN\n\n See explanation in ./email-validation-en.templ\n*/}}\n\n{{define \"subject\" -}}\nРегистрация Tinode: подтве"
},
{
"path": "server/templ/email-validation-uk.templ",
"chars": 1364,
"preview": "{{/*\n UKRAINIAN\n\n See explanation in ./email-validation-en.templ\n*/}}\n\n{{define \"subject\" -}}\nРеєстрація Tinode: підтв"
},
{
"path": "server/templ/email-validation-vi.templ",
"chars": 1288,
"preview": "{{/*\n VIETNAMESE\n\n See explanation in ./email-validation-en.templ\n*/}}\n\n{{define \"subject\" -}}\nXác thực đăng ký tài kh"
},
{
"path": "server/templ/email-validation-zh-TW.templ",
"chars": 1209,
"preview": "{{/*\n CHINESE Traditional (Taiwan)\n 繁體中文\n\n 此模板定義發送給用戶的電子郵件內容,用於請求確認註冊電子郵件地址。\n 語法請參見 https://golang.org/pkg/text/temp"
},
{
"path": "server/templ/email-validation-zh.templ",
"chars": 1131,
"preview": "{{/*\n CHINESE\n\n 定义用户注册邮件确认文案的模版。\n 语法参阅 https://golang.org/pkg/text/template/ 。\n\n 模版必须包含以下内容:\n - 'subject':邮件主题\n "
},
{
"path": "server/templ/sms-universal-en.templ",
"chars": 133,
"preview": "{{/*\n ENGLISH\n\n Universal confirmation and password reset template for SMS.\n*/}}\n\nTinode confirmation code: {{.Code}}\n"
},
{
"path": "server/templ/sms-universal-es.templ",
"chars": 141,
"preview": "{{/*\n SPANISH\n\n Universal confirmation and password reset template for SMS.\n*/}}\n\nCódigo de confirmación de Tinode: {{"
},
{
"path": "server/templ/sms-universal-fr.templ",
"chars": 160,
"preview": "{{/*\n FRENCH\n\n Modèle universel de confirmation et de réinitialisation du mot de passe pour SMS.\n*/}}\n\nCode de confi"
},
{
"path": "server/templ/sms-universal-pt.templ",
"chars": 146,
"preview": "{{/*\n PORTUGESE\n\n Modelo universal de confirmação e redefinição de senha para SMS.\n*/}}\n\nCódigo de confirmação Tinod"
},
{
"path": "server/templ/sms-universal-ru.templ",
"chars": 135,
"preview": "{{/*\n RUSSIAN\n\n Универсальный шаблон подтверждения и сброса пароля для СМС.\n*/}}\n\nКод подтверждения Tinode: {{.Code}"
},
{
"path": "server/templ/sms-universal-uk.templ",
"chars": 140,
"preview": "{{/*\n UKRAINIAN\n\n Універсальний шаблон підтвердження та скидання пароля для СМС.\n*/}}\n\nКод підтвердження Tinode: {{."
},
{
"path": "server/templ/sms-universal-vi.templ",
"chars": 133,
"preview": "{{/*\n VIETNAMESE\n\n Universal confirmation and password reset template for SMS.\n*/}}\n\nMã xác thực từ Tinode: {{.Code}}\n"
},
{
"path": "server/templ/sms-universal-zh-TW.templ",
"chars": 72,
"preview": "{{/*\n 繁體中文\n\n 通用確認和密碼重設簡訊模板。\n*/}}\n\nTinode 確認驗證碼:{{.Code}}\n{{.HostUrl}}\n"
},
{
"path": "server/templ/sms-universal-zh.templ",
"chars": 120,
"preview": "{{/*\n CHINESE\n\n Universal confirmation and password reset template for SMS.\n*/}}\n\n【Tinode】验证码: {{.Code}}\n{{.HostUrl}}\n"
},
{
"path": "server/tinode.conf",
"chars": 27919,
"preview": "// The JSON comments are somewhat brittle. Don't try anything too fancy.\n{\n\t// HTTP(S) address to listen on for websocke"
},
{
"path": "server/topic.go",
"chars": 124027,
"preview": "/******************************************************************************\n *\n * Description :\n * An isolated c"
}
]
// ... and 30 more files (download for full content)
About this extraction
This page contains the full source code of the tinode/chat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 230 files (2.5 MB), approximately 654.7k tokens, and a symbol index with 3531 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.