Decentralized, P2P Chat in 100 lines of code
In this tutorial we going to build a P2P, decentralized, SQL backed, server and backend-less chat site on ZeroNet network.
(You can also reach this article on ZeroBlog)
Creating new site
- Click on ⋮ > “Create new, empty site” menu item on the site ZeroHello.
- You will be redirected to a completely new site that is only modifiable by you.
- Drag the topright “0” button to left to show the sidebar, change the site title to “My ZeroChat tutorial”, then press “Save site settings”.
The chat site’s HTML code
- Open “data/[yoursiteaddress]/index.html” file in you favorite editor.
- Change the <body> tag’s content to:
The js/ZeroFrame.js file (automatically bundled if you create your site using ZeroHello) contains the ZeroFrame class that allows us to communicate with the websocket based ZeroFrame API.
First ZeroFrame API call
- Initialize our application in a<script> block:
If we reload the page we should see a “Ready to call ZeroFrame API!” message.
- To make the “Select User” button display the user select dialog add the following function to the ZeroChat class:
Display user’s current ZeroID account
When something changed that is affects the site (new content arrived, user changed, etc.) a websocket event will be pushed to your browser.
(the format is same as you query siteInfo command)
- To handle this event add this to our class:
This code will real-time update the currently selected user name.
- To also display the currently selected username on page load add this to “onOpenWebsocket” function:
Now our application should always display the currently selected user correctly. (Even if its modified from other browser window)
Setting user content permissions
To allow other users to post on our site we have to define the rules of the third-party content.
Create a data/users/content.json file in your site’s directory:
{
"files": {},
"ignore": ".*",
"modified": 0.0,
"signs": {},
"user_contents": {
"cert_signers": {
"zeroid.bit": [ "1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz" ]
},
"permission_rules": {
".*": {
"files_allowed": "data.json",
"max_size": 10000
},
"bitmsg/.*@zeroid.bit": { "max_size": 15000 }
},
"permissions": {
"bad@zeroid.bit": false,
"nofish@zeroid.bit": { "max_size": 100000 }
}
}
}
"ignore": ".*",
: The files in this directory will be signed by the users and not by the site’s owner.cert_signers
: We accept *@zeroid.bit users and they have to come with a cert that is has to signed by1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz
address.permission_rules
: We give 10kbytes of space to every user (15kb if registered using bitmessage)permissions
: Per-user permissions: ban “bad@zeroid.bit” user and allow 100k storage to “nofish@zeroid.bit” user. ( it's me :) )
After we saved this file we also have to modify our site’s root content.json to ignore everything in this directory on signing and load the file containing the rules:
...
"ignore": "data/.*",
"includes": {
"data/users/content.json": {
"signers": [],
"signers_required": 1
}
},
...
Note: You can give moderation permissions to other users by adding addresses to “signers” list.
- Sign the root content.json modifications by pressing the “Sign” button on the sidebar.
- Then keep the sidebar open and change “content.json” to “data/users/content.json” and press the “Sign” button again!
Adding messages to our json file
When hitting the Send button we going to add the message to our user’s data.json file, sign it, then publish it to other users.
- Create a new function in ZeroChat class:
- After this is done type something to the message input and press the Send! button! You should see the message in the data/users/[your auth address]/data.json file. (Don’t worry if you see “Publish failed” message: it is normal, since we don’t have any other users on our site yet)
Creating database
Now we can save and publish our messages to other users, let’s display it in our application! The best way to do this is map all data.json files to an SQL database.
The ZeroNet automatically do this for you, all you need is a dbschema.json file in your site’s directory that describe your table structure:
"db_name": "ZeroChat"
: Used only for debugging"db_file": "data/zerochat.db"
: The SQLite database file will be stored here"version": 2
: Define the json table structure, version 2 is better suited to ZeroID based sites. More info in the reference docs."maps": {
: Describe the json files -> table conversion"users/.+/data.json": { "to_table": [ "message" ] }
: Put the data from every user data.json file message node to message table."users/.+/content.json": { "to_json_table": [ "cert_user_id" ], "file_name": "data.json"}
: Store the username in the data.json file json table entry."tables": {
: Describe the tables and indexing structure. A json table entry will be created automatically for every json file.["json_id", "INTEGER REFERENCES json (json_id)"]
: Every table should contain a json_id column, it defines the source file path."schema_changed": 10
: Increment this when you change the table structure, so the peers can drop the table and re-create it from the json files.
Tip: For the best performance always create an index for json_id column in your tables, because when new file arrives the data will be updated based on this column.
- Press the Reload, then the Rebuild button on the sidebar to generate the database.
If you did everything well, then the data/zerochat.db SQLite file will be created. To browse these files I recommend using SQLiteStudio (it’s free and opensource)
Displaying messages
The messages are loaded to the SQL database, so we can easily query them using the dbQuery command:
- Add
this.loadMessages()
to onOpenWebsocket function, then reload the page and you should see the messages you typed in. - To make it “real time” and display new messages immediately as they come in you also have to add the
this.loadMessages()
to the onRequest function:
- And also reload the data when we submit a new message:
That’s it! Now the messages are updated in real-time! You can try it by opening an another browser window and enter a messages there.
Final touches
- Send messages by pressing enter
<input type="text" id="message" onkeypress="if (event.keyCode == 13) page.sendMessage()">
- And add some CSS style to make it look better