# ☀️ Twake

Twake is an open source collaborative workspace. It can be self-hosted, used is saas and easily integrated into your teams. Twake offers all the features for collaboration :  team chat, file storage, team calendar,  tasks manager.&#x20;

This **documentation** assists developers and users in having the best Twake experience as possible. We’ve developed Twake to have a better teamwork.


# Use Twake on twake.app

Ready to use Twake with your team?

#### Start using Twake now

To use Twake online it is simple, just go on [twake.app](https://twake.app) and click "Sign up".

{% hint style="info" %}
Want to install it on your own server ? [It is here](/gettingstarted/installation).
{% endhint %}

#### Now, you can learn how to become a Twake super-user

{% content-ref url="/pages/-Lp7wdE3REv5oYVimdcT" %}
[Welcome to Twake !](/how-to-use-it/welcome)
{% endcontent-ref %}

{% hint style="info" %}
If you want to see the latest updates you can go here: [https://github.com/linagora/Twake/blob/main/changelog.md](https://github.com/linagora/Twake/blob/canary/changelog.md)
{% endhint %}

## Use the canary version to try out new features

You want to try the latest features from Twake before they come out to production? You can!

We have a server called Canary that will give you access to new features 1 month before their official releases. This server is compatible with the classic server so it means you can still discuss with your team even if you are the only one using the canary server.

The goal of the canary server it to do additional testing with a larger set of users, so if you encounter any issue, please notify us to help us fix the version before to ship it on production.

When using the canary server, the best is to set it up on all your devices like so:

#### How to use it on my browser

Just go to [canary.twake.app](https://canary.twake.app) instead of web.twake.app.

#### How to use it on the desktop app

Press Shift+Alt+Command or Shift+Ctrl+Alt to open the "Change server url" popup then enters <https://canary.twake.app> in the input then press OK.

#### How to use it on the mobile app

Logout from the mobile app, click on Change server and put <https://canary.twake.app> then login.

{% hint style="info" %}
If you want to see the latest updates on the **canary** server you can go here: <https://github.com/linagora/Twake/blob/canary/changelog.md>
{% endhint %}


# Install on your server

How to install Twake

## Use Twake in SaaS

You can test or use Twake in our SaaS : [chat.twake.app](https://chat.twake.app)

## Run Twake in your server

First you'll need to [install docker and docker-compose](https://docs.docker.com/compose/install/).

Then you can install Twake on your server with this command

```
git clone https://github.com/TwakeApp/Twake.git
cd Twake/twake
docker-compose -f docker-compose.onpremise.mongo.yml up -d
```

Twake will be running on port 3000

### What's next ?

If you kept the default configuration, you can simply follow the signup steps, no email verification is required by default so you will get into Twake right after the signup steps.

## Ship Twake in production

See how to [manage configuration](/gettingstarted/configuration). And then how to [update security keys](/gettingstarted/configuration/security), and finally how to use your [custom domain](/gettingstarted/configuration/custom-domain-and-https).

### Update Twake

```
docker-compose stop
docker-compose rm #Remove images (not volumes so your data is safe)
docker-compose pull #Get new images
docker-compose up -d
docker-compose exec nginx yarn build #If you have custom frontend configuration
```

## Requirements and scalability

Currently you'll need at least a **2 cpu + 4 gb of ram** machine for **20-50 users** depending on their usage and with ElasticSearch disabled.

If you enable ElasticSearch, use two machines, or limit the cpu/ram dedicated to it and use a larger machine (at least 2gb of ram and 1 cpu dedicated to ES for 20-50 users).

If you need to deploy Twake for more users, you can use only one big machine up to 500 users (Will need something like **12 cpu and 32go of ram**), then you'll need to use multiple nodes.

{% hint style="info" %}
We deployed Twake on production for companies of 10 to 50 users in a single node. We also deployed Twake in a scalable mode and we support currently thousands of concurrent users.

If you deploy Twake in your own company we would love to have your feedback here <https://github.com/TwakeApp/Twake/issues/289> to improve our requirements documentation.
{% endhint %}

Now if you want to scale with Twake and support thousand of users, click on the link below :

{% content-ref url="/pages/-MIP-egLKKwFSm4n8hE\_" %}
[Scale with Twake](/gettingstarted/installation/scale-with-twake)
{% endcontent-ref %}


# Scale with Twake

You need Twake for more than 500 users ? You want to leverage ScyllaDB and ElasticSearch replication ? You are in the right place !

Scaling with Twake is possible if you install Twake with RabbitMQ, Redis, ElasticSearch and ScyllaDB.

```
git clone https://github.com/TwakeApp/Twake.git
cd Twake/twake

cp -n docker-compose.yml.dist.onpremise docker-compose.yml
cp -nR default-configuration/ configuration/

docker-compose pull

docker-compose up -d scylladb
sleep 5m #Wait scylladb to startup
docker-compose up -d php rabbitmq
sleep 10m #Wait php to create tables in scylladb

docker-compose up -d
```

> To run ElasticSearch (optional, but enabled by default in the Twake docker-compose) you must increase the max\_map\_count of your system: <https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#_set_vm_max_map_count_to_at_least_262144>
>
> To fix an other bug with ElasticSearch container, you must also run this command: `chmod 777 ./docker-data/es_twake` (create the folder if it doesn't exists in your docker-compose.yml folder)

Twake will be running on port 8000 🎉


# Configuration

More details about Twake configuration.

### Detach the configuration and start using your own

Each configuration file is optional, if not given, Twake will fallback to default configuration.

#### Backend configuration

You can find an example of Twake configuration (default configuration) here: <https://github.com/TwakeApp/Twake/blob/main/twake/backend/core/app/Configuration/Parameters.php.dist>

Copy the content of this file and put it in `[docker-compose.yml location]/configuration/backend/Parameters.php`

> **Tip:** you can put a 'cert' directory with apns.cert keys (mobile push notifications) beside the Parameters.php file.

#### Frontend configuration

You can find an example of Twake configuration (default configuration) here: <https://github.com/TwakeApp/Twake/blob/main/twake/frontend/src/app/environment/environment.ts.dist>

Copy the content of this file and put it in `[docker-compose.yml location]/configuration/frontend/environment.ts`

#### After a configuration change

Each time you change the configuration, restart your docker container like this:&#x20;

```
docker-compose restart nginx node php
docker-compose exec nginx yarn build #If you have custom frontend configuration
```

{% hint style="danger" %}
Warning, if you are using ScyllaDB (mandatory for any version before 2022) you must make sure ScyllaDB is started before to start the node container.

If your server is completely stoped you can use this command to make sure everything starts well:

&#x20;`docker-compose up -d scylladb; sleep 120; docker-compose up -d`
{% endhint %}


# Security

You should update this security keys to ship Twake in production.

> See how to [Detach Configuration](/gettingstarted/configuration) first.

The following keys must be updated to increase Twake security in /configuration/backend/Parameters.php:

```
"env" => [
  "secret" => "somesecret", //Any string
],
...
"websocket" => [
  ...
  "pusher_public" //Generate public and private key
  "pusher_private" //Put private key here
],
"db" => [
  ...
  "encryption_key" //Any string
]
...
"storage" => [
  ...
  "drive_salt" => "SecretPassword", //Any string
],
...
```


# Custom domain + HTTPS

Use a custom domain with Twake

{% hint style="info" %}
We do not offer the possibility to edit the nginx configuration present in the docker-compose containers yet. To enable https you first need to install nginx and configure on your machine.

Your nginx installation will be used to forward the requests from https to the docker-compose http port.

The last step is to tell Twake that the frontend is accessed from a different domain and protocol to handle the redirections.
{% endhint %}

#### Use port 80 or 443 over https

To use 443 create a new nginx install and attach a proxy to port 8000 + certauto.\
If you use Apache2 go on the [Apache2 configuration page](/gettingstarted/configuration/custom-domain-and-https/apache2-configuration).

> Follow this thread if you have issues with websockets and reverse proxy: <https://community.twake.app/t/twake-on-docker-behind-apache-proxy/78>

```
# /etc/nginx/site-enabled/default
location / {
    proxy_pass http://127.0.0.1:8000;
}

location /socketcluster/ {
    proxy_pass http://127.0.0.1:8000/socketcluster/;
    # this magic is needed for WebSocket
    proxy_http_version  1.1;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
    proxy_set_header    Host $http_host;
    proxy_set_header    X-Real-IP $remote_addr;
    proxy_connect_timeout 7d;
    proxy_send_timeout 7d;
    proxy_read_timeout 7d;
}
```

#### Configure domain name

You must edit the configuration in both `configuration/frontend/environment.ts` and `configuration/backend/Parameters.php`

```
//Exemple of environment.ts
export default {
  env_dev: false,
  mixpanel_enabled: false,
  sentry_dsn: false,
  mixpanel_id: false,
  front_root_url: 'https://twake.acme.com',
  api_root_url: 'https://twake.acme.com',
  websocket_url: 'wss://twake.acme.com'
};
```

```
//To change in Parameters.php
...
"env" => [
    ...
    "server_name" => "https://twake.acme.com/",
],
...
```

> Dont forget to restart your docker-compose 😉 and rebuild the frontend:\
> `docker-compose exec nginx yarn build`


# Apache2 configuration

🙏 From Dahpril (community) https\://github.com/TwakeApp/Twake/issues/76

#### Apache vhost

You need to create a vhost listening on port 443.

*For exemple with certbot :*

*1. Create a vhost listening on port 80 with ServerName equals to your custom domain. Don't define any document root. 2. Then, use certbot to get a certificate and automatically create your vhost listening on port 443*

```
sudo certbot --apache --email your_email -d your_domain --agree-tos --redirect --noninteractive
```

#### Reverse proxy

You have now to configure your reverse proxy directive. Head up to your 443 vhost configuration file and paste those directives (place them after server and ssl directives) :

```
RewriteEngine on
RewriteCond ${HTTP:Upgrade} websocket [NC]
RewriteCond ${HTTP:Connection} upgrade [NC]
RewriteRule .* "wss://127.0.0.1:8000/$1" [P,L]

ProxyRequests off

<Location />
    ProxyPass http://127.0.0.1:8000/
    ProxyPassReverse http://127.0.0.1:8000/
    ProxyPreserveHost On
</Location>
<Location /socketcluster>
    ProxyPass ws://127.0.0.1:8000/socketcluster
    ProxyPassReverse ws://127.0.0.1:8000/socketcluster
    ProxyPreserveHost On
</Location>

<Proxy *>
    AllowOverride All
    Order allow,deny
    Allow from All
</Proxy>

RequestHeader set X-Forwarded-port "80"
```

Be careful to NOT type trailing slash for ws location (it won't work).

I'm not sure that all directives are needed, but this configuration works for me.

#### Configuring remoteip

You also need to configure remoteip mod from apache with this command :

```
a2enmod remoteip
```

Then, edit /etc/apache2/conf-available/remoteip.conf to fit with this content :

```
RemoteIPHeader X-Real-IP
RemoteIPTrustedProxy 127.0.0.1 ::1
```

You can now enable the configuration of remoteip and restart Apache :

```
a2enconf remoteip
service apache2 restart
```

Return to the section "Configure domain name" of [Custom domain + HTTPS](/gettingstarted/configuration/custom-domain-and-https#configure-domain-name) page to continue the configuration.


# Configure mail server

To configure your mail serveur with Twake.

Here is the default mail block in the configuration file to edit (from Parameters.php, see "[Configuration page](/gettingstarted/configuration)"):

```
"mail" => [
    "sender" => [
        "host" => "", //smtp server
        "port" => "",
        "username" => "",
        "password" => "",
        "auth_mode" => "plain" //plain, login, cram-md5, or null
    ],
    "from" => "noreply@twakeapp.com",
    "dkim" => [ //Optional, to avoid lost emails, configure your dns with dkim
        "private_key" => "",
        "domain_name" => '',
        "selector" => ''
    ],
    "twake_domain_url" => "https://twakeapp.com/",
    "from_name" => "Twake", //Server owner name
    "twake_address" => "Twake, 54000 Nancy, France", //Server owner address
    "template_dir" => "/src/Twake/Core/Resources/views/", //Must not be modified
],
```

⚠️ Once edited, don't forget to restart docker.

You can test the good behaviour of emails going into your account parameters, emails, add a secondary email. Or simply try to invite a user using its email.


# Customisation

How to make Twake feel better in your company.

> Customising Twake on SaaS (web.twake.app) is not available yet, contact sales to install an on-premise Twake version.

### Customise style and logos

You can customise Twake for your brand using the `configuration/backend/Parameters.php` file.

```
"defaults" => [
  "branding" => [
    "header" => [
      "logo" => '', //Some logo used on header coloured background
      "apps" => [ //A list of apps accessible from header
        [
          "name"=> '', //App name
          "url"=> '', //Url to your app
          "icon"=> '', //App icon as image
        ],
      ],
    ],
    "style" => [
      "color" => '#2196F3', //Change default main color
      "default_border_radius" => '2', //Change default main border-radius
    ],
    "name" => "", //Brand name
    "enable_newsletter" => false, //Disable newsletter checkbox on subscribe
    "link" => "", //Link to your website (showed on login page)
    "logo" => "" //Coloured logo (white background)
  ]
]
```

### Customize apps

You can disable default apps with this command (apps will not be installed on future new companies or workspaces)

```
"defaults" => [
  "applications" => [
    "twake_calendar" => false, //Not available
    "twake_tasks" => [ "default" => false ], //Available but not by default
    "twake_drive" => [ "default" => true ], //Available and by default in every new workspaces
    "connectors" => [
      "jitsi" => [ "default" => true ],
      "linshare" => false
    ]
  ]
]
```

After editing this configuration, **restart docker-compose** (to import new configuration) and type the following command:

```
#docker-compose restart #To import new configuration
docker-compose exec php php bin/console twake:init
```


# Connectors and plugins

You can connect anything you want on Twake, read this to know how.

### Install connectors and plugins on SaaS version

Work in progress.

Ask us to be faster on <https://community.twake.app> !


# 👨‍💻 Authentication modes

Twake let you authenticate using CAS or OpenID.

Twake works with OpenID, CAS and in standalone.

See child pages to configure with [KeyCloak](/gettingstarted/configuration/authentication-modes/using-keycloak-ldap-openid-and-more) or [LemonLDAP](/gettingstarted/configuration/authentication-modes/installing-twake-with-lemonldap-ldap-openid-and-more).


# Using Keycloak (LDAP, OpenID and more)

Use Keycloak with Twake

#### Run keycloak and persist data

```
cd twake
docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -v $(pwd)/docker-data/keycloak-db:/opt/jboss/keycloak/standalone/data jboss/keycloak
```

#### Configure keycloak with twake for the first time

**On Keycloak**

Go to `http://localhost:8080/auth/`

Login with admin:admin

Go to client > account > Credentials tab and save the `Secret`

Go to Settings tab and add a Valid Redirect uri to `http://localhost:8000/*` and save

Then create an user in User / Add User

⚠️ Users must have an email and the email must be marked as verified !

Then create a password for this user.

**On Twake**

Go to your \[docker-compose file location]/configuration/backend/Parameters.php (see [Configuration](/gettingstarted/configuration))

Change defaults.auth.openid to:

```
  "use" => true,
  "provider_uri" => 'http://[machine_ip]:8080/auth/realms/master',
  "client_id" => 'account',
  "client_secret" => '[keycloak_secret]',
  "logout_suffix" => "/protocol/openid-connect/logout" //Specific to keycloak
```

ℹ️ \[machine\_ip] Because Twake is accessing keycloak for inside a docker container, do not use localhost or 127.0.0.1 to access keycloak.


# Installing Twake with LemonLDAP (LDAP, OpenID and more)

Harder than KeyCloak but has other features, see official LemonLDAP site.

#### 1. Twake configuration

Edit Twake PHP config `twake/backend/core/app/Configuration/Parameters.php`, in defaults.auth.openid

```
"openid" => [
    "use" => true,
    "provider_uri" => 'http://auth.open-paas.org.local',
    "client_id" => 'twake',
    "client_secret" => 'secret',
    "ignore_mail_verified" => true,
    "ignore_id_token_verification" => true,
    "provider_config" => [
      "token_endpoint"=> "http://auth.open-paas.org.local/oauth2/token", //token_endpoint
      "userinfo_endpoint" => "http://auth.open-paas.org.local/oauth2/userinfo",//userinfo_endpoint
      "end_session_endpoint" => "http://auth.open-paas.org.local/oauth2/logout",//end_session_endpoint
      "authorization_endpoint" => "http://auth.open-paas.org.local/oauth2/authorize",//authorization_endpoint
    ]

],
```

Add line to /etc/hosts if needed :

`sudo docker-compose exec php bash -c "echo '51.210.124.92 manager.open-paas.org.local auth.open-paas.org.local reload.open-paas.org.local' >> /etc/hosts"`

#### 2. Lemon LDAP configuration

Dans ClientOpenIDConnect > twake > Options > Basique > Adresse connexion : <http://15.236.209.74/ajax/users/openid>

Dans ClientOpenIDConnect > twake > Attributs exportés :

```
{
  email_verified: email_verified,
  picture: picture,
  name: cn,
  given_name: givenName,
  family_name: sn,
  email: mail,
  sub: uid
}
```

\[Ne marche pas pour le moment] Dans ClientOpenIDConnect > twake > Options > Déconnexion > Adresse : <http://15.236.209.74/ajax/users/openid/logout_success>


# Welcome to Twake !

Discover how to use Twake

Twake is an open source collaborative workspace. It can be self-hosted, used is saas and easily integrated into your teams. Twake offers all the features for collaboration :  team chat, file storage, team calendar,  tasks manager.

In this chapter, you'll find how to use Twake.


# Console

How to use the Twake console

The console is where you can manage your company and all information shared on Twake, LinShare and more.


# Users

How users are managed in the Twake console

As user admin, you have access to the user management page.

Open the console : <https://console.twake.app/> and open the page "Users".

![User management page](/files/-MYtPcbFIYNI9b95gjcf)

## Add a user

To work with your colleagues in your company, you have 2 choices :&#x20;

* Create user account
* Invite them by email

You can also invite users from the chat :&#x20;

{% content-ref url="/pages/-MYtt8J\_VRnBN15cOEO1" %}
[Invite user from Chat](/how-to-use-it/company-and-workspace/invite-user-from-chat)
{% endcontent-ref %}

### Send email invitation

To send an invitation, you can click on `Add user` > `Invite users`.Enter emails you want to invite separated with coma. After validation, an email is sent to this address. Your colleagues will have a link to signup and join your company.

![Invite user](/files/-MYtmpheMgnxW7noGdoZ)

### Create user account

You can create directly accounts for your colleagues. For that, click on  `Add user`  > `Create account`. After giving email, name and Role, you can specify a password (or generate a random one). You just have to send credentials to your teammate.&#x20;

![](/files/-MYtpfPih9FO7MgS2b2D)

## Manage user


# Company & workspace

How company and workspace work on Twake

## Company

Companies are the largest entities on Twake. They represent your virtual business. You will find all your employees and connectors that you have developed internally. Inside your company is your workspaces.

You can switch from one company to an other by clicking on the left bottom button :&#x20;

![](/files/-MY9G30gd5wQbGz98lmu)

## Workspace

Workspaces represent the teams or departments in your company. Each workspace has its members, discussions, files, tasks, and calendar. It's your team's sharing space.


# Invite user from Chat

Invite user from Twake

You can invite user in your workspace.

Open the menu, and click on `Collaborators`  > `Add colleagues`. Enter emails of your colleagues (separated with coma) and validate.

![](/files/-MYtuQ3fhQ_-HGr4pKU_)

You can also invite users from the console :

{% content-ref url="/pages/-MYtPM7GNj1N\_eksjfHo" %}
[Users](/how-to-use-it/console/users)
{% endcontent-ref %}


# Rights

How rights are managed on Twake

Twake has several levels of rights. Users belong to one or more workspaces within the company. They can have different statuses inherent to their membership to the company or workspace

## Owner

He's the main administrator of the whole company. He can:&#x20;

* Allow connectors and applications to be installed in workspaces&#x20;
* Change the corporate identity&#x20;
* Giving/withdrawing the right "Owner of the company" to a member of the company&#x20;

The creator of the company is the only owner. You can not change this role. Only the creator can be owner of the company.

## Admin

An admin has the same right than the owner. He can:&#x20;

* Allow connectors and applications to be installed in workspaces&#x20;
* Change the corporate identity&#x20;
* Giving/withdrawing the right "Owner of the company" to a member of the company&#x20;

The creator of the company is the only owner. You can not change this role. Only the creator can be owner of the company.

To grant the admin right, open the console (<https://console.twake.app>). Open `Users` section and `Edit` the member you want to set admin. Update the Role with `Admin` and save.

![](/files/-MYu86oJAcsJw32tgcA9)

## Workspace administrator

He's the team administrator. He has the same rights as a user. In addition, he can:&#x20;

* Add/remove connectors and applications in the workspace
* Changing the identity of the workspace&#x20;
* Adding members to the workspace&#x20;
* Delete workspace&#x20;

By default, the creator of the workspace is the administrator. This right can be cumulated with that of a company administrator.

To grant the workspace level, open your workspace collaborators page, and select `Administrator`&#x20;

![](/files/-MYu9loO1E-brNseJJcl)

## User

The "user" right is the default right of a member in a workspace. It can :&#x20;

* Read and send messages to public and private chat channels (to which he has been invited)&#x20;
* Create, modify, delete documents from the drive&#x20;
* Create, edit, delete events and calendars&#x20;
* Create, modify, delete tables and tasks Invite users to the workspace&#x20;
* Create a workspace within the company

It's a default right. If the collaborator is not Company or Workspace administrator and he is not a Guest user, he is a simple User

## Guest

This status is intended for external partners working on a specific topic with the team. Guest user can :

* Talk in the channel where they are invited

They can not join by themself any channel (even public). They need to be invited by a normal user.

To set the Guest level, open the console (<https://console.twake.app>) and the users page. Set the user's role to "Guest".

![](/files/-MYuBfa3PH-ofLNOW3P5)


# Applications

Discover Twake application


# Chat

How Twake chat is working

The discussion application  are Twake's entry points. This is where you communicate with your team.

{% content-ref url="/pages/-MF\_MDZV8QEU5GBORbvQ" %}
[Channels](/how-to-use-it/applications/how-to-use-chat/channels)
{% endcontent-ref %}

{% content-ref url="/pages/-MFfNPJLIAQNRxQVQaUV" %}
[Message](/how-to-use-it/applications/how-to-use-chat/message)
{% endcontent-ref %}


# Channels

Discover how channels are used on Twake

## Create a channel

To create a channel, you must have `Administrator` rights for the workspace in question.&#x20;

{% content-ref url="/pages/-MBnyZ4KNLi9AGmjZ9WO" %}
[Rights](/how-to-use-it/company-and-workspace/rights)
{% endcontent-ref %}

To create your channel, click on the `+` button right to the "Channel" inscription. Fill a name for your channel, select a wonderful emoji and select if it is a public or private channel (by default, channels are private).

## Channel sections

You can organize your channel by grouping them. If you are `Administrator`, you can drag a channel and drop in the section you want `Add here` or in `New group`.&#x20;

To edit section name, you just have to click on it, change the name and press `Enter`

![](/files/-MFfM3H7oBH7CeXm2Siy)

## Public channel

Public channels are visible to all members of the workspace.

## Private channel

Private channels are channels accessible only to a limited number of users. Members not present in these channels cannot see them.

### Modify the members of a private channel

You can choose the members of a private channel when creating it. To modify the members of a private channel once created, place your mouse on the channel, click on the `...` then on `Edit channel`, in the `Participants` box, you can add or remove the members of the channel.

![](/files/-MFaB7hDiHARXGYW547A)

## External member

You have the possibility to invite external members to your channel. External members are members who are not in the workspace and who are added to one, or more,  specific channel(s).&#x20;

This feature is useful for inviting users who are not in your company. Freelancers, for example, can be added to a specific channel to collaborate on a project. They will only have access to this channel, they will not see public channels.

![](/files/-MFeqIfRcw6o96aRKYjS)

## Mute a channel

You can mute a channel so that you do not receive notification of it. You will not receive a new notification from that channel unless you are mentioned (with @username). To do it, click on `...` and `Mute`a barred bell icon will appear.

## Star a channel

If a channel is very important for you, you can star it. Stared channels are gathered at the top of your channel. It is only for your view, not for everyone. To star a channel click on `...` and `Star this channel`. Your channel will be move to the stared channel section (at the top).


# Message

Send message on Twake

## Send a message

To send a message, select the channel where you want to post. In the input message, write what you want to send. Press `enter` to send the message.

If you want to add a new line to your message, press `Shift + Enter`.

![](/files/-MFfQd9xvrZvG5-ZhTTb)

#### Pin

If one of the message is important for the team, you can pin it (or unpin)

![](/files/-MFfRNiCLSRRuma8e_xj)

#### Reaction

You can react to a message with emoji. Click on the `Emoji button` in message to show emoji picker.

### Response to a message

Someone ask you a question ? You can answer it by clicking `Answer` below the message.

![](/files/-MFfUZOKuTmfbYP-RKOx)

### Reorganize your chat

You can reorganize your chat by dragging and dropping your messages.&#x20;

![](/files/-MFfX6zzBIzJKz3mYCl7)

###

### See thread message

You can see a thread in a specific view. Hover over in one, the button `Show on the right` . A side panel opens.

## Integration

### Gif&#x20;

You can send gif in your channel. Just clic gif icon to open a gif selector.

![](/files/-MGiO6vDf2Dz9fWevbsm)

### Call

If you need to call your team, there is native integration with Jitsi. Just click on the green button with the phone and a menu opens on the left to create a Jitsi call. Once the information is filled in, the call link is sent to your team.

## Send document

If you want to send a document through the chat, drag it from your computer and drop it into the chat. It will be uploaded and shared with your teammates.

You can also send a document from Twake drive: click on the `+` button, to the left of the input. You can select a file from Twake and share it in the chat room.


# Drive

How you can use the drive on Twake


# File and folder

How you can create file & folder on Twake

##


# Share file with public link

How you can share your files with a public link

In Twake, you can share your folders or files to external user. You juste have to create a public link on what you want to share and send the link. Your partner will access to these elements without creating a Twake account.

### Create a public link

Click on "public access" of what you want to share and "create an access link".

![](/files/-MDKUJCnT1DZifXw2wDc)

###

### Remove a public link

If you want to remove the public link, select folder you want to unlink, then, "Public access" and "Suppress the link"

![](/files/-MDKVIq9nLMo5AYHjwW4)


# Calendar

How to use Twake calendar


# Tasks

How to use Twake task


# Connectors

How to add connector on Twake


# n8n

Did you ever dreamed to use Twake and n8n together ?

## How to connect Twake with n8n

n8n is a powerful workflow automation tool wich can be self hosted : [n8n.io](https://n8n.io)

![n8n interface](/files/-MEweihn8XcFYXs7s0r4)

### Install n8n in Twake

Install your n8n application in your workspace and get your key :&#x20;

![](/files/-MDFRNJxmD40pyBjwn3m)

### Create your credential on n8n

![](/files/-MDFWTyWZKjOVBL1M-cD)


# Desktop and mobile app

Yes, we have desktop and mobile apps !

## Desktop

You can download Twake for Mac, Windows, Linux here: <https://github.com/linagora/Twake-Desktop/tree/master/download>

Click on the version according your OS, and click on "download" to get the desktop application.

![](/files/-McFSM3KzPR0OC8zcPyb)

### Switch to on-premise servers

On the desktop app, you can use the following options to change your server:

• On MacOS, click on Help > Change Twake Server\
• On Windows, MacOS and Linux, use the shortcut CTRL+ALT+SHIFT+S or CMD+ALT+SHIFT+S

## Mobile App

The mobile app is available on iOS App Store and Android Play Store.

### Switch to on-premise servers

On the mobile app, just log out and you should be able to change the server from the bottom left login page link "Change Server".

#### Push notifications on the on-premise mobile app

This feature is not yet available, say you want this feature using a "thumbs up" emoji here: <https://github.com/TwakeApp/Twake/issues/130>


# Privacy

This page is currently Work In Progress.


# Home

Welcome to the developers API home, here you can create apps, plugins and connectors for Twake.

## Introduction:

Twake offer the possibility to uses a lot of application to increase the quality of your team workflow. You can now easily send a Gif to add conviviality into your channels, start Jitsi calls in one click, create polls or even get notified in your channels using webhooks or automation as powerful as n8n allows.

## Ready to use integration:

Twake have some integrations ready to use that are just waiting for you to be installed in your workspaces. You can find in the list below these integrations:

* Jitsi: <https://github.com/linagora/Twake-Plugins-Jitsi>
* Giphy: <https://github.com/linagora/Twake-Plugins-Giphy>
* SimplePoll: <https://github.com/linagora/Twake-Plugins-SimplePoll>
* Webhoocks: <https://github.com/linagora/Twake-Plugins-Webhooks>
* N8n: <https://github.com/linagora/Twake-Plugins-n8n>

## Imagination is your limit&#x20;

By installing webhooks or n8n in your workspace, you can now imagine infinite uses cases to increase the quality of your team workflow. Connect your channels to get notify of new code changes when pushing on github or to get notify in a n8n workflow.&#x20;

If you think that you can't do something with integration, as Twake is open source, feel free to contribute to an existing Twake's integration or even more create the new integration that answer to your need. &#x20;

> Please do not work on this list of plugins as we already made them (their are just not yet open-sourced)\
> Zapier, IFTTT, OnlyOffice, RSS feed, Github, Gitlab and Reminder.

🤗 We are not yet ready for this documentation, up this issue to have it sooner : <https://github.com/TwakeApp/Twake/issues/116>


# Getting started

Welcome to the developers API of Twake, let's begin together

## Introduction:

*While users have the ability to create message complexes using the built-in formatting system, applications can go even further and offer many types of messages such as system messages for notifications or interactive messages for your most popular applications.*

## Basics:

* [Create your first application](/developers-api/get-started/create-your-first-application)
* [Authenticate with Postman](/developers-api/get-started/authenticate-postman)
* [Send a message with your application](/developers-api/get-started/send-a-message-for-twake)
* [Trigger action from command](/developers-api/get-started/trigger-action-from-command)


# Create your first application

You want to create an application for Twake? It's easy, just follow the steps in this documentation! 😀

## Introduction:

This guide will walk you through creating, setting up and installing a Twake application.

## Prerequisites:

* *You are a **manager** of the company.*

## Steps:

### 1. Create a Twake application

![ Fastest way to create an app](/files/-MFoHeYbsFMCcmF8vJ6T)

1. *Start by opening your `Workspace settings`*
2. *Go to `Applications and connectors`, you should see an **Installed applications area** and **Applications developed by the company**,*
3. *Click on `Access your applications and connectors` then `Create an application`,*
4. *Enter your application name and application group.*&#x20;

**Application group** is used to group your application with other applications of the same type. **Be careful, the application group will not be modifiable later.**

### **2. Identity of the application (Optional)**

![](/files/-MFoT3iRFjNgBqHL7tol)

Let's add a description and an icon for our application. \
it will be much prettier! 😇

### 3. API settings

There you will find some important pieces of information:

![](/files/-MFoWXWD4Yjk37hhJPS7)

* *Your API private key,*
* *Your public application identifier,*
* *URL that will be used to receive events for your application,*
* *List of IP addresses that have the right to call the Twake API with your credentials. (You can use `*`during the development of your application.)*

**Private key** and **Public application identifier** ​​relate to **calls to the Twake API**.

### 4. Display settings (Optional)

![You can fill your JSON object here](/files/-MFoymstR1Jy4AVNOCEL)

To configure where your application should display, you need to fill a `JSON` object in `Display Settings` field.

[Here](/developers-api/application-settings/application-visibility-example) is a quick example, each field is optional and his presence determines the positioning of your application in Twake.

### 5. Application privileges

![](/files/-MFp-XdgFwD0ykYV_p5t)

Your application can access and modify data, only according to your needs you don't need to access all the data present in Twake. \
\
This is why you must specify the accesses for the proper functioning of your application. These accesses will be public and indicated to the user before the installation of your application.

In our example, we will only add  `message_save` and `message_remove` in `Write privileges`.\
\
If you want to know more about capabilities and privileges, take a look at the list [here](/developers-api/application-settings/privileges).

### 6. Install application

Once you've configured your application, you need to install it on Twake.&#x20;

![](/files/-MFp6zjm5fwrKyOXARze)

Go to `Applications and connectors`, search and display your application then install it.

Your application is now ready, check the [Authenticate with Postman](/developers-api/get-started/authenticate-postman) documentation for starting using it !


# Authenticate with Postman

Postman example

## Introduction:

This guide will introduce you to authenticate with [Postman](https://www.getpostman.com/).

## Prerequisites:

* *You are a **manager** of the company.*
* You have already [created a Twake application](/developers-api/get-started/create-your-first-application).
* *You have previously installed* [*Postman*](https://www.getpostman.com/)*.*

## Steps:

### 1. Log your Application

* Open Postman&#x20;
* Find the app's credentials according to [/pages/-MFkdZUTjRdBWQMCHJOw#3.-api-settings](https://doc.twake.app/developers-api/get-started/pages/-MFkdZUTjRdBWQMCHJOw#3.-api-settings "mention")
* Send POST request with :&#x20;
  * Url: <https://web.twake.app/api/console/v1/login>
  * Headers: `{ "Content-Type": "application/json", }`
  * Body: `{ id: $APP_`*`ID, secret: $APP_SECRET`*` ``}`
* This POST request will return a JWT token, this token will allow your application to send events in Twake

### 2. Optional: Verify your token

If you're not sure that the procedure to generate a token goes well :&#x20;

* Send GET request with :&#x20;
  * Url: <https://web.twake.app/api/console/v1/me>
  * Headers: `{ "Content-Type": "application/json",` Authorization: "`Bearer " +  $APP_TOKEN }`

### &#x20;


# Send a message with your application

Send a message through API

\
Introduction: <a href="#introduction" id="introduction"></a>
------------------------------------------------------------

This guide will introduce you to send message with your application in Twake.

## Prerequisites: <a href="#prerequisites" id="prerequisites"></a>

* You have already created a Twake application.
* *Your application is installed and saved in your company.*

## Steps: <a href="#steps" id="steps"></a>

### 1. Send message as a new Thread&#x20;

* Find the app's token according to [1. Log your Application](https://doc.twake.app/developers-api/get-started/pages/-MFp4ZE5Vg12Dw8qLbRN#1.-log-your-application)
* Find the identifiers to target a channel:
  * Company's *id: $COMPANY\_ID*
  * Workspace's *id: $WOKSPACE\_*&#x49;D
  * Channel's *id: $CHANNEL\_ID*
* *Set the message you want to send:*
  * *Minimal:*&#x20;

    *$MESSAGE = { "text": "Hello world !" }*
  * *To take full advantage of the messages capability in Twake see the* [*MessageObject*](/internal-documentation/backend-services/messages-service/database-model)
* Send POST request with :&#x20;
  * Url: <https://web.twake.app/api/messages/v1/companies/$COMPANY\\_ID/threads>
  * Headers: `{ "Content-Type": "application/json",` Authorization: "`Bearer " +  $APP_TOKEN }`
  * Body: `{ resource: { participants: [ { type: "channel", id: $CHANNEL_ID, company_id: $COMPANY_ID, workspace_id: $WORKSPACE_ID, }, ], }, options: { $MESSAGE }, }`
* This POST request will return a [ThreadObject](/internal-documentation/backend-services/messages-service/database-model)

### 2. Send message as a Thread answer

* Find the app's token according to [1. Log your Application](https://doc.twake.app/developers-api/get-started/pages/-MFp4ZE5Vg12Dw8qLbRN#1.-log-your-application)
* Find the identifiers to target a channel:
  * Company's *id: $COMPANY\_ID*
  * Workspace's *id: $WOKSPACE\_*&#x49;D
  * Channel's *id: $CHANNEL\_ID*
  * *Thread's id: $THREAD\_Id*
* *Set the message you want to send:*
  * *Minimal:*&#x20;

    *$MESSAGE = { "text": "Hello world !" }*
  * *To take full advantage of the messages capability in Twake see the* [*MessageObject*](/internal-documentation/backend-services/messages-service/database-model)
* Send POST request with :&#x20;
  * Url: <https://web.twake.app/api/messages/v1/companies/$COMPAN&#x59;_&#x49;D/threads/$THREAD\\__&#x49;D>
  * Headers: `{ "Content-Type": "application/json",` Authorization: "`Bearer " +  $APP_TOKEN }`
  * Body: `{ resource: { $MESSAGE } }`
* This POST request will return a [MessageObject](/internal-documentation/backend-services/messages-service/database-model)

### 3. Applications can send customized messages&#x20;

The [MessageObject](/internal-documentation/backend-services/messages-service/database-model) object have a property called "block" that allow your application to send messages which contains more than a simple string. For example in an application message you can display an iFrame, buttons, menu selector, etc... Combining all this options you can create everything you want up to the limit of your imagination. To understand how to create powerful message using the block property see [blocks](/developers-api/blocks).


# Trigger action from command

Trigger action from command

## Introduction: <a href="#introduction" id="introduction"></a>

This guide will introduce you to trigger action from your application using command

## Prerequisites: <a href="#prerequisites" id="prerequisites"></a>

* You have already created a Twake application.
* Your application is installed and saved in your company.

## Steps: <a href="#steps" id="steps"></a>

### 1. Let your application listen to command

* Go in your app developer's setting:&#x20;
  * Click on your username in the top left corner
  * Go to workspace settings&#x20;
  * Go to integrations&#x20;
  * Click on the three-dot next to your application
  * Open developper setting&#x20;
* Click on display&#x20;
* You will find and editable object containing a twake object
* Add a new property commands in this object like this:

  * `"commands" : [{"command": string, "descritpion": string }]`
  * The first property of commands is command that let you define a name for your command, by default the command name is the name of your application.&#x20;
  * The second property of command is description that let you describe the way to use the command you want to define.

### 2. Use your command in a channel

* In the message editor write /command&#x20;
* A popup displaying the description on how to use the command related to your application should open.


# Application settings

Set your application


# Api

There you will find some important pieces of information


# Display

This is a JSON parameter to define where your app should appear

#### Latest version

```typescript
{
  twake: {
    version: 1;

    files?: {
      preview?: {
        url: string; //Url to preview file (full screen or inline)
        inline?: boolean;
        main_ext?: string[]; //Main extensions app can read
        other_ext?: string[]; //Secondary extensions app can read
      };
      actions?: //List of action that can apply on a file
      {
        name: string;
        id: string;
      }[];
    };

    //Chat plugin
    chat: {
      input?:
        | true
        | {
            icon?: string; //If defined replace original icon url of your app
            type?: "file" | "call"; //To add in existing apps folder / default icon
          };
      commands?: {
        command: string; // my_app mycommand
        description: string;
      }[];
      actions?: //List of action that can apply on a message
      {
        name: string;
        id: string;
      }[];
    };

    //Allow app to appear as a bot user in direct chat
    direct?:
      | true
      | {
          name?: string;
          icon?: string; //If defined replace original icon url of your app
        };

    //Display app as a standalone application in a tab
    tab?: {
      url: string;
    };

    //Display app as a standalone application on the left bar
    standalone?: {
      url: string;
    };

    //Define where the app can be configured from
    configuration: ("global" | "channel")[];
  };
};

```

#### Legacy format

```javascript
type ApplicationDisplay = {
  twake: 
    {
      "version": 0, //Legacy
      "tasks_module" : {
        "can_connect_to_tasks": true
    	},
      "calendar_module" : {
        "can_connect_to_calendar": true
    	}
    	"drive_module" : {
        "can_connect_to_directory": true,
        "can_open_files": {
    			"url": "", //Une url à appeler pour éditer le fichier (ouvert dans un onglet)
    			"preview_url": "", //Une url à appeler pour prévisualiser un fichier (iframe)
    			"main_ext": ["docx", "xlsx"], //Extensions principales
    			"other_ext": ["txt", "html"] //Extensions secondaires
    		},
    		"can_create_files": [
    			{
    				"url": "https://[...]/empty.docx",
    				"filename": "Untitled.docx",
    				"name": "Word Document"
    			},
          {
    				"url": "https://[...]/empty.xlsx",
    				"filename": "Untitled.xlsx",
    				"name": "Excel Document"
    			}
    		]
      },
      "member_app": true, // Si défini, votre application génèrera un membre
                          // virtuel dans l'espace de travail avec lequel les
                          // utilisateurs pourront discuter.
      "messages_module": {
        "in_plus": {
          "should_wait_for_popup": true
        },
        "right_icon": {
    			"icon_url": "", //If defined replace original icon url of your app 
          "should_wait_for_popup": true,
          "type": "file" //"file" | "call"
        },
        "action": {
          "should_wait_for_popup": true,
          "description": "fdsqfds" //Description de l'action, sinon remplacé par le nom de l'app
        },
        "commands": [
    			{
    				"command": "mycommand", // my_app mycommand
            "description": "fdsqfds"
    			}
    		]
      },
      "channel": {
        "can_connect_to_channel": ""
      },
      "channel_tab": {
        "iframe": ""
      },
      "app": {
        "iframe": "",
        "plus_btn": {
          "should_wait_for_popup": true
        }
      },
      "configuration": {
        "can_configure_in_workspace": true,
        "can_configure_in_channel": true,
    		"can_configure_in_calendar": true,
    		"can_configure_in_tasks": true,
        //"can_configure_in_directory": true
      }
    }
  }
}
```


# Privileges

Which privileges you need for your app


# Identity

How Identity are managed


# API Reference

Discover our wonderful API


# Webhook

How to use webhook with Twake


# Drive

How to manage Drive through API


# Message

How to manage messages through API

*While users have the ability to create message complexes using the built-in formatting system, applications can go even further and offer many types of messages such as system messages for notifications or interactive messages for your most popular applications.*

## Group\_id and channel\_id:

1. Get the current front id:

   `channel_service.currentChannelFrontId`
2. With the channel front id, you will be able to get the channel id by doing this:

   `collections.collections.channels.manager.findByFrontId("My-Front-Id")`
3. Get the current group id:

   `workspaceService.currentGroupId`

## Message methods:

{% content-ref url="/pages/-MFkD-UdbkDG-qYmB8zN" %}
[POST Request](/developers-api/api-reference/message/post-request)
{% endcontent-ref %}

{% content-ref url="/pages/-MFkORKwyA3jIAEDyj5r" %}
[DELETE Request](/developers-api/api-reference/message/delete-request)
{% endcontent-ref %}


# DELETE Request

This method allow to delete message to a specific channel.

#### Before starting, make sure you added **`message_save`** into **`write privileges`**. See the [Application access and privileges](/developers-api/get-started#application-access-and-privileges) section.

## DELETE Message

<mark style="color:green;">`POST`</mark> `https://api.twake.app/api/v1/messages/remove`

#### Headers

| Name          | Type   | Description                               |
| ------------- | ------ | ----------------------------------------- |
| Content-Type  | string | `application/json`                        |
| Authorization | string | `Basic base64(public_id:private_api_key)` |

#### Request Body

| Name      | Type   | Description                                            |
| --------- | ------ | ------------------------------------------------------ |
| message   | object | `Require channel_id and content, see the body section` |
| group\_id | string | `See the group_id and the channel_id section`          |

{% tabs %}
{% tab title="200 if request is successful, the response should be something like this." %}

```
{
    "result": {
        "channel_id": "--", // Channel id
        "id": "--" // Deleted message id
    }
}
```

{% endtab %}
{% endtabs %}

## Body example:&#x20;

```
{
	"group_id": "---", 
	"message": {
		"channel_id": "---",
		"id": "--" // Message id that you want to delete
	}
}
```


# POST Request

This method allow to send message to a specific channel.

#### Before starting, make sure you added **`message_save`** into **`write privileges`**. See the [Application access and privileges](/developers-api/get-started#application-access-and-privileges) section.

## POST message

<mark style="color:green;">`POST`</mark> `https://api.twake.app/api/v1/messages/save`

#### Headers

| Name          | Type   | Description                               |
| ------------- | ------ | ----------------------------------------- |
| Authorization | string | `Basic base64(public_id:private_api_key)` |
| Content-Type  | string | `application/json`                        |

#### Request Body

| Name      | Type   | Description                                            |
| --------- | ------ | ------------------------------------------------------ |
| message   | object | `Require channel_id and content, see the Body section` |
| group\_id | string | `See the group_id and channel_id section`              |

{% tabs %}
{% tab title="200 If request is successful, the response should be something like this." %}

```
{
    "object": {
        "id": "--", // Message Id
        "channel_id": "--", // Channel
        "parent_message_id": "--", // Thread id
        "sender": null, // User who send the message
        "application_id": "--", // Application who send the message
        "edited": false, 
        "pinned": null,
        "hidden_data": null,
        "reactions": [],
        "modification_date": 1598518289, // Last modification date
        "creation_date": 1598518289, // Creation date
        "content": "Hello !", // Object (see Twacode) or string
    }
}
```

{% endtab %}
{% endtabs %}

### Body example:&#x20;

```
{
	"group_id": "--",
	"message": {
		"channel_id": "--",
		"content": "Hello, this is my first message !",
		"_once_ephemeral_message": false // Set true if you want this message ephemeral
	}
}
```


# Authentication

How to manage authentification on API call

## Authencate your app in Twake

**All your connections** should respect the Basic access authentication protocol, which must be used via HTTPS, except in development mode. In order to make an API call with this method, you must add an HTTP header:

```
Authorization: Basic base64(public_id:private_api_key)
```

**You must concatenate** your [public\_id and private\_api\_key](/developers-api/get-started#identity-and-api-settings) , **then convert** the whole **to base64**. Your HTTP header will therefore look like:

```
# For the keys 'public_id' and 'private_key'
Authorization: Basic cHVibGljX2lkOnByaXZhdGVfYXBpX2tleQ==
```

## Authencate your app in a company

**All your requests should have at least a "group\_id" key with the company id you**


# Blocks

## Introduction: <a href="#introduction" id="introduction"></a>

This guide will introduce you to use blocks to custom Twake messages. Twake allows application to send customs messages. This customs messages offer the possibility for an application to easily format the text your application wants to send and/or display UI components like button, input or iframe.&#x20;

## Write your first block: <a href="#introduction" id="introduction"></a>

### 1. Take a look at slack block kit documentation&#x20;

* Go to this page: [Slack Block kit](https://api.slack.com/block-kit)
* Understand basic layers of block:
  * Block

    First layer object, defining the use case of the current block (Actions, Context, Header, Files...). It can contain block elements and Composition object.&#x20;
  * Block elements

    Second layer object, defining complex element that will be display in a block (Button, Menus, Input...). It can contain composition object
  * Composition object&#x20;

    Third layer object, formatting the data to display in both block and/or block elements

### 2. Try your first block&#x20;

* Go to this page: [Block Kit Builder](https://app.slack.com/block-kit-builder)
* Try to add/remove block&#x20;
* Start writing block and check your result

### 3. Twake block&#x20;

Twake have some blocks that are not implemented in slack block kit (iframes, progress bar and copiable). To use them please follow this:&#x20;

#### iframe &#x20;

An iframe is **Block** allowing you to display an html page in twake.

How to use it:&#x20;

* Iframe type:&#x20;

```
type BlockIframe = { 
          type: "iframe";
          iframe_url: string; 
          width: number; 
          height: number; 
     };
```

* type: always "iframe"
* iframe\_url: the URL of the web page you want to display&#x20;
* width: the with that you iframe will take
* height: the height that you iframe will take

Example:&#x20;

```
{ 
    "blocks": [ 
        { 
            "type": "iframe", 
            "iframe_url": "
            https://twake.app
            ", 
            width: "40vh", 
            height: "40vh" 
        }
    ]
}
```

#### Copiable

A copiable is **Block element** is a readable only input allowing you to copy string with a button.

How to use it:&#x20;

* Copiable type: it is a plain text input block element with readonly and copiable set to true

```
type BlockElementPlaintextInput = {
  type: "plain_text_input";
  action_id: string;
  placeholder?: CompositionPlainTextObject;
  initial_value?: string;
  multiline?: boolean;
  min_length?: number;
  max_length?: number;
  dispatch_action_config?: DispatchActionConfiguration;
  readonly?: boolean;
  copiable?: boolean;
};
```

* type: always `"plain_text_input"`
* readonly: always `true`
* copiable: always `true`

Example :&#x20;

```
{ 
    "blocks": [ 
        {
            "type": "input",
            "element": {
                "type": "plain_text_input",
                "action_id": "plain_text_input-action",
                "initial_value": "https://twake.app"
                "readonly": true,
                "copiable": true,
            },
        }
    ]
}
```

#### Progress bar

A Progess bar is **Block element** that display a progress bar.

How to use it:&#x20;

* Progress bar type:

```
export type BlockElementProgressBar = {
  type: "progress_bar";
  value: number; 
  title: string;
};
```

* type: always `"progress_bar"`
* value: the value of your progress between 0 and 100
* title: the title associate to your progress bar

Example :&#x20;

```
{ 
    "blocks": [ 
        {
            "type": "progress_bar",
            "value": 50,
            "title": "Chargement" 
            
        }
    ]
}
```

&#x20;


# Get started

Welcome to the internal documentation section. This chapter is for developers working in Twake team or wanting to participate in the project.

> If you are looking for the Developers API of Twake to make plugins, apps or connectors, go here : [Developers API](/developers-api/home)

## Before to start

* Fork our repo <https://github.com/TwakeApp/Twake> and checkout the **develop** branch

{% hint style="info" %}
You want to fix a translation issue? We use Weblate: <https://hosted.weblate.org/projects/linagora/twake-chat-web/>
{% endhint %}

## Run the backend (+ database)

1. Go to "twake/"
2. `docker-compose -f docker-compose.dev.mongo.yml up -d`
3. The backend will be running on port 3000

## Run the frontend

1. Go to "twake/frontend"
2. Run `yarn install` (better to use **yarn** than **npm**), our developers uses node 14 and 16, it should work with any upper version.
3. Prepare the **environment.ts** file like this: `cp environment/environment.ts.dist.dev environment/environment.ts`

```
export default {
  env_dev: true,
  front_root_url: 'http://localhost:3001',
  api_root_url: 'http://localhost:3000',
  websocket_url: 'ws://localhost:3000'
};
```

5\. Run `yarn start`

6\. It will propose to run on another port, say "yes" to run it on port 3001.

## Test and start develop

You should be able to go on <http://localhost:3001> just click on "create an account" and you'll be able to access Twake after a few steps.

-> Logs from backend can be accessed from `docker-compose -f docker-compose.dev.mongo.yml logs -f --tail 100`

-> Logs from frontend are visible in the output of `yarn start`

You can start writing code 🎉 ! It will reload the backend / frontend automatically each time you save.

{% hint style="danger" %}
Before to start implementing a new feature or bug fix, please find or create an issue on our repository (here <https://github.com/linagora/Twake/issues>) and put a comment to inform that you will work yourself on the issue. To avoid two same people doing the same work ;)
{% endhint %}

{% hint style="info" %}
If you have any issue, please come and join us on <https://community.twake.app/>
{% endhint %}

## Propose an improvement to be merged

For this you need to create a merge request on Github from your fork to our develop branch. Goes there: <https://github.com/linagora/Twake/compare> and click "compare across forks".

Tests will be ran automatically and should pass before to merge the code.

{% hint style="info" %}
We are hiring! Apply now on <https://job.linagora.com/en/join-us/>
{% endhint %}


# Twake Ecosystem Guidelines

Global guidelines for any new projects around Twake, Frontend and Backend guidelines are discussed here.

## Frontend guidelines

### Logo, UI and UX guidelines

#### Logos

{% file src="/files/-MX7LcLTtCPEFGMBWuxu" %}
Logo shape in SVG format
{% endfile %}

#### Colors and fonts

The fonts and colors to use are defined in the document bellow, scroll down for the hexadecimal codes of each color.

![](/files/-MX7KkFTYOi1PhGf9gpm)

Colors code extracted from the Twake theme <https://github.com/linagora/Twake/blob/main/twake/frontend/src/app/theme.less>

```css
// Circular Std = France
// Helvetica Neue = Vietnam
// TT Norms = Russia

// --- Twake fonts --- //
@main-font: 'Circular Std', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue',
  'Apple Color Emoji', Arial, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol';

// --- Twake colors --- //
@primary: #3840f7;
@primary-background: #3842f723;
@primary-hover: #3850f7;
@secondary: #0d0f38;

@black: #000000;
@black-alpha-50: #18295288;
@black-alpha-70: #18295255;
@grey-background: #f5f5f7;
@grey-light: #eeeeee;
@grey-dark: #6a6a6a;

@error: #ff5154;
@success: #04aa11;
@warning: #ff8607;
@white: #fff;
```

#### Icons and emojis (⚠️ not validated by UX designer yet)

Twake is currently using feather icons <https://feathericons.com/> and can fallback to Material Icons or FontAwesome.

Emojis on web must use the Apple emojis set preferably. On device, prefer to use the device emoji set.

### Frameworks and component system

#### Languages and frameworks

We recommend **TypeScript** and **VueJS** for any new projects around Twake. (But Twake itself currently uses ReactJS with typescript.)

#### **Components system**

We strongly recommend using Antd design system: <https://ant.design/> for 3 reasons:

1. We want to **differ from Material** UI that is too recognisable
2. Antd is very customisable, and **we provide a Twake theme here:** [**https://github.com/linagora/Twake/blob/main/twake/frontend/src/app/theme.less**](https://github.com/linagora/Twake/blob/main/twake/frontend/src/app/theme.less)
3. Antd contain more components than Material UI

### Libraries for common use cases

Feel free to add any library in this table.

| Use case        | used by            | prefered library                |
| --------------- | ------------------ | ------------------------------- |
| Infinite scroll | Twake message feed | <https://virtuoso.dev/> (React) |

## Backend guidelines

### Programmation languages

### Databases and middlewares


# Our stack

Here is the list of our middlewares and their usages.

{% hint style="info" %}
This document is not finished yet, don't hesitate to contribute on our Github.
{% endhint %}


# Backend and APIs

This page will document all the services implemented in the new NodeJS backend. For all the PHP services not yet migrated, please ask us directly on https\://community.twake.app/

## Twake Services

As a frontend developer / connector developer to read our APIs, or to understand our data models or just for fun, here is all the details over Twake backend services.

If document are empty, check out our Notion documentation: <https://www.notion.so/twake/Backend-documentation-e219323593d2401c9887d0e11b2a597b>

### General

{% content-ref url="/pages/-MZHkz7\_n5nLrZWRzeMG" %}
[(WIP) Authentication](/internal-documentation/backend-services/authentication)
{% endcontent-ref %}

### Services

{% content-ref url="/pages/-MUxUJOzhA9d3bWUYsn6" %}
[Users and workspaces](/internal-documentation/backend-services/users-and-workspaces-service)
{% endcontent-ref %}

{% content-ref url="/pages/-MZHlA65WU8NMHBQ9lHR" %}
[Applications](/internal-documentation/backend-services/applications)
{% endcontent-ref %}

{% content-ref url="/pages/-MUxTu5nj\_r9EfJ4MVAv" %}
[Channels and tabs](/internal-documentation/backend-services/channels-service)
{% endcontent-ref %}

{% content-ref url="/pages/-MUxU9OPGMKZiQf9ICBc" %}
[Messages](/internal-documentation/backend-services/messages-service)
{% endcontent-ref %}

{% content-ref url="/pages/-MUxU5Vloo9VxCpQUlU3" %}
[Notifications](/internal-documentation/backend-services/notifications-service)
{% endcontent-ref %}

## Get started to code in Twake

Want to edit Twake code ? Congratulation ! You participate in the development of a great product 😃

{% content-ref url="/pages/-MUxSI9XK6aws9J38-Oo" %}
[What is a service in Twake ?](/internal-documentation/backend-services/twake-service-development/start-working-into-a-service)
{% endcontent-ref %}

{% content-ref url="/pages/-MUxSOadonmSMwtWUE91" %}
[Create a new service](/internal-documentation/backend-services/twake-service-development/create-a-new-twake-service)
{% endcontent-ref %}

{% content-ref url="/pages/-MUxUh\_5pDsxFXCWC6v2" %}
[Platform/Technical services](/internal-documentation/backend-services/twake-service-development/platform)
{% endcontent-ref %}


# (WIP) Authentication

Routes to identify your user or your application.

{% hint style="info" %}
This page isn't ready yet :)
{% endhint %}


# Users and workspaces

How users and workspaces are managed in backend

Document still on notion: <https://www.notion.so/twake/Backend-documentation-e219323593d2401c9887d0e11b2a597b>


# Applications

How applications are managed in Twake backend

#### Description

**Applications** is everything related to connectors in Twake.

An application is described by an identity sheet containing this information:

* **Identity** (name, description, logo)
* **API** **preferences** (Twake→Connector events endpoint, API id and secret, and Connector→Twake allowed IPs)
* **Privileges and capabilities** (List of things the connector can read and can write)
* **Display information** (Where the connector is visible, button in chat, configuration in application list etc)

This is all we need to define a connector.

{% content-ref url="/pages/-MbjwbN76ZzXJowMYX5k" %}
[Database models](/internal-documentation/backend-services/applications/database-models)
{% endcontent-ref %}

{% content-ref url="/pages/-Mbjx1k-Kk4r0VqfKRqE" %}
[REST APIs](/internal-documentation/backend-services/applications/rest-apis)
{% endcontent-ref %}


# Database models

Application models for backend

**applications**\
Represent an application in the database

```javascript
{
  //PK
  "company_id": uuid;
  "id": uuid;

  "identity": ApplicationIdentity;
  "api": ApplicationApi;
  "access": ApplicationAccess;
  "display": ApplicationDisplay;
  "publication": ApplicationPublication;
  "stats": ApplicationStatistics;
}

type ApplicationIdentity = {
  name: string;
  iconUrl: string;
  description: string;
  website: string;
  categories: string[];
  compatibility: "twake"[];
};

type ApplicationPublication = {
  published: boolean;
};

type ApplicationStatistics = {
  createdAt: number;
  updatedAt: number;
  version: number;
};

type ApplicationApi = {
  hooksUrl: string;
  allowedIps: string;
  privateKey: string;
};

type ApplicationAccess = {
  privileges: string[];
  capabilities: string[];
  hooks: string[];
};

type ApplicationDisplay = {
  twake: {
    "version": 1,

		"files" : {
	    "preview": {
				"url": "", //Url to preview file (full screen or inline)
				"inline": true,
				"main_ext": ["docx", "xlsx"], //Main extensions app can read
				"other_ext": ["txt", "html"] //Secondary extensions app can read
			},
			"actions": [ //List of action that can apply on a file
				{
					"name": "string",
					"id": "string"
				}
			]
	  },

		//Chat plugin
	  "chat": {
	    "input": {
				"icon": "", //If defined replace original icon url of your app 
	      "type": "file" | "call" //To add in existing apps folder / default icon
	    },
	    "commands": [
				{
					"command": "mycommand", // my_app mycommand
	        "description": "fdsqfds"
				}
			],
			"actions": [ //List of action that can apply on a message
				{
					"name": "string",
					"id": "string"
				}
			]
	  },

		//Allow app to appear as a bot user in direct chat
    "direct": {
			"name": "My app Bot",
      "icon": "", //If defined replace original icon url of your app
    },

		//Display app as a standalone application in a tab
	  "tab": {
	    "url": ""
	  },

		//Display app as a standalone application on the left bar
	  "standalone": {
	    "url": ""
	  },

    //Define where the app can be configured from
	  "configuration": ["global", "channel"]
  };
};
```

**company\_application**\
Represent an application in a company

```javascript
{
	"company_id": uuid;
	"application_id": uuid;
	"id": uuid;
	
	"created_at": number;
	"created_by": string; //Will be the default delegated user when doing actions on Twake
}
```


# REST APIs

Rest API for application


# Channels and tabs

Channels are topics in Twake, users can subscribe to them, can make them private or can create them.

### Wording

**Member:** member in company without the tag "guest"\
**Guest:** company member with the tag "guest"\
**Channel member:** member in the channel\
**Channels types:**\
**→ Private:** Relative to a workspace but restricted to a defined group of members\
\&#xNAN;**→ Public:** Relative to a workspace anyone can find the channel and join\
\&#xNAN;**→ Direct:** Relative to a company, a discussion between a set of members but without defined topic

**To understand the routing** please read **"**&#x43;ollections based rest api:" in "Wording and formating"

TODO [Wording and formatting](https://www.notion.so/Wording-and-formatting-24ef27e094a042aea4899ac6a8039dee)

### What it does

Channel component is **not** responsible of the red badge counter (notification service is).

Channel component **is** responsible of keeping track of the message from where the user need to start reading but can be override by notification service if there is a mention to start reading from.

TODO [Some process details and constant s](https://www.notion.so/Some-process-details-and-constant-s-fb5b2d4974da490aa87bb87082af8454)

### Models an APIs

[Database models](/internal-documentation/backend-services/channels-service/database-models)

TODO [REST Api / Websockets Api](https://www.notion.so/REST-Api-Websockets-Api-458b153a6a6e46c2925dfc1db3859d3b)

TODO [In/Out microservice](https://www.notion.so/In-Out-microservice-e721e72e542244a69ca3a913e0b405ad)

TODO [Activity models](https://www.notion.so/Activity-models-0fa3acb0a13f41fd98bc98709908eedf)


# Database models

Database models of channel

## Channels

#### **channels**

The main channel table, this table should only be used when changing things on the channel (not frequently) so we don't add counters or last\_activity or anything like that.

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"workspace_id": "string" // ("uuid-v4" | "direct")
	"id": "uuid-v4",
	
	//Content
	"owner": "uuid-v4", //User-id of the channel owner (invisible but used on some access restriction-
	"icon": "String",
	"name": "String",
	"description": "String",
	"channel_group": "String",
	"visibility": "private" | "public" | "direct"
	"default": true | false, //The new members join by default this channel
	"archived": false | true,
	"archivation_date": 0, //Timestamp
}
```

#### **channel\_defaults**

Contain the default channels

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"workspace_id": "uuid-v4"

	"id": "uuid-v4" //Channel id
}
```

#### **channel\_counters**

We use a separated table to manage counters for this channel. Currently this is not used to do statistics but can be used to this goal in the future.

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"workspace_id": "string", // "uuid-v4" | "direct"
	"channel_id": "uuid-v4",
	"type": "members" | "guests" | "messages",

	"value": 0,
}
```

#### **channel\_last\_activity**

Store last channel activity for bold/not bold management

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"channel_id": "uuid-v4",

	"last_activity": 0, //Timestamp in seconds
}
```

#### **direct\_channel\_identifiers**

This table is used to find an existing discussion with a group of members. The "identifier" is generated from the group of members.

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"identifier": "string", // ordered CSV list of user ids
	"channel_id": "uuid-v4" //A way to find it in the channel table
}
```

## **Channels tabs**

#### **channel\_tabs**

Channels can have tabs that are connexion to other apps or different views.

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"workspace_id": "string", // "uuid-v4" | "direct"
	"channel_id": "uuid-v4",
	"tab_id": "uuid-v4",

	"name": "String", 
	"configuration": JSON,
	"application_id": "uuid-v4",
  "owner": "uuid-v4",
	"order": "string"
}
```

## **Channels members**

#### **channel\_members**

List of channels for an user

```javascript
{
	//Primary key
	"company_id": "uuid-v4", //Partition Key
	"workspace_id": "string", // "uuid-v4" | "direct", //Clustering key
	"user_id": "uuid-v4",
	"channel_id": "uuid-v4",

	"type": "member" | "guest" | "bot",

	"last_access": 0, //Timestamp in seconds
	"last_increment": 0, //Number
	"favorite": false | true, //Did the user add this channel to its favorites
	"notification_level": "all" | "none" | "group_mentions" | "user_mentions",
	"expiration": false | Timestamp, //Member expiration in channel (only for guests)
}
```

#### **channel\_members\_reversed**

List of users in channel

**Not implemented:** We need to ensure this replication regularly (on each user open channel) ?

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"workspace_id": "string", // "uuid-v4" | "direct",
	"channel_id": "uuid-v4",
	"type": "guest" | "bot" | "member",
	"user_id": "uuid-v4",
}
```


# Messages

Message api

Document still on Notion: <https://www.notion.so/twake/Backend-documentation-e219323593d2401c9887d0e11b2a597b>


# Database models

Message database models


# Files

File on Twake

## description

**Files** is everything related to file upload in Twake after the migration to Node.js. Note that the Twake Drive isn't part of this migration because it will be replaced by Linshare.

Twake Files upload support chunk upload and file encryption.

## Wording

**File:** document to upload, no constraint on the type of document (image, text, pdf ..)

**Chunk:** Large file are split in multiple chunk for the upload process

## Encryption

Files and Storage services in Twake feature encryption at rest in **aes-256-cbc**.

Each file is encrypted with two layers:

* A file encryption key and iv stored in database and different for each file.
* A global encryption key and iv used in addition to the previous one and equal for each file.

## Models and APIs

{% content-ref url="/pages/-M\_uLppeYs73\_vtbywx-" %}
[Database models](/internal-documentation/backend-services/files-service/database-models)
{% endcontent-ref %}

{% content-ref url="/pages/-M\_uLppf9JjeUo4WPLht" %}
[REST APIs](/internal-documentation/backend-services/files-service/rest-apis)
{% endcontent-ref %}

## Miscellaneous

{% content-ref url="/pages/-M\_uMzPEEZVa033pMptd" %}
[Resumable.js](/internal-documentation/backend-services/files-service/resumablejs)
{% endcontent-ref %}


# Database models

File database models

* **files** The main file object in database

  ```javascript
    
  javascript
    {
      
      //Primary key: [["company_id"], "id"]
      "company_id": "uuid-v4",
      "id": "uuid-v4",
      "user_id": "string",
      "application_id": "string",
      "updated_at": "number", 
      "created_at":"number
        
      "upload_data": (json){
        "size": number, //Total file size
        "chunks": number, //Number of chunks
       }
      "metadata": (json){
        "name": "string", //File name
        "mime": "type/subtype",
      }
      "thumbnails": (json) {  //Url to thumbnail (or set it to undefined if no relevant)
        "index": "string",
        "id": "uuid-v4",
        "type": "string",
        "size": "number,
        "width": number, //Thumbnail width (for images only)
        "height": number, //Thumbnail height (for images only)
      }
  }

  ```


# REST APIs

Rest api for files

**Prefix**: /internal/services/files/v1/

## General

## Get file metadata (check user belongs to comapny)

<mark style="color:blue;">`GET`</mark> `/internal/services/files/v1/:company_id/files/:file_id`

This route is called to get the metadata related to the `file_id` mentioned in the URL

{% tabs %}
{% tab title="200 " %}

```
Response:
  {
    "resource": {
        "company_id": "uuid-v4",
        "id": "uuid-v4",
        "application_id": "string",
        "created_at": "number",
        "encryption_key": "",
        "metadata": {
            "name": "string",
            "mime": "string"
        },
        "thumbnails": [
            {
                "index": number,
                "id": "string,
                "size": number,
                "type": "string",
                "width": number,
                "height": number
            }
        ],
        "updated_at": number,
        "upload_data": {
            "size": number,
            "chunks": number
        },
        "user_id": "uuid-v4"
    }
  }
```

{% endtab %}
{% endtabs %}

## Download a file&#x20;

<mark style="color:blue;">`GET`</mark> `/internal/services/files/v1/companies/:company_id/files/:file_id/download`

This route is called to download the file related to the `file_id` mentionned in the URL

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

## Download thumbnails

<mark style="color:blue;">`GET`</mark> `/internal/services/files/v1/companies/:company_id/files/:file_id/thumbnails/:id`

This route is called to download the thumbnail related to the `file_id` mentionned in the URL

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

## Delete a file

<mark style="color:red;">`DELETE`</mark> `/internal/services/files/v1/companies/:company_id/files/:file_id`

This route is called to delete the file related to the `file_id` mentionned in the URL

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

## Classic upload

To upload a single file, you must call this route and put the file binary data into a "file" multipart section.

## Upload a file&#x20;

<mark style="color:green;">`POST`</mark> `/internal/services/files/v1/companies/:company_id/files?thumbnail_sync=1`

This route is called to upload a file when chunk upload is not necessary.\
Thumbnail\_sync: when set then backend will wait up to 10 seconds for preview to be generated before reply.

#### Query Parameters

| Name            | Type    | Description |
| --------------- | ------- | ----------- |
| thumbnail\_sync | boolean |             |

#### Request Body

| Name | Type   | Description                     |
| ---- | ------ | ------------------------------- |
| File | object | The file which will be uploaded |

{% tabs %}
{% tab title="200 " %}

```
response : 
  {
    "resource": {
        "company_id": "uuid-v4",
        "metadata": {
            "name": "string",
            "mime": "string"
        },
        "thumbnails": [],
        "application_id": string,
        "upload_data": {
            "size": number,
            "chunks": number
        },
        "id": "uuid-v4",
        "updated_at": number,
        "created_at": number
  }
}
  
```

{% endtab %}
{% endtabs %}

## Upload with chunk

To upload a file in multiple chunk you must first initial the file itself, and then upload into the file.

The file initialization and following upload calls takes this parameters as **a query string**:

* **filename**: string, file name
* **type**: string, mime type for the file
* **total\_chunks**: number, total number of chunk to be uploaded
* **total\_Size**: number, sum of every chunk size (total file size)
* **chunk\_number**: number, current chunk uploaded, set it to undefined during file creation process.
* **thumbnail\_sync:** when set then backend will wait up to 10 seconds for the preview to be generated before to reply.

## Upload a file with chunk

<mark style="color:green;">`POST`</mark> `/internal/services/files/v1/companies/:company_id/files/?filename...`

This route should first be called without data to initialise the entity for multi-chunk, then chunks must be sent on other route below

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

## Overwrite a file&#x20;

<mark style="color:green;">`POST`</mark> `/internal/services/files/v1/companies/:company_id/files/:file_id/?totalChunks...`

Overwrite a file \
(check user belongs to company)\
User can call this if the file was not already uploaded. If file already exist only apps can do this (users cannot directly overwrite a file).

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}


# Resumable.js

How we use resumable on Twake

**Link to the official documentation:** [http://resumablejs.com](http://resumablejs.com/)

## What is Resumable.js

{% hint style="info" %}
Using Resumable is not mandatory to upload a file to Twake. You can do it manually, see REST APIs page.
{% endhint %}

It’s a JavaScript library providing multiple simultaneous, stable and resumable uploads via the HTML5 File API.

The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each files into small chunks; whenever the upload of a chunk fails, uploading is retried until the procedure completes. This allows uploads to automatically resume uploading after a network connection is lost either locally or to the server. Additionally, it allows for users to pause, resume and even recover uploads without losing state.

Resumable.js does not have any external dependencies other the `HTML5 File API`. This is relied on for the ability to chunk files into smaller pieces. Currently, this means that support is limited to Firefox 4+ and Chrome 11+.

## What Resumable.js does

In Frontend side resumable split the file in multiple small chunks. For the upload process resumable ask the server with an HTTP GET request to know if the chunk requested is saved on the server side. If yes, resumable ask for the next chunk. If not, resumable send an HTTP POST request with the chunk to the server.

With POST request from resumable, there is multiple information fields contains for exemple the number of chunk, the chunk size, the total size of the file, an unique identifier for the file, and the name of the file.


# Notifications

Notifications on Twake


# Database models

Notification database model

#### **channel\_members\_notification\_preferences**

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"channel_id": "uuid-v4",
	"user_id": "uuid-v4",

	"preferences": "all" | "mentions" | "me" | "none",
	"last_read": 16000000, //Timestamp in seconds	
}
```

#### **channel\_thread\_users**

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"channel_id": "uuid-v4",
	"thread_id": "uuid-v4",

	"user_id": "uuid-v4"
}
```

#### **user\_notifications\_badges (this is not a counter like scylladb counters here, just classic table)**

```javascript
{
	//Primary key
	"company_id": "uuid-v4", (partition key)
	"user_id": "uuid-v4", (clustering key)
	"workspace_id": "uuid-v4", (clustering key)
	"channel_id": "uuid-v4", (clustering key)
	"thread_id": "uuid-v4", (clustering key)
}
```

#### **user\_notifications\_general\_preferences**

```javascript
{
	//Primary key
	"company_id": "uuid-v4",
	"user_id": "uuid-v4",
	"workspace_id": "uuid-v4",

	"preferences": PreferencesObject
}
```


# Twake service development

Get started with Twake service development

{% content-ref url="/pages/-MUxSI9XK6aws9J38-Oo" %}
[What is a service in Twake ?](/internal-documentation/backend-services/twake-service-development/start-working-into-a-service)
{% endcontent-ref %}

{% content-ref url="/pages/-MUxSOadonmSMwtWUE91" %}
[Create a new service](/internal-documentation/backend-services/twake-service-development/create-a-new-twake-service)
{% endcontent-ref %}

{% content-ref url="/pages/-MUxUh\_5pDsxFXCWC6v2" %}
[Platform/Technical services](/internal-documentation/backend-services/twake-service-development/platform)
{% endcontent-ref %}


# What is a service in Twake ?

You want to add new routes in an existing service, for instance add a feature to our channel service ? You are in the right place !

The backend is developed using a **software component approach** in order to compose and adapt the platform based on needs and constraints. The current section describes this approach, and how to extend it by creating new components.

The platform has the following properties:

* A platform is composed of multiple components
* A component has an unique name in the platform
* A component can provide a `service`
* A component can consume `services` from other components
* A component has a lifecycle composed of several states: `ready`, `initialized`, `started`, `stopped`
* A component lifecycle changes when a lifecycle event is triggered by the platform: `init`, `start`, `stop`
* By creating links between components (service producers and consumers), components lifecycles **are also linked together**: A component going from `ready` to `initialized` will wait for all its dependencies to be in `initialized` state. This is automatically handled by the platform.

The platform currently have some limitations:

* Components can not have cyclic dependencies: if `component X` requires a component which requires `component X` directly or in one of its dependencies, the platform will not start
* Components can only have local dependencies.

## Discover what is in a service

To unfold the internal ways of services in Twake, we will follow a simple request journey into our framework.

1. The requests starts from Twake Frontend or Postman for instance,
2. it then goes to a controller which validate the request parameters and extract them for the services,
3. the services uses the given parameters to get/set entities in database and returns a proper reply.

### /web/controllers : where everything starts

This is where you declare the routing you want to use.

### /services : where the magic happen

This is where you work for real, calling databases, sending websockets events, using tasks pushers etc.

### /entities : where we keep the data

If you store data, you must define its data model and how it is stored in our database middleware.

{% hint style="danger" %}
This document is not finished, you can contribute to it on our Github.
{% endhint %}


# Create a new service

If you are here, you probably have a very great idea for Twake, like adding a brand new feature into Twake, maybe a coffee maker service ? ☕️

{% hint style="info" %}
Please ensure you read the [Start working into a service](/internal-documentation/backend-services/twake-service-development/start-working-into-a-service) before
{% endhint %}

To create a new component, a new folder must be created under the `src/services` one and an `index.ts` file must export the a class. This class will be instantiated by the platform and will be linked to the required services automatically.

In order to illustrate how to create a component, let's create a fake Notification service.

1. Create the folder `src/services/notification`
2. Create an `index.ts` file which exports a `NotificationService` class

```javascript
// File src/services/notification/index.ts
import { TwakeService } from "../../core/platform/framework";
import NotificationServiceAPI from "./api.ts";

export default class NotificationService extends TwakeService<NotificationServiceAPI> {
  version = "1";
  name = "notification";
  service: NotificationServiceAPI;

  api(): NotificationServiceAPI {
    return this.service;
  }
}
```

1. Our `NotificationService` class extends the generic `TwakeService` class and we defined the `NotificationServiceAPI` as its generic type parameter. It means that in the platform, the other components will be able to retrieve the component from its name and then consume the API defined in the `NotificationServiceAPI` interface and exposed by the `api` method.

   We need to create this `NotificationServiceAPI` interface which must extend the `TwakeServiceProvider` from the platform like:

```javascript
// File src/services/notification/api.ts
import { TwakeServiceProvider } from "../../core/platform/framework/api";

export default interface NotificationServiceAPI extends TwakeServiceProvider {

  /**
   * Send a message to a list of recipients
   */
  send(message: string, recipients: string[]): Promise<string>;
}
```

1. Now that the interfaces are defined, we need to create the `NotificationServiceAPI` implementation (this is a dummy implementation which does nothing but illustrates the process):

```javascript
// File src/services/notification/services/api.ts
import NotificationServiceAPI from "../api";

export class NotificationServiceImpl implements NotificationServiceAPI {
  version = "1";

  async send(message: string, recipients: string[]): Promise<string> {
    return Promise.resolve(`${message} sent`);
  }
}
```

1. `NotificationServiceImpl` now needs to be instanciated from the `NotificationService` class since this is where we choose to keep its reference and expose it. There are several places which can be used to instanciate it, in the constructor itself, or in one of the `TwakeService` lifecycle hooks. The `TwakeService` abstract class has several lifecycle hooks which can be extended by the service implementation for customization pusposes:
2. `public async doInit(): Promise<this>;` Customize the `init` step of the component. This is generally the place where services are instanciated. From this step, you can retrieve services consumed by the current component which have been already initialized by the platform.
3. `public async doStart(): Promise<this>;` Customize the `start` step of the component. You have access to all other services which are already started.

```javascript
// File src/services/notification/index.ts
import { TwakeService } from "../../core/platform/framework";
import NotificationServiceAPI from "./api.ts";
import NotificationServiceImpl from "./services/api.ts";

export default class NotificationService extends TwakeService<NotificationServiceAPI> {
  version = "1";
  name = "notification";
  service: NotificationServiceAPI;

  api(): NotificationServiceAPI {
    return this.service;
  }

  public async doInit(): Promise<this> {
    this.service = new NotificationServiceImpl();

    return this;
  }
}
```

1. Now that the service is fully created, we can consume it from any other service in the platform. To do this, we rely on Typescript decorators to define the links between components. For example, let's say that the a `MessageService` needs to call the `NotificationServiceAPI`, we can create the link with the help of the `@Consumes` decorator and get a reference to the `NotificationServiceAPI` by calling the `getProvider` on the component context like:

```javascript
import { TwakeService, Consumes } from "../../core/platform/framework";
import MessageServiceAPI from "./providapier";
import NotificationServiceAPI from "../notification/api";

@Consumes(["notification"])
export default class MessageService extends TwakeService<MessageServiceAPI> {

  public async doInit(): Promise<this> {
    const notificationService = this.context.getProvider<NotificationServiceAPI>("notification");

    // You can not call anything defined in the NotificationServiceAPI interface from here or from inner services by passing down the reference to notificationService.
  }
}
```

**Configuration**

The platform and services configuration is defined in the `config/default.json` file. It uses [node-config](https://github.com/lorenwest/node-config) under the hood and to configuration file inheritence is supported in the platform.

The list of services to start is defined in the `services` array like:

```javascript
{
  "services": ["auth", "user", "channels", "webserver", "websocket", "database", "realtime"]
}
```

Then each service can have its own configuration block which is accessible from its service name i.e. `websocket` service configuration is defined in the `websocket` element like:

```javascript
{
  "services": ["auth", "user", "channels", "webserver", "websocket", "orm"],
  "websocket": {
    "path": "/socket",
    "adapters": {
      "types": [],
      "redis": {
        "host": "redis",
        "port": 6379
      }
    }
  }
}
```

On the component class side, the configuration object is directly accessible from the `configuration` property like:

```javascript
export default class WebSocket extends TwakeService<WebSocketAPI> {
  async doInit(): Promise<this> {
    // get the "path" value, defaults to "/socket" if not defined
    const path = this.configuration.get < string > ("path", "/socket");

    // The "get" method is generic and can accept custom types like
    const adapters = this.configuration.get < AdaptersConfiguration > "adapters";
  }
}

interface AdaptersConfiguration {
  types: Array<string>;
  redis: SocketIORedis.SocketIORedisOptions;
}
```

{% hint style="info" %}
After creating a new service, you can add controllers, business services and entities, go back to the [What is a service section](/internal-documentation/backend-services/twake-service-development/start-working-into-a-service)
{% endhint %}

## Create a new technical service

Now you are bringing things a step further, you are going to add new core services in Twake, like for instance a new database connector or encryption system.

Creating a new core service is as easy as creating a functional service. But it must be in `src/core/platform/services` .

You can read the [complete list of existing technical services here](/internal-documentation/backend-services/twake-service-development/platform) .


# Platform/Technical services

List of core shared components in Twake backend, available in src/core/platform/services

## **Database Technical Service**

Twake uses a custom ORM to work with both MongoDB and CassandraDB/ScyllaDB.

{% content-ref url="/pages/-Me5DPy\_6lRbhSLIu2A4" %}
[Database ORM platform service](/internal-documentation/backend-services/twake-service-development/platform/database-orm-platform-service)
{% endcontent-ref %}

## **Realtime Technical Service**

The framework provides simple way to create CRUD Services which will notify clients using Websockets by following some conventions:

1. The service must implement the `CRUDService` generic interface
2. In order to push notification to clients, Typescript decorators must be added on methods (Create, Update, Delete methods)

For example, let's say that we want to implement a CRUD Service for `messages`:

```javascript
import {
  CRUDService,
  CreateResult,
  DeleteResult,
  UpdateResult,
  EntityId,
} from "@/core/platform/framework/api/crud-service";

class Message {
  text: string;
  createdAt: Date;
  author: string;
}

class MessageService implements CRUDService<Message> {
  async create(item: Message): Promise<CreateResult<Message>> {
    // save the message then return a CreateResult instance
  }

  async get(id: EntityId): Promise<Message> {
    // get the message from its ID and return it
  }

  async update(id: EntityId, item: Message): Promise<UpdateResult<Message>> {
    // update the message with the given id, patch it with `item` values
    // then return an instance of UpdateResult
  }

  async delete(id: EntityId): Promise<DeleteResult<Message>> {
    // delete the message from its id then return a DeleteResult instance
  }

  async list(): Promise<Message[]> {
    // get a list of messages
  }
}
```

By implementing the CRUD service following the API, we can now add realtime message to clients connected to Websocket on a selected collection of CRUD operations. For example, if we want to just notify when a `message` is created, we just have to add the right decorator on the `create` method like:

```javascript
import { RealtimeCreated } from "@/core/platform/framework/decorators";

// top and bottom code removed for clarity
class MessageService implements CRUDService<Message> {

  @RealtimeCreated<Message>({
    room: "/messages",
    path: message => `/messages/${message.id}`
  })
  async create(item: Message): Promise<CreateResult<Message>> {
    // save the message then return a CreateResult instance
    const created: Message = new Message(/* */);

    return new CreateResult<Message>("message", created)
  }
  // ...
}
```

The `RealtimeCreated` decorator will intercept the `create` call and will publish an event in an internal event bus with the creation result, the input data and the `"/messages"` path. On the other side of the event bus, an event listener will be in charge of delivering the event to the right Websocket clients as described next.

The Realtime\* decorators to add on methods to intercept calls and publish data are all following the same API. A decorator takes two parameters as input:

* First one is the room name to publish the notification to (`/messages` in the example above)
* Second one is the full path of the resource linked to the action (`message => /messages/${message.id}` in the example above)

Both parameters can take a string or an arrow function as parameter. If arrow function is used, the input parameter will be the result element. By doing this, the paths can be generated dynamically at runtime.

## **Websocket Technical Service**

Services annotated as described above automatically publish events to WebSockets. Under the hood, it uses Socket.IO rooms to send events to the right clients.

**Authentication**

* Client have to provide a valid JWT token to be able to connect and be authenticated by providing it as string in the `authenticate` event like `{ token: "the jwt token value" }`
* If the JWT token is valid, the client will receive a `authenticated` event
* If the JWT token is not valid or empty, the client will receive a `unauthorized` event

**Example**

```javascript
const io = require("socket.io-client");

// Get a JWT token from the Twake API first
const token =
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOjEsImlhdCI6MTYwMzE5ODkzMn0.NvQoV9KeWuTNzRvzqbJ5uZCQ8Nmi2rCYQzcKk-WsJJ8";
const socket = io.connect("http://localhost:3000", { path: "/socket" });

socket.on("connect", () => {
  socket
    .emit("authenticate", { token })
    .on("authenticated", () => {
      console.log("User Authenticated");
    })
    .on("unauthorized", err => {
      console.log("User is not authorized", err);
    });
});

socket.on("disconnected", () => console.log("Disconnected"));
```

**Joining rooms**

CRUD operations on resources are pushing events in Socket.io rooms. In order to receive events, clients must subscribe to rooms by sending an `realtime:join` on an authenticated socket with the name of the room to join and with a valid JWT token like `{ name: "room name", token: "the jwt token for this room" }`: Users can not subscribe to arbitratry rooms, they have to be authorized to.

As a result, the client will receive events:

* `realtime:join:success` when join is succesful with data containing the name of the linked room like `{ name: "room" }`.
* `realtime:join:error` when join failed with data containing the name of the linked room and the error details like `{ name: "room", error: "some error message" }`.

**Example**

```javascript
const io = require("socket.io-client");

const token =
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOjEsImlhdCI6MTYwMzE5ODkzMn0.NvQoV9KeWuTNzRvzqbJ5uZCQ8Nmi2rCYQzcKk-WsJJ8";
const socket = io.connect("http://localhost:3000", { path: "/socket" });

socket.on("connect", () => {
  socket
    .emit("authenticate", { token })
    .on("authenticated", () => {
      // join the /channels room
      socket.emit("realtime:join", { name: "/channels", token: "twake" });
      socket.on("realtime:join:error", message => {
        // will fire when join does not provide a valid token
        console.log("Error on join", message);
      });

      // will be fired on each successful join.
      // As event based, this event is not linked only to the join above
      // but to all joins. So you have to dig into the message to know which one
      // is successful.
      socket.on("realtime:join:success", message => {
        console.log("Successfully joined room", message.name);
      });
    })
    .on("unauthorized", err => {
      console.log("Unauthorized", err);
    });
});

socket.on("disconnected", () => console.log("Disconnected"));
```

**Leaving rooms**

The client can leave the room by emitting a `realtime:leave` event with the name of the room given as `{ name: "the room to leave" }`.

As a result, the client will receive events:

* `realtime:leave:success` when leave is succesful with data containing the name of the linked room like `{ name: "room" }`.
* `realtime:leave:error` when leave failed with data containing the name of the linked room and the error details like `{ name: "room", error: "some error message" }`.

Note: Asking to leave a room which has not been joined will not fire any error.

**Example**

```javascript
socket.on("connect", () => {
  socket.emit("authenticate", { token }).on("authenticated", () => {
    // leave the "/channels" room
    socket.emit("realtime:leave", { name: "/channels" });

    socket.on("realtime:leave:error", message => {
      // will fire when join does not provide a valid token
      console.log("Error on leave", message);
    });

    // will be fired on each successful leave.
    // As event based, this event is not linked only to the leave above
    // but to all leaves. So you have to dig into the message to know which one
    // is successful.
    socket.on("realtime:leave:success", message => {
      console.log("Successfully left room", message.name);
    });
  });
});
```

**Subscribe to resource events**

Once the given room has been joined, the client will receive `realtime:resource` events with the resource linked to the event as data:

```javascript
{
  "action": "created",
  "room": "/channels",
  "type": "channel",
  "path": "/channels/5f905327e3e1626399aaad79",
  "resource": {
    "name": "My channel",
    "id": "5f905327e3e1626399aaad79"
  }
```

**Example:**

```javascript
socket.on("connect", () => {
  socket
    .emit("authenticate", { token })
    .on("authenticated", () => {
      // join the "/channels" room
      socket.emit("realtime:join", { name: "/channels", token: "twake" });

      // will only occur when an action occured on a resource
      // and if and only if the client joined the room
      // in which the resource is linked
      socket.on("realtime:resource", event => {
        console.log("Resource has been ${event.action}", event.resource);
      });
    })
    .on("unauthorized", err => {
      console.log("Unauthorized", err);
    });
});
```


# Database ORM platform service

## How to use it ?

A. Create an entity and put it anywhere in the code

```typescript
import { Entity, Column } from "../../../core/platform/services/database/services/orm/decorators";

@Entity("my_entity", {
  primaryKey: [["company_id"], "id"], //Primary key, see Cassandra documentation for more details
  type: "my_entity",
})
export class MyEntity {
  @Type(() => String)
  @Column("company_id", "uuid", { generator: "uuid" })
  company_id: string;

  @Column("workspace_id", { generator: "uuid" })
  id: string;

  @Column("text", "encoded")
  text: string;
}
```

B. Create a repository to manage entities

```typescript
const database: DatabaseServiceAPI = ...;
const repository = await database.getRepository("my_entity", MyEntity);

const newEntity = new MyEntity();
newEntity.company_id = "";
newEntity.text = "";
await repository.save(newEntity);

const entities = await repository.find({company_id: "", id: ""});
const entity = await repository.findOne({company_id: "", id: ""});

await repository.remove({company_id: "", id: ""});
```

## FAQ

#### I set a column to a type but I get an other type on code. Why for two identical definitions it created fields of different types?

It depends on what database you use (mongo or scylladb) for development. Here is the process for each:&#x20;

Scylla:

* on startup it creates the tables with the requested types, in this case twake\_boolean => tinyint on scylla side
* on save entity it will convert the node type (boolean) to the good cql request: "{bool: false}" => "SET bool = 0", it happens in the transformValueToDbString method
* on find entity it will convert the database raw value (a tinyint) to the nodejs type (boolean): 1 => true, 0 => false.

Mongo:

* on startup it does nothing (mongo don't need to initialise columns
* on save entity it will create a document, it means in mongo we just store json for each entity, there is no really a column concept.
* on find entity we just get back the saved json and map it to the entity in node.

  Even if mongo just store json directly from mongo, we sometime do some changes to the data before to save in mongo, it will also be in the typeTransforms.ts file.

So what could have happened in you case ?

* (1) if you use mongodb and we did not enforce the type before to save to mongo, then maybe you used a string instead of a boolean at some point in time while working and mongo just saved it as it was (without checking the requested type on entity)
* (2) other possibility is that we incorrectly get the information from the database on the typeTransforms.ts file, from cassandra for instance I think we don't convert tinyint back to clean boolean, so you could get 0 and 1 instead of false and true. And maybe instead of 0 and 1 sometime undefined values can convert to ''.
* To fix all this just enforce the types in typeTransforms.ts for the twake\_boolean type.


# Web, desktop and mobile

Get started with Twake frontend.


# Table

Create a table

#### Usage

```jsx
import Table from 'components/Table/Table.js';
```

```jsx
<Table /> 
```

####

#### Props

| **name**                                     | **Description**                                                                                                                                                  | **Type**  | **Default** |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------- |
| **columns**                                  |                                                                                                                                                                  | ReactNode | null        |
| **onAdd**                                    | If defined a add button appear and clicking on it call this callback.                                                                                            | Function  | null        |
| **addText**                                  | Button content                                                                                                                                                   | ReactNode | null        |
| **onSearch**                                 | Called when search bar is changed. Return a Promise that resolve with a list of users. Argument is (query: string, maxResult: number)=>{return new Promise(...)} | Function  | null        |
| **onRequestNextPage, onRequestPreviousPage** | (token: string, maxResult: number)=>{return new Promise(...)}                                                                                                    | Function  | null        |
| **rowKey**                                   | (row)=>return row\.id                                                                                                                                            | Function  | null        |

**Functions (callable on the component from parent)**

| **name**                  | **Description**                             | **Type** |
| ------------------------- | ------------------------------------------- | -------- |
| **newElements(elements)** | Add new rows at the beginning of the table. | Function |

**Internal implementation**

* If no data in table, set loading to true, call onRequestNextPage without offset token.
* If next page button is clicked, get last row token, set Table to loading, and requestNextPage.
* When recieve result in requestNextPage, set loading to false and update data.
* Table must memorize already loaded pages to be faster
* Table must detect when request a page and no result is returned in this case disable corresponding button.


# ObjectModal

A beautiful, centered, medium modal with all you need to structure its content.

#### Usage

```jsx
import ObjectModal from 'components/ObjectModal/ObjectModal.js';
```

```jsx
<ObjectModal
  title={
    <ObjectModalTitle>
      My awesome title !
    </ObjectModalTitle>
  }
  onClose={() => myComponent.closeSomething()}
  disabled
  footer={
    <Button>
      Definitly not usefull
    </Button>
  }
>
  <FirstChild />
  <span>Some text</span>
</ObjectModal>
```

####

#### Props

| **name**     | **Description**                                                | **Type**  | **Default** |
| ------------ | -------------------------------------------------------------- | --------- | ----------- |
| **disabled** | Disable scrollbar X axis (need to rename for a better clarity) | Boolean   | false       |
| **footer**   | Define a footer component                                      | ReactNode | null        |
| **onClose**  | Add close icon in the component, waiting for a function        | Function  | null        |
| **title**    | *Define a title component*                                     | ReactNode | null        |

####

#### Preview

<div align="center"><img src="/files/-MCHzaGTFRBERTnq-no4" alt="Modal example with task editor"></div>


# ObjectModalTitle

Main title for the ObjectModal component.

#### Usage

```jsx
import ObjectModalTitle from 'components/ObjectModal/ObjectModal.js';
```

```jsx
  <ObjectModalTitle className="my-awesome-class" style={{ marginBottom: 0 }}>
    My awesome title !
  </ObjectModalTitle>
```

####

#### Props

| **name**      | **Description**                      | **Type** | **Default** |
| ------------- | ------------------------------------ | -------- | ----------- |
| **className** | Use predefined classes for the title | string   | null        |
| **style**     | Define a custom style for the title  | object   | null        |

####

#### Preview

![](/files/-MCSSo9QTH9kfiqoIQep)


# ObjectModalSeparator

Separate your component sections with a simple line.

#### Usage

```jsx
import ObjectModalSeparator from 'components/ObjectModal/ObjectModal.js';
```

```jsx
<ObjectModalSeparator />
```

####

#### Props

| Name |         **Description**        | **Type** | **Default** |
| :--: | :----------------------------: | :------: | :---------: |
|      | no property for this component |          |             |

####

#### Preview

![ObjectModalSeparator example in the ChannelWorkspaceEditor](/files/-MCSIxFKvEJMCYQIVfCI)


# ObjectModalSectionTitle

Section title, perfect after a separator.

#### Usage

```jsx
import ObjectModalFormTitle from 'components/ObjectModal/ObjectModal.js';
```

```jsx
  <ObjectModalFormTitle
    name={'Awesome Title!'}
  />
```

####

#### Props

| **name**      | **Description**                      | **Type** | **Default** |
| ------------- | ------------------------------------ | -------- | ----------- |
| **className** | Use predefined classes for the title | string   | null        |
| **icon**      | Define icon for the title            | string   | null        |
| **name**      | Define the title name                | string   | null        |
| **style**     | Define a custom style for the title  | object   | null        |

####

#### Preview

![](/files/-MCSUsn2UhD8HNkC0mOT)


# ObjectModalFormTitle

Icon with sub heading for ObjectModal component. Perfect before a form field.

#### Usage

```jsx
import ObjectModalFormTitle from 'components/ObjectModal/ObjectModal.js';
```

```jsx
  <ObjectModalFormTitle
    name={'Awesome Title!'}
    icon="star"
  />
```

####

#### Props

| **name**      | **Description**                      | **Type** | **Default** |
| ------------- | ------------------------------------ | -------- | ----------- |
| **className** | Use predefined classes for the title | string   | null        |
| **icon**      | Define icon for the title            | string   | null        |
| **name**      | Define the title name                | string   | null        |
| **style**     | Define a custom style for the title  | object   | null        |


# UserListManager

Not documented yet?


# MediumPopupManager

Not documented yet.


# MenuManager

Not documented yet.


# Translation

Want to translate Twake ?

## Translation process

Twake is built for everyone. That means we support languages that user want. If we do not already  support your language, or you find a mistake in the translation, you have a simply way to do that.

Send us a mail to this address saying that you want to help us on the translation : <sales@twake.app>. After, you'll have to follow the bellow steps.

## Join weblate server

At Twake, we use [weblate](https://hosted.weblate.org/), a tool that allow you to translate Twake without working on the code. After you sent an email, we'll invite you to the project. Inside the Twake project you can find  [twake-web-frontend](https://hosted.weblate.org/projects/twake/twake-web-frontend/) that contains all translation for the web version.

## Start to translate

After choosing the language you want to contribute, you can click on categories that need some work (like `Not translated strings` . Now you can start to translate : you have to fulfill the input You can see `Other languages` tab to give you more context about the string. After, just click on save and it is done !

![](/files/-Meiv4qDnjs9b5Dcub6k)

## Thank you!&#x20;

Twake is build by and for the community. Your commitment is highly appreciate! In reward, we can offer you a year on our SaaS version. You just have to contact us through this email : <sales@twake.app>


