-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
establish NostrEvent subclassing pattern with basic event kinds (#56)
* establish NostrEvent subclassing pattern with basic event kinds Closes #48 Closes #49 Closes #50 Closes #51 * lint * validate relay URLs * add external interface for creating set metadata and recommend server events, plus tests
- Loading branch information
1 parent
bf0a080
commit 4da2bbc
Showing
9 changed files
with
274 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// | ||
// RecommendServerEvent.swift | ||
// | ||
// | ||
// Created by Bryan Montz on 7/23/23. | ||
// | ||
|
||
import Foundation | ||
|
||
/// An event that contains a relay the event creator wants to recommend to its followers. | ||
/// | ||
/// > Note: [NIP-01 Specification](https://github.com/nostr-protocol/nips/blob/b503f8a92b22be3037b8115fe3e644865a4fa155/01.md#basic-event-kinds) | ||
public final class RecommendServerEvent: NostrEvent { | ||
public var relayURL: URL? { | ||
let components = URLComponents(string: content) | ||
guard components?.scheme == "wss" || components?.scheme == "ws" else { | ||
return nil | ||
} | ||
return components?.url | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// | ||
// SetMetadataEvent.swift | ||
// | ||
// | ||
// Created by Bryan Montz on 7/22/23. | ||
// | ||
|
||
import Foundation | ||
|
||
/// An object that describes a user. | ||
public struct UserMetadata: Codable { | ||
|
||
/// The user's name. | ||
public let name: String? | ||
|
||
/// The user's description of themself. | ||
public let about: String? | ||
|
||
/// The user's website address. | ||
public let website: URL? | ||
|
||
/// The user's Nostr address. | ||
/// | ||
/// > Note: [NIP-05 Specification](https://github.com/nostr-protocol/nips/blob/master/05.md#nip-05). | ||
public let nostrAddress: String? | ||
|
||
/// A URL to retrieve the user's picture. | ||
public let pictureURL: URL? | ||
|
||
/// A URL to retrieve the user's banner image. | ||
public let bannerPictureURL: URL? | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case name, about, website | ||
case nostrAddress = "nip05" | ||
case pictureURL = "picture" | ||
case bannerPictureURL = "banner" | ||
} | ||
|
||
public init(name: String?, about: String?, website: URL?, nostrAddress: String?, pictureURL: URL?, bannerPictureURL: URL?) { | ||
self.name = name | ||
self.about = about | ||
self.website = website | ||
self.nostrAddress = nostrAddress | ||
self.pictureURL = pictureURL | ||
self.bannerPictureURL = bannerPictureURL | ||
} | ||
} | ||
|
||
/// An event that contains a user profile. | ||
/// | ||
/// > Note: [NIP-01 Specification](https://github.com/nostr-protocol/nips/blob/b503f8a92b22be3037b8115fe3e644865a4fa155/01.md#basic-event-kinds) | ||
public final class SetMetadataEvent: NostrEvent { | ||
|
||
/// A dictionary containing all of the properties in the `content` field of the ``NostrEvent``. | ||
public var rawUserMetadata: [String: Any] { | ||
guard let data = content.data(using: .utf8) else { | ||
return [:] | ||
} | ||
let dict = try? JSONSerialization.jsonObject(with: data) | ||
return dict as? [String: Any] ?? [:] | ||
} | ||
|
||
/// An object that contains decoded user properties from the `content` field of the ``NostrEvent``. | ||
public var userMetadata: UserMetadata? { | ||
guard let data = content.data(using: .utf8) else { | ||
return nil | ||
} | ||
return try? JSONDecoder().decode(UserMetadata.self, from: data) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// | ||
// TextNoteEvent.swift | ||
// | ||
// | ||
// Created by Bryan Montz on 7/23/23. | ||
// | ||
|
||
import Foundation | ||
|
||
/// An event that contains plaintext content. | ||
/// | ||
/// > Note: [NIP-01 Specification](https://github.com/nostr-protocol/nips/blob/b503f8a92b22be3037b8115fe3e644865a4fa155/01.md#basic-event-kinds) | ||
public final class TextNoteEvent: NostrEvent { | ||
|
||
/// Pubkeys mentioned in the note content. | ||
public var mentionedPubkeys: [String] { | ||
let pubkeyTags = tags.filter { $0.identifier == .pubkey } | ||
return pubkeyTags.map { $0.contentIdentifier } | ||
} | ||
|
||
/// Events mentioned in the note content. | ||
public var mentionedEventIds: [String] { | ||
let eventTags = tags.filter { $0.identifier == .event } | ||
return eventTags.map { $0.contentIdentifier } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,26 @@ import XCTest | |
|
||
final class EventCreatingTests: XCTestCase, EventCreating, EventVerifying { | ||
|
||
func testCreateSetMetadataEvent() throws { | ||
let meta = UserMetadata(name: "Nostr SDK Test", | ||
about: "I'm a test account. I'm used to test the Nostr SDK for Apple platforms.", | ||
website: URL(string: "https://github.com/nostr-sdk/nostr-sdk-ios")!, | ||
nostrAddress: "[email protected]", | ||
pictureURL: URL(string: "[email protected]"), | ||
bannerPictureURL: URL(string: "[email protected]")!) | ||
|
||
let event = try setMetadataEvent(withUserMetadata: meta, signedBy: Keypair.test) | ||
|
||
XCTAssertEqual(event.userMetadata?.name, "Nostr SDK Test") | ||
XCTAssertEqual(event.userMetadata?.about, "I'm a test account. I'm used to test the Nostr SDK for Apple platforms.") | ||
XCTAssertEqual(event.userMetadata?.website, URL(string: "https://github.com/nostr-sdk/nostr-sdk-ios")) | ||
XCTAssertEqual(event.userMetadata?.nostrAddress, "[email protected]") | ||
XCTAssertEqual(event.userMetadata?.pictureURL, URL(string: "[email protected]")) | ||
XCTAssertEqual(event.userMetadata?.bannerPictureURL, URL(string: "[email protected]")) | ||
|
||
try verifyEvent(event) | ||
} | ||
|
||
func testCreateSignedTextNote() throws { | ||
let note = try textNote(withContent: "Hello world!", | ||
signedBy: Keypair.test) | ||
|
@@ -22,4 +42,23 @@ final class EventCreatingTests: XCTestCase, EventCreating, EventVerifying { | |
|
||
try verifyEvent(note) | ||
} | ||
|
||
func testCreateRecommendServerEvent() throws { | ||
let inputURL = URL(string: "wss://relay.test")! | ||
let event = try recommendServerEvent(withRelayURL: inputURL, | ||
signedBy: Keypair.test) | ||
|
||
XCTAssertEqual(event.kind, .recommendServer) | ||
XCTAssertEqual(event.relayURL, inputURL) | ||
XCTAssertEqual(event.pubkey, Keypair.test.publicKey.hex) | ||
XCTAssertEqual(event.tags, []) | ||
|
||
try verifyEvent(event) | ||
} | ||
|
||
func testRecommendServerEventFailsWithNonWebsocketURL() throws { | ||
let inputURL = URL(string: "https://not-a-socket.com")! | ||
XCTAssertThrowsError(try recommendServerEvent(withRelayURL: inputURL, | ||
signedBy: Keypair.test)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ final class EventDecodingTests: XCTestCase, FixtureLoading { | |
|
||
func testDecodeSetMetadata() throws { | ||
|
||
let event: NostrEvent = try decodeFixture(filename: "set_metadata") | ||
let event: SetMetadataEvent = try decodeFixture(filename: "set_metadata") | ||
|
||
XCTAssertEqual(event.id, "d214c914b0ab49ec919fa5f60fabf746f421e432d96f941bd2573e4d22e36b51") | ||
XCTAssertEqual(event.pubkey, "00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700") | ||
|
@@ -21,11 +21,28 @@ final class EventDecodingTests: XCTestCase, FixtureLoading { | |
XCTAssertEqual(event.tags, []) | ||
XCTAssertTrue(event.content.hasPrefix("{\"banner\":\"https://nostr.build/i/nostr.build")) | ||
XCTAssertEqual(event.signature, "7bb7f031fbf41f49eeb44fdfb061bc8d143197d33fae8d29b017709adf2b17c76e78ccb2ee128ee93d0661cad4c626a747d48a178745c94944a693ff31ea7619") | ||
|
||
// access metadata properties from raw dictionary | ||
XCTAssertEqual(event.rawUserMetadata["name"] as? String, "cameri") | ||
XCTAssertEqual(event.rawUserMetadata["about"] as? String, "@HodlWithLedn. All opinions are my own.\nBitcoiner class of 2021. Core Nostr Developer. Author of Nostream. Professional Relay Operator.") | ||
XCTAssertEqual(event.rawUserMetadata["website"] as? String, "https://primal.net/cameri") | ||
XCTAssertEqual(event.rawUserMetadata["nip05"] as? String, "[email protected]") | ||
XCTAssertEqual(event.rawUserMetadata["picture"] as? String, "https://nostr.build/i/9396d5cd901304726883aea7363543f121e1d53964dd3149cadecd802608aebe.jpg") | ||
XCTAssertEqual(event.rawUserMetadata["banner"] as? String, "https://nostr.build/i/nostr.build_90a51a2e50c9f42288260d01b3a2a4a1c7a9df085423abad7809e76429da7cdc.gif") | ||
|
||
// access metadata properties from decoded object | ||
let userMetadata = try XCTUnwrap(event.userMetadata) | ||
XCTAssertEqual(userMetadata.name, "cameri") | ||
XCTAssertEqual(userMetadata.about, "@HodlWithLedn. All opinions are my own.\nBitcoiner class of 2021. Core Nostr Developer. Author of Nostream. Professional Relay Operator.") | ||
XCTAssertEqual(userMetadata.website, URL(string: "https://primal.net/cameri")) | ||
XCTAssertEqual(userMetadata.nostrAddress, "[email protected]") | ||
XCTAssertEqual(userMetadata.pictureURL, URL(string: "https://nostr.build/i/9396d5cd901304726883aea7363543f121e1d53964dd3149cadecd802608aebe.jpg")) | ||
XCTAssertEqual(userMetadata.bannerPictureURL, URL(string: "https://nostr.build/i/nostr.build_90a51a2e50c9f42288260d01b3a2a4a1c7a9df085423abad7809e76429da7cdc.gif")) | ||
} | ||
|
||
func testDecodeTextNote() throws { | ||
|
||
let event: NostrEvent = try decodeFixture(filename: "text_note") | ||
let event: TextNoteEvent = try decodeFixture(filename: "text_note") | ||
|
||
XCTAssertEqual(event.id, "fa5ed84fc8eeb959fd39ad8e48388cfc33075991ef8e50064cfcecfd918bb91b") | ||
XCTAssertEqual(event.pubkey, "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2") | ||
|
@@ -39,6 +56,24 @@ final class EventDecodingTests: XCTestCase, FixtureLoading { | |
XCTAssertEqual(event.tags, expectedTags) | ||
XCTAssertEqual(event.content, "I think it stays persistent on your profile, but interface setting doesn’t persist. Bug. ") | ||
XCTAssertEqual(event.signature, "96e6667348b2b1fc5f6e73e68fb1605f571ad044077dda62a35c15eb8290f2c4559935db461f8466df3dcf39bc2e11984c5344f65aabee4520dd6653d74cdc09") | ||
|
||
XCTAssertEqual(event.mentionedPubkeys, ["f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9"]) | ||
XCTAssertEqual(event.mentionedEventIds, ["93930d65435d49db723499335473920795e7f13c45600dcfad922135cf44bd63"]) | ||
} | ||
|
||
func testDecodeRecommendServer() throws { | ||
|
||
let event: RecommendServerEvent = try decodeFixture(filename: "recommend_server") | ||
|
||
XCTAssertEqual(event.id, "test-id") | ||
XCTAssertEqual(event.pubkey, "test-pubkey") | ||
XCTAssertEqual(event.createdAt, 1683799330) | ||
XCTAssertEqual(event.kind, .recommendServer) | ||
XCTAssertEqual(event.tags, []) | ||
XCTAssertEqual(event.content, "wss://nostr.relay") | ||
XCTAssertEqual(event.signature, "test-signature") | ||
|
||
XCTAssertEqual(event.relayURL, URL(string: "wss://nostr.relay")) | ||
} | ||
|
||
func testDecodeContactList() throws { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"content": "wss://nostr.relay", | ||
"created_at": 1683799330, | ||
"id": "test-id", | ||
"kind": 2, | ||
"pubkey": "test-pubkey", | ||
"sig": "test-signature", | ||
"tags": [] | ||
} |