commit 28790ccfabe1f5c82a60afaa945423312166fa01
parent 37b5309dd4c6ea07d5219b0b5ba201e8fd73e952
Author: William Casarin <jb55@jb55.com>
Date: Mon, 11 Apr 2022 10:34:35 -0700
reorganize
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
13 files changed, 444 insertions(+), 332 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -9,6 +9,13 @@
/* Begin PBXBuildFile section */
4C75EFA427FA577B0006080F /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; };
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA527FF87A20006080F /* Nostr.swift */; };
+ 4C75EFAA28049C9F0006080F /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 4C75EFA928049C9F0006080F /* CachedAsyncImage */; };
+ 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAC28049CFB0006080F /* PostButton.swift */; };
+ 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAE28049D340006080F /* NostrFilter.swift */; };
+ 4C75EFB128049D510006080F /* NostrResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB028049D510006080F /* NostrResponse.swift */; };
+ 4C75EFB328049D640006080F /* NostrEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB228049D640006080F /* NostrEvent.swift */; };
+ 4C75EFB528049D790006080F /* Relay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB428049D790006080F /* Relay.swift */; };
+ 4C75EFB728049D990006080F /* RelayPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB628049D990006080F /* RelayPool.swift */; };
4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE627F7A08100C66700 /* damusApp.swift */; };
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE827F7A08100C66700 /* ContentView.swift */; };
4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */; };
@@ -41,6 +48,12 @@
4C75EFA327FA577B0006080F /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
4C75EFA527FF87A20006080F /* Nostr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nostr.swift; sourceTree = "<group>"; };
4C75EFA72804823E0006080F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
+ 4C75EFAC28049CFB0006080F /* PostButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostButton.swift; sourceTree = "<group>"; };
+ 4C75EFAE28049D340006080F /* NostrFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrFilter.swift; sourceTree = "<group>"; };
+ 4C75EFB028049D510006080F /* NostrResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrResponse.swift; sourceTree = "<group>"; };
+ 4C75EFB228049D640006080F /* NostrEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEvent.swift; sourceTree = "<group>"; };
+ 4C75EFB428049D790006080F /* Relay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relay.swift; sourceTree = "<group>"; };
+ 4C75EFB628049D990006080F /* RelayPool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPool.swift; sourceTree = "<group>"; };
4CE6DEE327F7A08100C66700 /* damus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = damus.app; sourceTree = BUILT_PRODUCTS_DIR; };
4CE6DEE627F7A08100C66700 /* damusApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = damusApp.swift; sourceTree = "<group>"; };
4CE6DEE827F7A08100C66700 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -60,6 +73,7 @@
buildActionMask = 2147483647;
files = (
4CE6DF1227F7A2B300C66700 /* Starscream in Frameworks */,
+ 4C75EFAA28049C9F0006080F /* CachedAsyncImage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -84,10 +98,25 @@
isa = PBXGroup;
children = (
4C75EFA327FA577B0006080F /* PostView.swift */,
+ 4C75EFAC28049CFB0006080F /* PostButton.swift */,
);
path = Views;
sourceTree = "<group>";
};
+ 4C75EFAB28049CC80006080F /* Nostr */ = {
+ isa = PBXGroup;
+ children = (
+ 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */,
+ 4C75EFA527FF87A20006080F /* Nostr.swift */,
+ 4C75EFAE28049D340006080F /* NostrFilter.swift */,
+ 4C75EFB028049D510006080F /* NostrResponse.swift */,
+ 4C75EFB228049D640006080F /* NostrEvent.swift */,
+ 4C75EFB428049D790006080F /* Relay.swift */,
+ 4C75EFB628049D990006080F /* RelayPool.swift */,
+ );
+ path = Nostr;
+ sourceTree = "<group>";
+ };
4CE6DEDA27F7A08100C66700 = {
isa = PBXGroup;
children = (
@@ -111,14 +140,13 @@
4CE6DEE527F7A08100C66700 /* damus */ = {
isa = PBXGroup;
children = (
+ 4C75EFAB28049CC80006080F /* Nostr */,
4C75EFA72804823E0006080F /* Info.plist */,
4C75EFA227FA576C0006080F /* Views */,
4CE6DEE627F7A08100C66700 /* damusApp.swift */,
4CE6DEE827F7A08100C66700 /* ContentView.swift */,
4CE6DEEA27F7A08200C66700 /* Assets.xcassets */,
4CE6DEEC27F7A08200C66700 /* Preview Content */,
- 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */,
- 4C75EFA527FF87A20006080F /* Nostr.swift */,
);
path = damus;
sourceTree = "<group>";
@@ -166,6 +194,7 @@
name = damus;
packageProductDependencies = (
4CE6DF1127F7A2B300C66700 /* Starscream */,
+ 4C75EFA928049C9F0006080F /* CachedAsyncImage */,
);
productName = damus;
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
@@ -241,6 +270,7 @@
mainGroup = 4CE6DEDA27F7A08100C66700;
packageReferences = (
4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */,
+ 4C75EFA828049C9F0006080F /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */,
);
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
projectDirPath = "";
@@ -284,11 +314,17 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
+ 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
+ 4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
+ 4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
+ 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */,
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
+ 4C75EFB528049D790006080F /* Relay.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -619,6 +655,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
+ 4C75EFA828049C9F0006080F /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/lorenzofiamingo/swiftui-cached-async-image";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 2.0.0;
+ };
+ };
4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/daltoniam/Starscream";
@@ -630,6 +674,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ 4C75EFA928049C9F0006080F /* CachedAsyncImage */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 4C75EFA828049C9F0006080F /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */;
+ productName = CachedAsyncImage;
+ };
4CE6DF1127F7A2B300C66700 /* Starscream */ = {
isa = XCSwiftPackageProductDependency;
package = 4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */;
diff --git a/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -8,6 +8,15 @@
"revision" : "df8d82047f6654d8e4b655d1b1525c64e1059d21",
"version" : "4.0.4"
}
+ },
+ {
+ "identity" : "swiftui-cached-async-image",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/lorenzofiamingo/swiftui-cached-async-image",
+ "state" : {
+ "revision" : "eeb1565d780d1b75d045e21b5ca2a1e3650b0fc2",
+ "version" : "2.1.0"
+ }
}
],
"version" : 2
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -64,11 +64,6 @@ enum Sheets: Identifiable {
}
}
-enum NostrKind: Int {
- case metadata = 0
- case text = 1
-}
-
struct ContentView: View {
@State var status: String = "Not connected"
@State var sub_id: String? = nil
@@ -228,22 +223,6 @@ struct ContentView_Previews: PreviewProvider {
}
}
-func PostButton(action: @escaping () -> ()) -> some View {
- return Button(action: action, label: {
- Text("+")
- .font(.system(.largeTitle))
- .frame(width: 57, height: 50)
- .foregroundColor(Color.white)
- .padding(.bottom, 7)
- })
- .background(Color.blue)
- .cornerRadius(38.5)
- .padding()
- .shadow(color: Color.black.opacity(0.3),
- radius: 3,
- x: 3,
- y: 3)
-}
func get_metadata_since_time(_ metadata_event: NostrEvent?) -> Int64? {
@@ -281,7 +260,3 @@ func get_profiles()
*/
-func add_rw_relay(_ pool: RelayPool, _ url: String) {
- let url_ = URL(string: url)!
- try! pool.add_relay(url_, info: RelayInfo.rw)
-}
diff --git a/damus/Nostr.swift b/damus/Nostr.swift
@@ -1,17 +0,0 @@
-//
-// Nostr.swift
-// damus
-//
-// Created by William Casarin on 2022-04-07.
-//
-
-import Foundation
-
-
-struct Profile: Decodable {
- let name: String?
- let about: String?
- let picture: String?
-}
-
-
diff --git a/damus/Nostr/Nostr.swift b/damus/Nostr/Nostr.swift
@@ -0,0 +1,33 @@
+//
+// Nostr.swift
+// damus
+//
+// Created by William Casarin on 2022-04-07.
+//
+
+import Foundation
+
+
+struct Profile: Decodable {
+ let name: String?
+ let about: String?
+ let picture: String?
+}
+
+enum NostrKind: Int {
+ case metadata = 0
+ case text = 1
+}
+
+enum NostrTag {
+ case other_event(OtherEvent)
+ case key_event(KeyEvent)
+}
+
+struct NostrSubscription {
+ let sub_id: String
+ let filter: NostrFilter
+}
+
+
+
diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift
@@ -0,0 +1,43 @@
+//
+// NostrEvent.swift
+// damus
+//
+// Created by William Casarin on 2022-04-11.
+//
+
+import Foundation
+
+struct OtherEvent {
+ let event_id: String
+ let relay_url: String
+}
+
+struct KeyEvent {
+ let key: String
+ let relay_url: String
+}
+
+struct NostrEvent: Decodable, Identifiable {
+ let id: String
+ let pubkey: String
+ let created_at: Int64
+ let kind: Int
+ let tags: [[String]]
+ let content: String
+ let sig: String
+}
+
+func decode_nostr_event(txt: String) -> NostrResponse? {
+ return decode_data(Data(txt.utf8))
+}
+
+func decode_data<T: Decodable>(_ data: Data) -> T? {
+ let decoder = JSONDecoder()
+ do {
+ return try decoder.decode(T.self, from: data)
+ } catch {
+ print("decode_data failed for \(T.self): \(error)")
+ }
+
+ return nil
+}
diff --git a/damus/Nostr/NostrFilter.swift b/damus/Nostr/NostrFilter.swift
@@ -0,0 +1,40 @@
+//
+// NostrFilter.swift
+// damus
+//
+// Created by William Casarin on 2022-04-11.
+//
+
+import Foundation
+
+struct NostrFilter: Codable {
+ var ids: [String]?
+ var kinds: [Int]?
+ var referenced_ids: [String]?
+ var pubkeys: [String]?
+ var since: Int64?
+ var until: Int64?
+ var authors: [String]?
+
+ private enum CodingKeys : String, CodingKey {
+ case ids
+ case kinds
+ case referenced_ids = "#e"
+ case pubkeys = "#p"
+ case since
+ case until
+ case authors
+ }
+
+ public static var filter_text: NostrFilter {
+ NostrFilter(ids: nil, kinds: [1], referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: nil)
+ }
+
+ public static var filter_profiles: NostrFilter {
+ return NostrFilter(ids: nil, kinds: [0], referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: nil)
+ }
+
+ public static func filter_since(_ val: Int64) -> NostrFilter {
+ return NostrFilter(ids: nil, kinds: nil, referenced_ids: nil, pubkeys: nil, since: val, until: nil, authors: nil)
+ }
+}
diff --git a/damus/Nostr/NostrResponse.swift b/damus/Nostr/NostrResponse.swift
@@ -0,0 +1,39 @@
+//
+// NostrResponse.swift
+// damus
+//
+// Created by William Casarin on 2022-04-11.
+//
+
+import Foundation
+
+enum NostrResponse: Decodable {
+ case event(String, NostrEvent)
+ case notice(String)
+
+ init(from decoder: Decoder) throws {
+ var container = try decoder.unkeyedContainer()
+
+ // Only use first item
+ let typ = try container.decode(String.self)
+ if typ == "EVENT" {
+ let sub_id = try container.decode(String.self)
+ var ev: NostrEvent
+ do {
+ ev = try container.decode(NostrEvent.self)
+ } catch {
+ print(error)
+ throw error
+ }
+ self = .event(sub_id, ev)
+ return
+ } else if typ == "NOTICE" {
+ let msg = try container.decode(String.self)
+ self = .notice(msg)
+ return
+ }
+
+ throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "expected EVENT or NOTICE, got \(typ)"))
+ }
+}
+
diff --git a/damus/Nostr/Relay.swift b/damus/Nostr/Relay.swift
@@ -0,0 +1,35 @@
+//
+// Relay.swift
+// damus
+//
+// Created by William Casarin on 2022-04-11.
+//
+
+import Foundation
+
+struct RelayInfo {
+ let read: Bool
+ let write: Bool
+
+ static let rw = RelayInfo(read: true, write: true)
+}
+
+struct Relay: Identifiable {
+ let url: URL
+ let info: RelayInfo
+ let connection: RelayConnection
+
+ var id: String {
+ return get_relay_id(url)
+ }
+
+}
+
+enum RelayError: Error {
+ case RelayAlreadyExists
+ case RelayNotFound
+}
+
+func get_relay_id(_ url: URL) -> String {
+ return url.absoluteString
+}
diff --git a/damus/Nostr/RelayConnection.swift b/damus/Nostr/RelayConnection.swift
@@ -0,0 +1,89 @@
+//
+// NostrConnection.swift
+// damus
+//
+// Created by William Casarin on 2022-04-02.
+//
+
+import Foundation
+import Starscream
+
+enum NostrConnectionEvent {
+ case ws_event(WebSocketEvent)
+ case nostr_event(NostrResponse)
+}
+
+class RelayConnection: WebSocketDelegate {
+ var isConnected: Bool = false
+ var socket: WebSocket
+ var handleEvent: (NostrConnectionEvent) -> ()
+
+ init(url: URL, handleEvent: @escaping (NostrConnectionEvent) -> ()) {
+ var req = URLRequest(url: url)
+ req.timeoutInterval = 5
+ self.socket = WebSocket(request: req)
+ self.handleEvent = handleEvent
+
+ socket.delegate = self
+ }
+
+ func connect(){
+ socket.connect()
+ }
+
+ func disconnect() {
+ socket.disconnect()
+ }
+
+ func send(_ filters: [NostrFilter], sub_id: String) {
+ guard let req = make_nostr_req(filters, sub_id: sub_id) else {
+ print("failed to encode nostr req: \(filters)")
+ return
+ }
+ socket.write(string: req)
+ }
+
+ func didReceive(event: WebSocketEvent, client: WebSocket) {
+ switch event {
+ case .connected:
+ self.isConnected = true
+
+ case .disconnected: fallthrough
+ case .cancelled: fallthrough
+ case .error:
+ self.isConnected = false
+
+ case .text(let txt):
+ if let ev = decode_nostr_event(txt: txt) {
+ handleEvent(.nostr_event(ev))
+ return
+ }
+
+ print("decode failed for \(txt)")
+ // TODO: trigger event error
+
+ default:
+ break
+ }
+
+ handleEvent(.ws_event(event))
+ }
+
+}
+
+func make_nostr_req(_ filters: [NostrFilter], sub_id: String) -> String? {
+ let encoder = JSONEncoder()
+ var req = "[\"REQ\",\"\(sub_id)\""
+ for filter in filters {
+ req += ","
+ guard let filter_json = try? encoder.encode(filter) else {
+ return nil
+ }
+ let filter_json_str = String(decoding: filter_json, as: UTF8.self)
+ req += filter_json_str
+ }
+ req += "]"
+ print("req: \(req)")
+ return req
+}
+
diff --git a/damus/Nostr/RelayPool.swift b/damus/Nostr/RelayPool.swift
@@ -0,0 +1,79 @@
+//
+// RelayPool.swift
+// damus
+//
+// Created by William Casarin on 2022-04-11.
+//
+
+import Foundation
+
+class RelayPool {
+ var relays: [Relay] = []
+ let custom_handle_event: (String, NostrConnectionEvent) -> ()
+
+ init(handle_event: @escaping (String, NostrConnectionEvent) -> ()) {
+ self.custom_handle_event = handle_event
+ }
+
+ func add_relay(_ url: URL, info: RelayInfo) throws {
+ let relay_id = get_relay_id(url)
+ if get_relay(relay_id) != nil {
+ throw RelayError.RelayAlreadyExists
+ }
+ let conn = RelayConnection(url: url) { event in
+ self.handle_event(relay_id: relay_id, event: event)
+ }
+ let relay = Relay(url: url, info: info, connection: conn)
+ self.relays.append(relay)
+ }
+
+ func connect(to: [String]? = nil) {
+ let relays = to.map{ get_relays($0) } ?? self.relays
+ for relay in relays {
+ relay.connection.connect()
+ }
+ }
+
+ func send(filters: [NostrFilter], sub_id: String, to: [String]? = nil) {
+ let relays = to.map{ get_relays($0) } ?? self.relays
+
+ for relay in relays {
+ if relay.connection.isConnected {
+ relay.connection.send(filters, sub_id: sub_id)
+ }
+ }
+ }
+
+ func get_relays(_ ids: [String]) -> [Relay] {
+ var relays: [Relay] = []
+
+ for id in ids {
+ if let relay = get_relay(id) {
+ relays.append(relay)
+ }
+ }
+
+ return relays
+ }
+
+ func get_relay(_ id: String) -> Relay? {
+ for relay in relays {
+ if relay.id == id {
+ return relay
+ }
+ }
+
+ return nil
+ }
+
+ func handle_event(relay_id: String, event: NostrConnectionEvent) {
+ // handle reconnect logic, etc?
+ custom_handle_event(relay_id, event)
+ }
+}
+
+func add_rw_relay(_ pool: RelayPool, _ url: String) {
+ let url_ = URL(string: url)!
+ try! pool.add_relay(url_, info: RelayInfo.rw)
+}
+
diff --git a/damus/RelayConnection.swift b/damus/RelayConnection.swift
@@ -1,288 +0,0 @@
-//
-// NostrConnection.swift
-// damus
-//
-// Created by William Casarin on 2022-04-02.
-//
-
-import Foundation
-import Starscream
-
-struct OtherEvent {
- let event_id: String
- let relay_url: String
-}
-
-struct KeyEvent {
- let key: String
- let relay_url: String
-}
-
-enum NostrConnectionEvent {
- case ws_event(WebSocketEvent)
- case nostr_event(NostrResponse)
-}
-
-enum NostrTag {
- case other_event(OtherEvent)
- case key_event(KeyEvent)
-}
-
-struct NostrSubscription {
- let sub_id: String
- let filter: NostrFilter
-}
-
-struct NostrFilter: Codable {
- var ids: [String]?
- var kinds: [Int]?
- var referenced_ids: [String]?
- var pubkeys: [String]?
- var since: Int64?
- var until: Int64?
- var authors: [String]?
-
- private enum CodingKeys : String, CodingKey {
- case ids
- case kinds
- case referenced_ids = "#e"
- case pubkeys = "#p"
- case since
- case until
- case authors
- }
-
- public static var filter_text: NostrFilter {
- NostrFilter(ids: nil, kinds: [1], referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: nil)
- }
-
- public static var filter_profiles: NostrFilter {
- return NostrFilter(ids: nil, kinds: [0], referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: nil)
- }
-
- public static func filter_since(_ val: Int64) -> NostrFilter {
- return NostrFilter(ids: nil, kinds: nil, referenced_ids: nil, pubkeys: nil, since: val, until: nil, authors: nil)
- }
-}
-
-enum NostrResponse: Decodable {
- case event(String, NostrEvent)
- case notice(String)
-
- init(from decoder: Decoder) throws {
- var container = try decoder.unkeyedContainer()
-
- // Only use first item
- let typ = try container.decode(String.self)
- if typ == "EVENT" {
- let sub_id = try container.decode(String.self)
- var ev: NostrEvent
- do {
- ev = try container.decode(NostrEvent.self)
- } catch {
- print(error)
- throw error
- }
- self = .event(sub_id, ev)
- return
- } else if typ == "NOTICE" {
- let msg = try container.decode(String.self)
- self = .notice(msg)
- return
- }
-
- throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "expected EVENT or NOTICE, got \(typ)"))
- }
-}
-
-struct NostrEvent: Decodable, Identifiable {
- let id: String
- let pubkey: String
- let created_at: Int64
- let kind: Int
- let tags: [[String]]
- let content: String
- let sig: String
-}
-
-struct RelayInfo {
- let read: Bool
- let write: Bool
-
- static let rw = RelayInfo(read: true, write: true)
-}
-
-struct Relay: Identifiable {
- let url: URL
- let info: RelayInfo
- let connection: RelayConnection
-
- var id: String {
- return get_relay_id(url)
- }
-
-}
-
-func get_relay_id(_ url: URL) -> String {
- return url.absoluteString
-}
-
-enum RelayError: Error {
- case RelayAlreadyExists
- case RelayNotFound
-}
-
-class RelayPool {
- var relays: [Relay] = []
- let custom_handle_event: (String, NostrConnectionEvent) -> ()
-
- init(handle_event: @escaping (String, NostrConnectionEvent) -> ()) {
- self.custom_handle_event = handle_event
- }
-
- func add_relay(_ url: URL, info: RelayInfo) throws {
- let relay_id = get_relay_id(url)
- if get_relay(relay_id) != nil {
- throw RelayError.RelayAlreadyExists
- }
- let conn = RelayConnection(url: url) { event in
- self.handle_event(relay_id: relay_id, event: event)
- }
- let relay = Relay(url: url, info: info, connection: conn)
- self.relays.append(relay)
- }
-
- func connect(to: [String]? = nil) {
- let relays = to.map{ get_relays($0) } ?? self.relays
- for relay in relays {
- relay.connection.connect()
- }
- }
-
- func send(filters: [NostrFilter], sub_id: String, to: [String]? = nil) {
- let relays = to.map{ get_relays($0) } ?? self.relays
-
- for relay in relays {
- if relay.connection.isConnected {
- relay.connection.send(filters, sub_id: sub_id)
- }
- }
- }
-
- func get_relays(_ ids: [String]) -> [Relay] {
- var relays: [Relay] = []
-
- for id in ids {
- if let relay = get_relay(id) {
- relays.append(relay)
- }
- }
-
- return relays
- }
-
- func get_relay(_ id: String) -> Relay? {
- for relay in relays {
- if relay.id == id {
- return relay
- }
- }
-
- return nil
- }
-
- func handle_event(relay_id: String, event: NostrConnectionEvent) {
- // handle reconnect logic, etc?
- custom_handle_event(relay_id, event)
- }
-}
-
-class RelayConnection: WebSocketDelegate {
- var isConnected: Bool = false
- var socket: WebSocket
- var handleEvent: (NostrConnectionEvent) -> ()
-
- init(url: URL, handleEvent: @escaping (NostrConnectionEvent) -> ()) {
- var req = URLRequest(url: url)
- req.timeoutInterval = 5
- self.socket = WebSocket(request: req)
- self.handleEvent = handleEvent
-
- socket.delegate = self
- }
-
- func connect(){
- socket.connect()
- }
-
- func disconnect() {
- socket.disconnect()
- }
-
- func send(_ filters: [NostrFilter], sub_id: String) {
- guard let req = make_nostr_req(filters, sub_id: sub_id) else {
- print("failed to encode nostr req: \(filters)")
- return
- }
- socket.write(string: req)
- }
-
- func didReceive(event: WebSocketEvent, client: WebSocket) {
- switch event {
- case .connected:
- self.isConnected = true
-
- case .disconnected: fallthrough
- case .cancelled: fallthrough
- case .error:
- self.isConnected = false
-
- case .text(let txt):
- if let ev = decode_nostr_event(txt: txt) {
- handleEvent(.nostr_event(ev))
- return
- }
-
- print("decode failed for \(txt)")
- // TODO: trigger event error
-
- default:
- break
- }
-
- handleEvent(.ws_event(event))
- }
-
-}
-
-func decode_nostr_event(txt: String) -> NostrResponse? {
- return decode_data(Data(txt.utf8))
-}
-
-func decode_data<T: Decodable>(_ data: Data) -> T? {
- let decoder = JSONDecoder()
- do {
- return try decoder.decode(T.self, from: data)
- } catch {
- print("decode_data failed for \(T.self): \(error)")
- }
-
- return nil
-}
-
-func make_nostr_req(_ filters: [NostrFilter], sub_id: String) -> String? {
- let encoder = JSONEncoder()
- var req = "[\"REQ\",\"\(sub_id)\""
- for filter in filters {
- req += ","
- guard let filter_json = try? encoder.encode(filter) else {
- return nil
- }
- let filter_json_str = String(decoding: filter_json, as: UTF8.self)
- req += filter_json_str
- }
- req += "]"
- print("req: \(req)")
- return req
-}
-
diff --git a/damus/Views/PostButton.swift b/damus/Views/PostButton.swift
@@ -0,0 +1,26 @@
+//
+// PostButton.swift
+// damus
+//
+// Created by William Casarin on 2022-04-11.
+//
+
+import Foundation
+import SwiftUI
+
+func PostButton(action: @escaping () -> ()) -> some View {
+ return Button(action: action, label: {
+ Text("+")
+ .font(.system(.largeTitle))
+ .frame(width: 57, height: 50)
+ .foregroundColor(Color.white)
+ .padding(.bottom, 7)
+ })
+ .background(Color.blue)
+ .cornerRadius(38.5)
+ .padding()
+ .shadow(color: Color.black.opacity(0.3),
+ radius: 3,
+ x: 3,
+ y: 3)
+}