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

YSC's blog

ZeroNet开发相关...

总共有 22 个节点

关注已关注

按日期索引
按标签索引

最新评论:

发布新文章

Title

21 hours ago · 2 min read ·
3 comments

标签:
Body
继续阅读

[翻译]ZeroNet 站点开发教程 #2

发表于 2016-9-30 · 8 分钟读完 · 最后修改 发表于 2016-10-20
标签: ZeroNet

原文链接: ZeroNet site development tutorial #2

第一部分 我们使用了一些简单的 ZeroFrame API 调用创建了一个 ZeroNet 站点。

We going to extend it to accept, store and query messages using a SQLite database and use ZeroID to identify user names.

The final page and source code is available at http://127.0.0.1:43110/1AvF5TpcaamRNtqvN1cnDEWzNmUtD47Npg


添加 ZeroID 用户选择

Add a new link to index.html that will allow us to select the ZeroID identity we want to use:

<html>
<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!"/>
<ul id="messages">
 <li>Welcome to ZeroChat!</li>
</ul>
<script type="text/javascript" src="js/all.js" async></script>
</body>
</html>

To make it work add a function to js/ZeroChat.coffee that displays the certificate selection dialog to user:

    selectUser: =>
        Page.cmd "certSelect", [["zeroid.bit"]]
        return false

As parameter you should include the accepted certificate provider names. (zeroid.bit here)

If you hit a refresh and click on "Select user", then you should see the dialog, but nothing changes when you select your zeroid certificate.

显示用户当前的 ZeroID 账户

When something is changed that 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 setSiteInfo command)

To handle this event add this function:

    route: (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"
            @site_info = message.params  # Save site info data to allow access it later

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

To also update the user name on page load modify the onOpenWebsocket function:

    onOpenWebsocket: (e) =>
        @cmd "serverInfo", {}, (server_info) =>
            @addLine "serverInfo response: <pre>" + JSON.stringify(server_info,null,2) + "</pre>"
        @cmd "siteInfo", {}, (site_info) =>
            @addLine "siteInfo response: <pre>" + JSON.stringify(site_info,null,2) + "</pre>"
            # Update currently selected username
            if site_info.cert_user_id
                document.getElementById("select_user").innerHTML = site_info.cert_user_id
            @site_info = site_info  # Save site info data to allow access it later

设置用户内容权限 (content permissions)

To allow users to post on our site we have to define the rules of the third-party content.

Create a data/users directory and create a data/users/content.json file in it:

{
  "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": ".*",: When the site owner signing this content.json don't add any file to it, they will be signed by the users.
  • 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 have to modify our root content.json to also ignore files in this directory 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.

Now we have to sign the data/users/content.json file using the following command:
zeronet.py siteSign [siteaddress] --inner_path data/users/content.json

添加消息到我们的 json 文件

When hitting the Send button we going to add the message to our data.json file, sign it, then publish it to other users.

  • First add the onclick="return Page.sendMessage()" event listener to Send input button html tag.

  • Then create a new function in ZeroChat.coffee:

    sendMessage: =>
        if not Page.site_info.cert_user_id  # No account selected, display error
            Page.cmd "wrapperNotification", ["info", "Please, select your account."]
            return false

        inner_path = "data/users/#{@site_info.auth_address}/data.json"  # This is our data file

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

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

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

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

        return false
  • 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.

创建数据库

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_keyvalue": [ "cert_user_id" ]
        }
    },
    "tables": {
        "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": 1
        }
    }
}
  • "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_keyvalue": [ "cert_user_id" ] }: Store the user's authentication id in simple key/value structure.
  • "tables": {: Describe the table and indexing structure.
  • ["json_id", "INTEGER REFERENCES json (json_id)"]: Every table should contain a json_id column, it defines the source file path.
  • "schema_changed": 1: 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.

Execute zeronet.py dbRebuild [your site address] command to generate database from current files.

The data/zerochat.db SQLite file will be created, to browse these files I recommend using SQLiteStudio (it's free and opensource)

At this point you have to restart your ZeroNet client to detect and manage your site's newly created database.

显示消息

As the messages are now it the SQL database we can query them using the dbQuery command:

    loadMessages: ->
        @cmd "dbQuery", ["SELECT * FROM message ORDER BY date_added"], (messages) =>
            document.getElementById("messages").innerHTML = ""  # Always start with empty messages
            for message in messages
                @addLine message.body

Add the @loadMessages() line 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 have to add the @loadMessages() to the route part:

    route: (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"
            @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"
                @loadMessages()

And also reload the data when we submit a new message:

    sendMessage: =>
        ...
            # Write file to disk
            @cmd "fileWrite", [inner_path, btoa(json_raw)], (res) =>
                @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.

显示 ZeroID 用户名

To display the sender user name we have to create a more complex SQL query, because the user names are stored in content.json file and the messages are in data.json.

+ we will also escape the incoming messages to disallow html codes.

    loadMessages: ->
        query = """
            SELECT message.*, keyvalue.value AS cert_user_id FROM message
            LEFT JOIN json AS data_json USING (json_id)
            LEFT JOIN json AS content_json ON (
                data_json.directory = content_json.directory AND content_json.file_name = 'content.json'
            )
            LEFT JOIN keyvalue ON (keyvalue.key = 'cert_user_id' AND keyvalue.json_id = content_json.json_id)
            ORDER BY date_added
        """
        @cmd "dbQuery", [query], (messages) =>
            document.getElementById("messages").innerHTML = ""  # Always start with empty messages
            for message in messages
                body = message.body.replace(/</g, "&lt;").replace(/>/g, "&gt;")  # Escape html tags in body
                @addLine "<b>#{message.cert_user_id}</b>: #{body}"

最后的更改

  • We can remove the site siteInfo and serverInfo debug messages:
    onOpenWebsocket: (e) =>
        @cmd "siteInfo", {}, (site_info) =>
            # Update currently selected username
            if site_info.cert_user_id
                document.getElementById("select_user").innerHTML = site_info.cert_user_id
            @site_info = site_info  # Save site info data to allow access it later
        @loadMessages()
  • Send messages by hitting 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%; height: 34px; }
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>
  • After you edited any file never forget to sign and publish the modifications!
zeronet.py siteSign [your site address] --publish

恭喜! Now you have a server-less, pure P2P, SQL backed chat application! :)

0 条评论:

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