Skip to content

Commit

Permalink
Add support for NIP-40 - Expiration Timestamp (#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
tyiu authored Oct 20, 2024
1 parent abff491 commit 66012be
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The following [NIPs](https://github.com/nostr-protocol/nips) are implemented:
- [ ] [NIP-36: Sensitive Content](https://github.com/nostr-protocol/nips/blob/master/36.md)
- [ ] [NIP-38: User Statuses](https://github.com/nostr-protocol/nips/blob/master/38.md)
- [ ] [NIP-39: External Identities in Profiles](https://github.com/nostr-protocol/nips/blob/master/39.md)
- [ ] [NIP-40: Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md)
- [x] [NIP-40: Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md)
- [ ] [NIP-42: Authentication of clients to relays](https://github.com/nostr-protocol/nips/blob/master/42.md)
- [x] [NIP-44: Versioned Encryption](https://github.com/nostr-protocol/nips/blob/master/44.md)
- [ ] [NIP-45: Counting results](https://github.com/nostr-protocol/nips/blob/master/45.md)
Expand Down
28 changes: 28 additions & 0 deletions Sources/NostrSDK/Events/NostrEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ public class NostrEvent: Codable, Equatable, Hashable {
tags.compactMap { EventCoordinates(eventCoordinatesTag: $0) }
}

/// Unix timestamp at which the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays.
/// See [NIP-40 - Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md).
public var expiration: Int64? {
if let expiration = firstValueForTagName(.expiration) {
return Int64(expiration)
} else {
return nil
}
}

/// Whether the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays.
/// See [NIP-40 - Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md).
public var isExpired: Bool {
if let expiration {
return Int64(Date.now.timeIntervalSince1970) >= expiration
} else {
return false
}
}

/// All tags with the provided name.
public func allTags(withTagName tagName: TagName) -> [Tag] {
tags.filter { $0.name == tagName.rawValue }
Expand Down Expand Up @@ -355,6 +375,14 @@ public extension NostrEvent {
return self
}

/// Specifies a unix timestamp at which the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays.
/// See [NIP-40 - Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md).
@discardableResult
public final func expiration(_ expiration: Int64) -> Self {
tags.append(Tag(name: .expiration, value: String(expiration)))
return self
}

public func build(signedBy keypair: Keypair) throws -> T {
try T(
kind: kind,
Expand Down
4 changes: 4 additions & 0 deletions Sources/NostrSDK/Tag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ public enum TagName: String {
/// coordinates to a replaceable event, which includes the kind number, pubkey that signed the event, and optionally the identifier (if the replaceable event is parameterized)
case eventCoordinates = "a"

/// Specifies a unix timestamp at which the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays.
/// See [NIP-40 - Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md).
case expiration

/// a hashtag to categorize events for easy searching
case hashtag = "t"

Expand Down
20 changes: 20 additions & 0 deletions Tests/NostrSDKTests/Events/NostrEventTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,24 @@ final class NostrEventTests: XCTestCase, FixtureLoading, MetadataCoding {
XCTAssertEqual(metadata.relays?[1], relay2)
}

func testExpiration() throws {
let futureExpiration = Int64(Date.now.timeIntervalSince1970 + 10000)
let futureExpirationEvent = try NostrEvent.Builder(kind: .textNote)
.expiration(futureExpiration)
.build(signedBy: .test)
XCTAssertEqual(futureExpirationEvent.expiration, futureExpiration)
XCTAssertFalse(futureExpirationEvent.isExpired)

let pastExpiration = Int64(Date.now.timeIntervalSince1970 - 1)
let pastExpirationEvent = try NostrEvent.Builder(kind: .textNote)
.expiration(pastExpiration)
.build(signedBy: .test)
XCTAssertEqual(pastExpirationEvent.expiration, pastExpiration)
XCTAssertTrue(pastExpirationEvent.isExpired)

let decodedExpiredEvent: NostrEvent = try decodeFixture(filename: "test_event_expired")
XCTAssertEqual(decodedExpiredEvent.expiration, 1697090842)
XCTAssertTrue(decodedExpiredEvent.isExpired)
}

}
9 changes: 9 additions & 0 deletions Tests/NostrSDKTests/Fixtures/test_event_expired.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"content": "This is a test message.",
"created_at": 1687090842,
"id": "b7fbbb4d473db3b3a671ad995f1248d56e9a2ed7f145c1e5f74308e60db2eba7",
"kind": 1,
"pubkey": "9947f9659dd80c3682402b612f5447e28249997fb3709500c32a585eb0977340",
"sig": "09a1dcace3cec8cd9934b544cede6955c9876e385d73491f1ca5354a0baecda834c43ac9510ef779f8c8c7543b3faccd821e40e0a52dde8527d071cb0d53230c",
"tags": [["expiration","1697090842"]]
}

0 comments on commit 66012be

Please sign in to comment.