diff --git a/book/snippets/nostr/js/index.js b/book/snippets/nostr/js/index.js index 66b01484a..d717feaa8 100644 --- a/book/snippets/nostr/js/index.js +++ b/book/snippets/nostr/js/index.js @@ -1,6 +1,8 @@ const keys = require("./src/keys"); const eventJson = require("./src/event/json"); const eventBuilder = require("./src/event/builder"); +const filters = require("./src/messages/filters"); +const clientMessages = require("./src/messages/client"); const relayMessages = require("./src/messages/relay"); const nip01 = require("./src/nip01"); const nip05 = require("./src/nip05"); @@ -20,6 +22,8 @@ async function main() { eventJson.eventJson(); eventBuilder.eventBuilder(); + filters.run(); + await clientMessages.run(); await relayMessages.run(); nip01.run(); diff --git a/book/snippets/nostr/js/src/messages/client.js b/book/snippets/nostr/js/src/messages/client.js new file mode 100644 index 000000000..cf05a0b1f --- /dev/null +++ b/book/snippets/nostr/js/src/messages/client.js @@ -0,0 +1,63 @@ +const { ClientMessage, EventBuilder, Filter, Keys, loadWasmAsync } = require('@rust-nostr/nostr-sdk'); + +async function run() { + await loadWasmAsync(); + + const keys = Keys.generate(); + const event = EventBuilder.textNote("TestTextNoTe", []).signWithKeys(keys); + + console.log() + console.log("Client Messages:"); + + // ANCHOR: event-message + // Create Event client message + console.log(" Event Client Message:"); + let clientMessage = ClientMessage.event(event); + console.log(` - JSON: ${clientMessage.asJson()}`); + // ANCHOR_END: event-message + + console.log(); + // ANCHOR: req-message + // Create Request client message + console.log(" Request Client Message:"); + let f = new Filter().id(event.id); + clientMessage = ClientMessage.req("ABC123", [f]); + console.log(` - JSON: ${clientMessage.asJson()}`); + // ANCHOR_END: req-message + + console.log(); + // ANCHOR: close-message + // Create Close client message + console.log(" Close Client Message:"); + clientMessage = ClientMessage.close("ABC123"); + console.log(` - JSON: ${clientMessage.asJson()}`); + // ANCHOR_END: close-message + + console.log(); + // ANCHOR: parse-message + // Parse Messages from JSON + console.log(" Parse Client Messages:"); + clientMessage = ClientMessage.fromJson('["REQ","ABC123",{"#p":["421a4dd67be773903f805bcb7975b4d3377893e0e09d7563b8972ee41031f551"]}]'); + console.log(` - JSON: ${clientMessage.asJson()}`); + // ANCHOR_END: parse-message + + console.log(); + // ANCHOR: auth-message + // Create Auth client message (NIP42) + console.log(" Auth Client Message:"); + clientMessage = ClientMessage.auth(event); + console.log(` - JSON: ${clientMessage.asJson()}`); + // ANCHOR_END: auth-message + + console.log(); + // ANCHOR: count-message + // Create Count client message (NIP45) + console.log(" Count Client Message:"); + f = new Filter().pubkey(keys.publicKey); + clientMessage = ClientMessage.count("ABC123", [f]); + console.log(` - JSON: ${clientMessage.asJson()}`); + // ANCHOR_END: count-message + +} + +module.exports.run = run; \ No newline at end of file diff --git a/book/snippets/nostr/js/src/messages/filters.js b/book/snippets/nostr/js/src/messages/filters.js new file mode 100644 index 000000000..9d2017de2 --- /dev/null +++ b/book/snippets/nostr/js/src/messages/filters.js @@ -0,0 +1,157 @@ +const { Filter, Keys, Kind, EventBuilder, Timestamp, Tag, loadWasmSync } = require('@rust-nostr/nostr-sdk'); + +async function run() { + loadWasmSync(); + + // Generate keys and Events + const keys = Keys.generate(); + const keys2 = Keys.generate(); + + const kind0 = new Kind(0); + const kind1 = new Kind(1); + const kind4 = new Kind(4); + + const event = EventBuilder.textNote("Hello World!", []).signWithKeys(keys); + const event2 = new EventBuilder(kind0, "Goodbye World!", [Tag.identifier("Identification D Tag")]).signWithKeys(keys2); + + console.log(); + console.log("Creating Filters:"); + + // ANCHOR: create-filter-id + // Filter for specific ID + console.log(" Filter for specific Event ID:"); + let f = new Filter().id(event.id); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-id + + console.log(); + // ANCHOR: create-filter-author + // Filter for specific Author + console.log(" Filter for specific Author:"); + f = new Filter().author(keys.publicKey); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-author + + console.log(); + // ANCHOR: create-filter-kind-pk + // Filter by PK and Kinds + console.log(" Filter with PK and Kinds:"); + f = new Filter() + .pubkey(keys.publicKey) + .kind(kind1); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-kind-pk + + console.log(); + // ANCHOR: create-filter-search + // Filter for specific string + console.log(" Filter for specific search string:"); + f = new Filter().search("Ask Nostr Anything"); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-search + + console.log(); + // ANCHOR: create-filter-timeframe + console.log(" Filter for events from specific public key within given timeframe:"); + // Create timestamps + const date = new Date(2009, 1, 3, 0, 0); + const timestamp = Math.floor(date.getTime() / 1000); + const sinceTs = Timestamp.fromSecs(timestamp); + const untilTs = Timestamp.now(); + + // Filter with timeframe + f = new Filter() + .pubkey(keys.publicKey) + .since(sinceTs) + .until(untilTs); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-timeframe + + console.log(); + // ANCHOR: create-filter-limit + // Filter for specific PK with limit + console.log(" Filter for specific Author, limited to 10 Events:"); + f = new Filter() + .author(keys.publicKey) + .limit(10); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-limit + + console.log(); + // ANCHOR: create-filter-hashtag + // Filter for Hashtags + console.log(" Filter for a list of Hashtags:"); + f = new Filter().hashtags(["#Bitcoin", "#AskNostr", "#Meme"]); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-hashtag + + console.log(); + // ANCHOR: create-filter-reference + // Filter for Reference + console.log(" Filter for a Reference:"); + f = new Filter().reference("This is my NIP-12 Reference"); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-reference + + console.log(); + // ANCHOR: create-filter-identifier + // Filter for Identifier + console.log(" Filter for a Identifier:"); + f = new Filter().identifier("This is my NIP-12 Identifier"); + console.log(` ${f.asJson()}`); + // ANCHOR_END: create-filter-identifier + + console.log(); + console.log("Modifying Filters:"); + // ANCHOR: modify-filter + // Modifying Filters (adding/removing) + f = new Filter() + .pubkeys([keys.publicKey, keys2.publicKey]) + .ids([event.id, event2.id]) + .kinds([kind0, kind1]) + .author(keys.publicKey); + + // Add an additional Kind to existing filter + f = f.kinds([kind4]); + + // Print Results + console.log(" Before:"); + console.log(` ${f.asJson()}`); + console.log(); + + // Remove PKs, Kinds and IDs from filter + f = f.removePubkeys([keys2.publicKey]); + console.log(" After (remove pubkeys):"); + console.log(` ${f.asJson()}`); + kind_rem0 = new Kind(0); + kind_rem4 = new Kind(4); + f = f.removeKinds([kind_rem0, kind_rem4]); + console.log(" After (remove kinds):"); + console.log(` ${f.asJson()}`); + + f = f.removeIds([event2.id]); + console.log(" After (remove IDs):"); + console.log(` ${f.asJson()}`); + // ANCHOR_END: modify-filter + + console.log(); + console.log("Other Filter Operations:"); + // ANCHOR: other-parse + // Parse filter + console.log(" Parse Filter from Json:"); + const fJson = f.asJson(); + f = Filter.fromJson(fJson); + console.log(` ${f.asJson()}`); + // ANCHOR_END: other-parse + + console.log(); + // ANCHOR: other-match + console.log(" Logical tests:"); + kind_match = new Kind(1); + f = new Filter().author(keys.publicKey).kind(kind_match); + console.log(` Event match for filter: ${f.matchEvent(event)}`); + console.log(` Event2 match for filter: ${f.matchEvent(event2)}`); + // ANCHOR_END: other-match +} + +module.exports.run = run; \ No newline at end of file diff --git a/book/src/nostr/05_01-client-message.md b/book/src/nostr/05_01-client-message.md index b79bd413e..84b98436b 100644 --- a/book/src/nostr/05_01-client-message.md +++ b/book/src/nostr/05_01-client-message.md @@ -56,7 +56,31 @@ When presented with a client message object as either a JSON or an instance of t
JavaScript
-TODO +The `ClientMessage` class easily handles the construction of the 3 main message types `EVENT`, `REQ`, and `CLOSE`. +In the examples below we can utilize the relevant class methods `event()`, `req()` and `close()`, respectively, to create the client message objects. + +Once we have the `ClientMessage` objects we can use the `asJson()` method to present their content. + + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/client.js:event-message}} +``` + +Note that when constructing a `REQ` we want to pass through a `Filter` object which will allow the relay to return data meeting a given set of criteria. +Please jump to the [Filter](05_01_01-filter.md) section for more details on how to construct these objects. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/client.js:req-message}} +``` + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/client.js:close-message}} +``` + +When presented with a client message object as either a JSON using the `fromJson()` method. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/client.js:parse-message}}
@@ -112,7 +136,21 @@ Note that `COUNT` is effectively a specific type of `REQ` message therefore it u
JavaScript
-TODO +As an extension of the client messaging section of the protocol [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) and [NIP-45](https://github.com/nostr-protocol/nips/blob/master/45.md) introduce two new messaging types `AUTH` and `COUNT`. + +The `AUTH` type is designed to facilitate a method by which clients can authenticate with a given relay. +Whereas the `COUNT` type offers a method for clients can request simple counts of events from relays. +These are constructed in much the same way as the earlier message examples, by using the `ClientMessage` class in conjunction with the relevant methods `auth()` and `count()`. + +```python,ignore +{{#include ../../snippets/nostr/python/src/messages/client.py:auth-message}} +``` + +Note that `COUNT` is effectively a specific type of `REQ` message therefore it utilizes the `Filter` object in constructing the criteria which should be used by the relay to return the count value. + +```python,ignore +{{#include ../../snippets/nostr/python/src/messages/client.py:count-message}} +```
@@ -132,7 +170,7 @@ TODO -## Error Messages +## Negentropy Messages @@ -147,7 +185,8 @@ TODO
Finally, the `ClientMessageEnum` class also opens up three additional message types `NEG_OPEN()`, `NEG_CLOSE()` and `NEG_MSG()`. -These do not form part of the standard protocol specification but do have specific uses when it comes to providing methods by which error messaging can be handled by clients. +These do not form part of the standard protocol specification but instead form part of an additional protocol [Negentropy](https://github.com/hoytech/negentropy) for handling set-reconciliation. + To construct these we need to first create them as instance of the `ClientMessageEnum` class and then pass these into a `ClientMessage` object using the `from_enum()` method. ```python,ignore @@ -167,7 +206,7 @@ To construct these we need to first create them as instance of the `ClientMessag
JavaScript
-TODO +Not currently available in the Javascript Bindings.
diff --git a/book/src/nostr/05_01_01-filter.md b/book/src/nostr/05_01_01-filter.md index 638238e18..2f99a08d2 100644 --- a/book/src/nostr/05_01_01-filter.md +++ b/book/src/nostr/05_01_01-filter.md @@ -37,7 +37,7 @@ Filtering events by author using `author()`. {{#include ../../snippets/nostr/python/src/messages/filters.py:create-filter-author}} ``` -Filtering events based on multiple criteria. In this case, by public key using `public_key()` and kind using `kind()`. +Filtering events based on multiple criteria. In this case, by public key using `pubkey()` and kind using `kind()`. ```python,ignore {{#include ../../snippets/nostr/python/src/messages/filters.py:create-filter-kind-pk}} @@ -78,7 +78,55 @@ Finally, filtering using hashtags (`hashtag()`), NIP-12 reference tags (`referen
JavaScript
-TODO +The following code examples all utilise the `Filters()` along with associated methods to create filter objects and print these in JSON format using the `asJson()` method. + +Filtering events based on a specific event ID using `id()`. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-id}} +``` + +Filtering events by author using `author()`. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-author}} +``` + +Filtering events based on multiple criteria. In this case, by public key using `pubkey()` and kind using `kind()`. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-kind-pk}} +``` + +Filtering for specific text strings using `search()`. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-search}} +``` + +Restricting query results to specific timeframes (using `since()` and `until()`), as well as limiting search results to a maximum of 10 records using `limit()`. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-timeframe}} +``` + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-limit}} +``` + +Finally, filtering using hashtags (`hashtags()`), NIP-12 reference tags (`reference()`) and identifiers (`identifiers()`), respectively. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-hashtag}} +``` + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-reference}} +``` + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:create-filter-identifier}} +```
@@ -125,8 +173,14 @@ Similarly, the range of 'remove' methods (e.g. `remove_kinds()`) allow us to tak
JavaScript
-TODO +Adding more conditions to existing objects can be done by simply calling the relevant method on the instance of the object. +In this example we create a initial filter with `pubkeys()`, `ids()`, `kinds()` and a single `author()` then modify the object further to include another kind (4) to the existing list of kinds (0, 1). + +Similarly, the range of 'remove' methods (e.g. `removekinds()`) allow us to take an existing filter and remove unwanted conditions without needed to reconstruct the filter object from scratch. +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:modify-filter}} +```
Kotlin
@@ -181,7 +235,17 @@ To perform a logical test and determine if a given event object matches existing
JavaScript
-TODO +We can parse existing filter JSON object using the `fromJson()` method when instantiating a filter object. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:other-parse}} +``` + +To perform a logical test and determine if a given event object matches existing filter conditions the `matchEvent()` method can be used. + +```javascript,ignore +{{#include ../../snippets/nostr/js/src/messages/filters.js:other-match}} +```