[mkdocs] docs: add listen tx flow tutorial#916
Conversation
| : Notifies subscribed clients every time a new block is added to the chain. | ||
|
|
||
| === "Request body" | ||
| === "Frame" |
There was a problem hiding this comment.
Changed from "Request body" to "Frame" so that's not confused with the new "Requests" concept.
There was a problem hiding this comment.
Sounds good. But I'd call the tabs "Subscription frame" and "Notification body", so it's extra clear what they are.
I would even go as far as adding :material-arrow-up-bold: and :material-arrow-down-bold: (for example) at the beginning of the tab title so the direction of each message is explicit.
|
|
||
| [TransactionMetaDataPair](../rest/nem.md#model/TransactionMetaDataPair) | ||
|
|
||
| #### `/account/mosaic/owned/{address}` |
There was a problem hiding this comment.
Discovered some more websocket channels while working on this tutorial.
| } | ||
| ``` | ||
|
|
||
| ## Requests |
There was a problem hiding this comment.
Added a new section to document requests properly. Originally I thought the registration only existed for account channels to work, but it turns out there are a few more that push snapshots of current data to channels.
More details in https://github.com/QuantumMechanics/nem-lightwallet/tree/master/lightwallet#nis-websocket-channels-and-data
There was a problem hiding this comment.
Nice finding! And good idea to give it its own section.
Again, I would mention in the intro to this section (after the definition) that all requests are made via a SEND frame,
and the destination always begins with /w/api/ (so you can remove this detail from the definition).
It's also worth mentioning that some of these requests do not receive any answer in return, and some others get an answer through any of the channels above.
209c9cd to
50f1500
Compare
50f1500 to
b936694
Compare
| port. | ||
| The SockJS endpoint is `/w/messages`, for example: `http://localhost:7778/w/messages`. | ||
|
|
||
| ## Session |
There was a problem hiding this comment.
Before it only covered connection + subscription, now the full cycle with "requests" taken into account.
There was a problem hiding this comment.
I don't understand what a "session" is.
Is it a STOMPS or SockJS concept? Or are you just describing a typical usage session?
You need to clarify this. Maybe rename the section "Typical Usage Session" ?
segfaultxavi
left a comment
There was a problem hiding this comment.
I have only reviewed the WebSocket reference page.
Apologies for spending so much time reviewing such a obscure corner of the docs, but I don't like the shape it has on Symbol and decided to fix it in this review.
I'll continue now to review the rest of the PR, but wanted to submit this early.
| To get **live updates** when an event occurs on the blockchain, NEM publishes | ||
| [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API). | ||
|
|
||
| Client applications can open a WebSocket connection and subscribe to any of the available channels instead of needing | ||
| to constantly poll the [REST API](../rest/nem.md) for updates. | ||
|
|
||
| When an event occurs in a channel, the <node:> sends a notification to every subscribed client in real-time. |
There was a problem hiding this comment.
| NEM publishes blockchain events over | |
| [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), so applications can receive live updates | |
| without constantly polling the [REST API](../rest/nem.md). | |
| Client applications open a WebSocket connection to any <node:> in the network and subscribe to the [channels](#channels) | |
| they want to monitor. | |
| When an event occurs on a channel, the node notifies every subscribed client in real time. | |
| Some channels also accept [requests](#requests) for immediate data, similar to the REST API. | |
| This can simplify applications that use WebSockets as their only API for both live notifications and on-demand updates. |
In this way:
- We establish the terminology we are going to use: clients SUBSCRIBE and REQUEST, nodes NOTIFY. I don't like "push" or "send" because they work in both directions so they can be ambiguous.
- We introduce requests early on so they are not a surprise later, and we state their purpose.
- We introduce the node upfront, otherwise, you mention "the node" and it's not clear which node is that.
| port. | ||
| The SockJS endpoint is `/w/messages`, for example: `http://localhost:7778/w/messages`. | ||
|
|
||
| ## Session |
There was a problem hiding this comment.
I don't understand what a "session" is.
Is it a STOMPS or SockJS concept? Or are you just describing a typical usage session?
You need to clarify this. Maybe rename the section "Typical Usage Session" ?
| ## Session | ||
|
|
||
| The SockJS endpoint is `/w/messages`, for example `http://localhost:7778/w/messages`. | ||
| A client that does not use SockJS can open a plain WebSocket to `/w/messages/websocket` directly. |
There was a problem hiding this comment.
You can't just throw the non-SockJS API here without any more information and never mention it again :D
Also, putting it in the section intro, makes it look like it is an important part of the process.
I would move all mention of the non-SockJS SPI to an info box at the end of this section AND provide more information.
So if SockJS is not used, what is the protocol? What changes? Is there anywhere the user can go to know more if they are interested in this topic?
|
|
||
| After opening the SockJS connection, the client drives the session with STOMP frames. | ||
| A _frame_ is a plain-text message made of a command, optional `header:value` lines, and an optional body. | ||
| Frame |
There was a problem hiding this comment.
This glossary term is global, so we risk collisions if we use too generic words.
Since this is a pretty niche usage, I would call it "STOMP Frame" instead.
The section title should probably be called "STOMP Frames" too.
Same problem with "Channel" and "Request" below, although those are probably related to WebSocket or SockJS, not STOMP.
| !!! note "Some channels require registration" | ||
|
|
||
| Some channels, like [account channels](#account-channels), deliver nothing until they receive a registration | ||
| request. | ||
| A client must send this request before subscribing to the channels it enables. | ||
|
|
||
| See [registration requests](#registration-requests) for the full list. |
There was a problem hiding this comment.
Instead of making this an info box, which interrupts the flow of the session, I would make it an optional step:
| !!! note "Some channels require registration" | |
| Some channels, like [account channels](#account-channels), deliver nothing until they receive a registration | |
| request. | |
| A client must send this request before subscribing to the channels it enables. | |
| See [registration requests](#registration-requests) for the full list. | |
| 3. Send an optional [registration request](#registration-requests) to receive notifications for channels which | |
| do not send them automatically. |
Also, we avoid "nothing", which is too strong a word.
| Request | ||
| : A message the client sends to a `/w/api/...` destination to make the node act once. | ||
| It either **registers** (a prerequisite some channels require before they deliver) or pushes a **snapshot** of | ||
| current data to a channel. |
There was a problem hiding this comment.
| Request | |
| : A message the client sends to a `/w/api/...` destination to make the node act once. | |
| It either **registers** (a prerequisite some channels require before they deliver) or pushes a **snapshot** of | |
| current data to a channel. | |
| Request | |
| : A message sent by the client to make the node deliver: | |
| either notifications on channels that require **registration**, | |
| or an immediate response containing a **snapshot** of the state of the blockchain. |
I think it reads more clearly, but we can iterate.
| req:w/api/account/subscribe | ||
| : Registers the address so that [account channels](#account-channels) begin delivering events. | ||
| It pushes nothing back to any channel. |
There was a problem hiding this comment.
| req:w/api/account/subscribe | |
| : Registers the address so that [account channels](#account-channels) begin delivering events. | |
| It pushes nothing back to any channel. | |
| req:w/api/account/subscribe | |
| : Makes the node start sending notifications about the selected [account channel](#account-channels). |
| Every request is **read-only**. | ||
| It never changes the chain, only registers interest in receiving events or replays existing data. | ||
|
|
||
| ### Registration requests |
There was a problem hiding this comment.
I would add a short intro explaining why these are needed.
Instead of "SEND frame" I would title the code blocks ":material-arrow-up-bold: Registration frame".
Maybe even use the same table layout as above (if we decide to use it), for consistency, even when it would only have one column here.
| #### `/w/api/account/get` | ||
|
|
||
| req:w/api/account/get | ||
| : Registers the address and pushes its current state to <ws:account/{address}>. |
There was a problem hiding this comment.
Is this exactly the same as the previous one, but triggering an immediate notification?
If so, I would explain it in this way.
Always make it explicit who is sending each message and avoid using "push" on its own.
| { "account": "{address}" } | ||
| ``` | ||
|
|
||
| ### Snapshot requests |
There was a problem hiding this comment.
Again, short intro explaining why these are needed.
The definitions of all requests are a bit confusing. I would begin them with something like:
"This request forces the node to send an immediate notification containing..."
Instead of "SEND frame" I would title the code blocks ":material-arrow-up-bold: Request frame".
Maybe even use the same table layout as above (if we decide to use it), for consistency, even when it would only have one column here.
segfaultxavi
left a comment
There was a problem hiding this comment.
Comments for the tutorial proper now.
|
|
||
| NODE_URL = os.getenv('NODE_URL', 'http://libertalia.nemtest.net:7778') | ||
| print(f'Using node {NODE_URL}') | ||
| NODE_HOST = os.getenv('NODE_HOST', 'libertalia.nemtest.net') |
There was a problem hiding this comment.
I understand this change simplifies creating URLs for the WebSocket tutorials, but I think this forces us to change ALL other tutorials to follow the same pattern.
Otherwise, different tutorials use different environment variables and it's a bit messy.
Do you want to apply this pattern to the rest of tutorials, including Symbol? :)
| NODE_URL = f'http://{NODE_HOST}:7890' | ||
| WS_URL = f'http://{NODE_HOST}:7778' |
There was a problem hiding this comment.
| NODE_URL = f'http://{NODE_HOST}:7890' | |
| WS_URL = f'http://{NODE_HOST}:7778' | |
| NODE_REST_URL = f'http://{NODE_HOST}:7890' | |
| NODE_WS_URL = f'http://{NODE_HOST}:7778' |
For consistency.
| print(f'Connected to {WS_URL}') | ||
| # [<step-2] | ||
| # Register the account and confirm it is active [>step-3] | ||
| account = f'/account/{MONITOR_ADDRESS}' |
There was a problem hiding this comment.
| account = f'/account/{MONITOR_ADDRESS}' | |
| destination = f'/account/{MONITOR_ADDRESS}' |
Because this is the meaning of this field for the SUBSCRIBE frame
(and this is how you called it below).
| await stomp_subscribe(websocket, account, 'id-0') | ||
| await stomp_send(websocket, '/w/api/account/get', | ||
| json.dumps({'account': MONITOR_ADDRESS})) | ||
| async for raw in websocket: |
| To receive notifications on an account's transaction channels, the address must first be **registered** with the node. | ||
|
|
||
| The code registers the address by sending a <req:w/api/account/get> request, which both registers it and | ||
| replies with the account's current state on the <ws:account/{address}> channel. |
There was a problem hiding this comment.
| replies with the account's current state on the <ws:account/{address}> channel. | |
| triggers a reply containing the account's current state on the <ws:account/{address}> channel. |
| print(f'{name}: hash={message_hash[:16]}...') | ||
| is_match = message_hash.upper() == expected_hash | ||
| if name == 'confirmed' and is_match: | ||
| short = transaction_hash[:16] |
There was a problem hiding this comment.
short_hash for clarity and consistency with JS.
| To catch that reply, the code subscribes to the channel using `id-0`, then waits. | ||
| Once the reply arrives, the registration is active and the temporary subscription is dropped. |
There was a problem hiding this comment.
| To catch that reply, the code subscribes to the channel using `id-0`, then waits. | |
| Once the reply arrives, the registration is active and the temporary subscription is dropped. | |
| To verify that the subscription is successful, the code first temporarily subscribes to the | |
| <ws:account/{address}> channel, sends the registration request, and waits for that reply. | |
| Once the reply arrives, the registration is active and the temporary subscription to the | |
| general account messages channel is dropped, using the same `id-0` used during registration. |
|
|
||
| {{ tutorial.code_snippet_tagged('step-4') }} | ||
|
|
||
| With the account registered, the code subscribes to two address-scoped channels: |
There was a problem hiding this comment.
| With the account registered, the code subscribes to two address-scoped channels: | |
| With the account's address registered, the code subscribes to two address-scoped channels: |
So the connection between the account and the channels below is extra clear.
|
|
||
| {{ tutorial.code_snippet_tagged('step-5') }} | ||
|
|
||
| This tutorial builds a minimal [Transfer Transaction](../transactions/transfer-xem.md) to the monitored address, with a |
There was a problem hiding this comment.
I would make this link point to the Textbook, and then, below, instead of "as usual", I would say "following the same procedure as in the Transfer Transaction Tutorial", and include the tutorial link there.
| Each message follows the [TransactionMetaDataPair](../reference/rest/nem.md#model/TransactionMetaDataPair) schema, whose | ||
| `meta.hash.data` field holds the transaction hash. | ||
|
|
||
| When a message from the `/transactions/{address}` channel arrives whose hash matches the announced transaction, the |
There was a problem hiding this comment.
| When a message from the `/transactions/{address}` channel arrives whose hash matches the announced transaction, the | |
| When a message from the <ws:transactions/{address}> channel arrives whose hash matches the announced transaction, the |
Same in another place below.
Adapts Listen tx flow tutorial to NEM and documents missing websocket channels and requests.