? 正在编辑: Post:21.body 保存 删除 取消
内容已改变 签署并发布新内容

YSC's blog

ZeroNet开发相关...

总共有 21 个节点

关注已关注

按日期索引
按标签索引

最新评论:

发布新文章

Title

21 hours ago · 2 min read ·
3 comments

标签:
Body
继续阅读

[翻译]ZeroChat 教程 (新)

发表于 2016-12-24 · 9 分钟读完 · 最后修改 发表于 2016-12-24
标签: ZeroNet

原文链接: ZeroChat tutorial (new)

In this tutorial we going to build a P2P, decentralized, server and backend-less chat site in less then 100 lines of code.
(This is a simplified and pure-javascript rework of the original tutorial.)


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 top-right “0” button to left to show the sidebar
  • At the bottom 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:
<body>

<a href="#Select+user" id="select_user" onclick='return page.selectUser()'>Select user</a>:
<input type="text" id="message">
<input type="button" id="send" value="Send!" onclick="return page.sendMessage()"/>
<ul id="messages">
 <li>Welcome to ZeroChat!</li>
</ul>
<script type="text/javascript" src="js/ZeroFrame.js"></script>

</body>

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, after the ZeroFrame.js include:
<script type="text/javascript" src="js/ZeroFrame.js"></script>

<script>
class ZeroChat extends ZeroFrame {
    addMessage (username, message) {
        var message_escaped = message.replace(/</g, "&lt;").replace(/>/g, "&gt;")  // Escape html tags in the message
        this.messages.innerHTML += "<li><b>" + username + "</b>: " + message_escaped + "</li>"
    }

    onOpenWebsocket () {
        this.messages = document.getElementById("messages")
        this.addMessage("System", "Ready to call ZeroFrame API!")
    }
}

page = new ZeroChat()
</script>
  • 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:
    selectUser () {
        this.cmd("certSelect", {accepted_domains: ["zeroid.bit"]})
        return false
    }

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:
    onRequest (cmd, message) {
        if (cmd == "setSiteInfo") {
            if (message.params.cert_user_id)
                document.getElementById("select_user").innerHTML = message.params.cert_user_id
            else
                document.getElementById("select_user").innerHTML = "Select user"
            this.site_info = message.params  // Save site info data to allow access it later
        }
    }

This code will real-time update the currently selected user name.

  • To also display the currently selected username when the page loads add this to “onOpenWebsocket” function:
        this.cmd("siteInfo", {}, (site_info) => {
            if (site_info.cert_user_id)
                document.getElementById("select_user").innerText = site_info.cert_user_id
            this.site_info = site_info
        })

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 by 1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz 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:
    sendMessage () {
        if (!this.site_info.cert_user_id) {  // No account selected, display error
            this.cmd("wrapperNotification", ["info", "Please, select your account."])
            return false
        }

        // This is our data file path
        var inner_path = "data/users/" + this.site_info.auth_address + "/data.json"

        // Load our current messages
        this.cmd("fileGet", {"inner_path": inner_path, "required": false}, (data) => {
            if (data)  // Parse current data file
                data = JSON.parse(data)
            else  // Not exists yet, use default data
                data = { "message": [] }

            // Add the new message to data
            data.message.push({
                "body": document.getElementById("message").value,
                "date_added": Date.now()
            })

            // Encode data array to utf8 json text
            var json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t')))

            // Write file to disk
            this.cmd("fileWrite", [inner_path, btoa(json_raw)], (res) => {
                if (res == "ok") {
                    // Reset the message input
                    document.getElementById("message").value = ""
                    // Publish the file to other users
                    this.cmd("sitePublish", {"inner_path": inner_path})
                } else {
                    this.cmd("wrapperNotification", ["error", "File write error: #{res}"])
                }
            })
        })

        return false
    }
  • After this modification 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",
    "db_file": "data/zerochat.db",
    "version": 2,
    "maps": {
        "users/.+/data.json": {
            "to_table": [ "message" ]
        },
        "users/.+/content.json": {
            "to_json_table": [ "cert_user_id" ],
            "file_name": "data.json"
        }
    },
    "tables": {
        "json": {
            "cols": [
                ["json_id", "INTEGER PRIMARY KEY AUTOINCREMENT"],
                ["directory", "TEXT"],
                ["file_name", "TEXT"],
                ["cert_user_id", "TEXT"]
            ],
            "indexes": ["CREATE UNIQUE INDEX path ON json(directory, file_name)"],
            "schema_changed": 10
        },
        "message": {
            "cols": [
                ["body", "TEXT"],
                ["date_added", "INTEGER"],
                ["json_id", "INTEGER REFERENCES json (json_id)"]
            ],
            "indexes": ["CREATE UNIQUE INDEX message_key ON message(json_id, date_added)"],
            "schema_changed": 10
        }
    }
}
  • "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": {: Describes the json files -> table conversion
  • "users/.+/data.json": { "to_table": [ "message" ] }: Put the data from every user's data.json file message node to message table.
  • "users/.+/content.json": { "to_json_table": [ "cert_user_id" ], "file_name": "data.json"}: For easy SQL Join store the username in the data.json file's 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:

    loadMessages () {
        this.cmd("dbQuery", ["SELECT * FROM message LEFT JOIN json USING (json_id) ORDER BY date_added DESC"], (messages) => {
            document.getElementById("messages").innerHTML = ""  // Always start with empty messages
            for (var i=0; i < messages.length; i++) {
                this.addMessage(messages[i].cert_user_id, messages[i].body)
            }
        })
    }

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:

    onRequest (cmd, message) {
        if (cmd == "setSiteInfo") {
            if (message.params.cert_user_id)
                document.getElementById("select_user").innerHTML = message.params.cert_user_id
            else
                document.getElementById("select_user").innerHTML = "Select user"
            this.site_info = message.params  // Save site info data to allow access it later

            // Reload messages if new file arrives
            if (message.params.event[0] == "file_done")
                this.loadMessages()
        }
    }

Also reload the data when we submit a new message:

    sendMessage () {
        ...
            // Write file to disk
            this.cmd("fileWrite", [inner_path, btoa(json_raw)], (res) => {
                this.loadMessages()
                ...
        ...

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
<style>
* { font-family: monospace; line-height: 1.5em; font-size: 13px; vertical-align: middle; }
body { background-color: white; }
input#message { padding: 5px; width: 50%;  }
input#send { height: 34px; margin-left: -2px; }
ul { padding: 0px; }
li { list-style-type: none; border-top: 1px solid #eee; padding: 5px 0px; }
li:nth-child(odd) { background-color: #F9FAFD; }
li b { color: #3F51B5; }
</style>

Congratulations! You have completed the ZeroChat tutorial! :)

Some idea for improvements:

  • Display message time
  • Move source code to separate .js file
  • Limit displayed messages / add paging
  • Markdown formatting
  • Username mention

Tip: The .js files are cached. To avoid it you need to keep open the Dev Console (F12) with "Disable cache" checked.

Tip: Don't forget to re-sign and publish your site if you do any modification on the source code.

Tip: Your site's private key is in data/users.json file (search for your site's address, then there will be a privatekey entry), so it's a good idea to backup it!

Tip: ZeroNet has a built-in .js and .css merging feature for developers. To enable it add debug to zeronet.conf file's [global] section, then it will response every file's content in the directory if you request all.js or all.css

0 条评论:

user_name1 day ago
回复
Body
<< >>
This page is a snapshot of ZeroNet. Start your own ZeroNet for complete experience. Learn More