mirror of
git://jb55.com/damus
synced 2024-09-30 00:40:45 +00:00
Compare commits
6 Commits
247f313b54
...
07c504f701
Author | SHA1 | Date | |
---|---|---|---|
|
07c504f701 | ||
|
d58d777541 | ||
|
e951370a76 | ||
|
b18941383f | ||
|
f71957e061 | ||
|
68409f3440 |
@ -458,6 +458,7 @@
|
||||
D723411A2B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; };
|
||||
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; };
|
||||
D724D8272B64B40B00ABE789 /* DamusPurpleAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D724D8262B64B40B00ABE789 /* DamusPurpleAccountView.swift */; };
|
||||
D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */; };
|
||||
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
||||
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; };
|
||||
@ -1375,6 +1376,7 @@
|
||||
D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleEnvironment.swift; sourceTree = "<group>"; };
|
||||
D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = "<group>"; };
|
||||
D724D8262B64B40B00ABE789 /* DamusPurpleAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleAccountView.swift; sourceTree = "<group>"; };
|
||||
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURLTests.swift; sourceTree = "<group>"; };
|
||||
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
||||
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDamusState.swift; sourceTree = "<group>"; };
|
||||
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; };
|
||||
@ -2544,6 +2546,7 @@
|
||||
E0E024102B7C19C20075735D /* TranslationTests.swift */,
|
||||
E06336A92B75832100A88E6B /* ImageMetadataTest.swift */,
|
||||
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */,
|
||||
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */,
|
||||
);
|
||||
path = damusTests;
|
||||
sourceTree = "<group>";
|
||||
@ -3503,6 +3506,7 @@
|
||||
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */,
|
||||
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
||||
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
||||
D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */,
|
||||
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */,
|
||||
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */,
|
||||
3AAC7A022A60FE72002B50DF /* LocalizationUtilTests.swift in Sources */,
|
||||
|
BIN
damus/Assets.xcassets/zeusln.imageset/zeus.png
vendored
BIN
damus/Assets.xcassets/zeusln.imageset/zeus.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 11 KiB |
@ -686,10 +686,8 @@ struct ContentView: View {
|
||||
|
||||
let new_relay_filters = load_relay_filters(pubkey) == nil
|
||||
for relay in bootstrap_relays {
|
||||
if let url = RelayURL(relay) {
|
||||
let descriptor = RelayDescriptor(url: url, info: .rw)
|
||||
add_new_relay(model_cache: model_cache, relay_filters: relay_filters, pool: pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: settings.developer_mode)
|
||||
}
|
||||
let descriptor = RelayDescriptor(url: relay, info: .rw)
|
||||
add_new_relay(model_cache: model_cache, relay_filters: relay_filters, pool: pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: settings.developer_mode)
|
||||
}
|
||||
|
||||
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
||||
@ -888,13 +886,13 @@ func setup_notifications() {
|
||||
|
||||
struct FindEvent {
|
||||
let type: FindEventType
|
||||
let find_from: [String]?
|
||||
let find_from: [RelayURL]?
|
||||
|
||||
static func profile(pubkey: Pubkey, find_from: [String]? = nil) -> FindEvent {
|
||||
static func profile(pubkey: Pubkey, find_from: [RelayURL]? = nil) -> FindEvent {
|
||||
return FindEvent(type: .profile(pubkey), find_from: find_from)
|
||||
}
|
||||
|
||||
static func event(evid: NoteId, find_from: [String]? = nil) -> FindEvent {
|
||||
static func event(evid: NoteId, find_from: [RelayURL]? = nil) -> FindEvent {
|
||||
return FindEvent(type: .event(evid), find_from: find_from)
|
||||
}
|
||||
}
|
||||
|
@ -63,10 +63,6 @@ func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow:
|
||||
}
|
||||
|
||||
|
||||
func decode_json_relays(_ content: String) -> [String: RelayInfo]? {
|
||||
return decode_json(content)
|
||||
}
|
||||
|
||||
func decode_json_relays(_ content: String) -> [RelayURL: RelayInfo]? {
|
||||
return decode_json(content)
|
||||
}
|
||||
@ -140,7 +136,7 @@ func make_contact_relays(_ relays: [RelayDescriptor]) -> [RelayURL: RelayInfo] {
|
||||
|
||||
func make_relay_metadata(relays: [RelayDescriptor], keypair: FullKeypair) -> NostrEvent? {
|
||||
let tags = relays.compactMap { r -> [String]? in
|
||||
var tag = ["r", r.url.id]
|
||||
var tag = ["r", r.url.absoluteString]
|
||||
if (r.info.read ?? true) != (r.info.write ?? true) {
|
||||
tag += r.info.read == true ? ["read"] : ["write"]
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class DamusState: HeadlessDamusState {
|
||||
let events: EventCache
|
||||
let bookmarks: BookmarksManager
|
||||
let postbox: PostBox
|
||||
let bootstrap_relays: [String]
|
||||
let bootstrap_relays: [RelayURL]
|
||||
let replies: ReplyCounter
|
||||
let wallet: WalletModel
|
||||
let nav: NavigationCoordinator
|
||||
@ -37,7 +37,7 @@ class DamusState: HeadlessDamusState {
|
||||
let ndb: Ndb
|
||||
var purple: DamusPurple
|
||||
|
||||
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [String], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
|
||||
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
|
||||
self.pool = pool
|
||||
self.keypair = keypair
|
||||
self.likes = likes
|
||||
|
@ -77,13 +77,13 @@ class EventsModel: ObservableObject {
|
||||
state.pool.unsubscribe(sub_id: sub_id)
|
||||
}
|
||||
|
||||
private func handle_event(relay_id: String, ev: NostrEvent) {
|
||||
private func handle_event(relay_id: RelayURL, ev: NostrEvent) {
|
||||
if events.insert(ev) {
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
func handle_nostr_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
func handle_nostr_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let nev) = ev, nev.subid == self.sub_id
|
||||
else {
|
||||
return
|
||||
|
@ -53,7 +53,7 @@ class FollowersModel: ObservableObject {
|
||||
has_contact.insert(ev.pubkey)
|
||||
}
|
||||
|
||||
func load_profiles<Y>(relay_id: String, txn: NdbTxn<Y>) {
|
||||
func load_profiles<Y>(relay_id: RelayURL, txn: NdbTxn<Y>) {
|
||||
let authors = find_profiles_to_fetch_from_keys(profiles: damus_state.profiles, pks: contacts ?? [], txn: txn)
|
||||
if authors.isEmpty {
|
||||
return
|
||||
@ -64,7 +64,7 @@ class FollowersModel: ObservableObject {
|
||||
damus_state.pool.subscribe_to(sub_id: profiles_id, filters: [filter], to: [relay_id], handler: handle_event)
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let nev) = ev else {
|
||||
return
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ class FollowingModel {
|
||||
self.damus_state.pool.unsubscribe(sub_id: sub_id)
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
// don't need to do anything here really
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class HomeModel {
|
||||
// NDBTODO: let's get rid of this entirely, let nostrdb handle it
|
||||
var has_event: [String: Set<NoteId>] = [:]
|
||||
var deleted_events: Set<NoteId> = Set()
|
||||
var last_event_of_kind: [String: [UInt32: NostrEvent]] = [:]
|
||||
var last_event_of_kind: [RelayURL: [UInt32: NostrEvent]] = [:]
|
||||
var done_init: Bool = false
|
||||
var incoming_dms: [NostrEvent] = []
|
||||
let dm_debouncer = Debouncer(interval: 0.5)
|
||||
@ -135,7 +135,7 @@ class HomeModel {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
||||
func process_event(sub_id: String, relay_id: RelayURL, ev: NostrEvent) {
|
||||
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
|
||||
return
|
||||
}
|
||||
@ -211,7 +211,7 @@ class HomeModel {
|
||||
pdata.status.update_status(st)
|
||||
}
|
||||
|
||||
func handle_nwc_response(_ ev: NostrEvent, relay: String) {
|
||||
func handle_nwc_response(_ ev: NostrEvent, relay: RelayURL) {
|
||||
Task { @MainActor in
|
||||
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
|
||||
guard let nwc_str = damus_state.settings.nostr_wallet_connect,
|
||||
@ -222,7 +222,7 @@ class HomeModel {
|
||||
|
||||
// since command results are not returned for ephemeral events,
|
||||
// remove the request from the postbox which is likely failing over and over
|
||||
if damus_state.postbox.remove_relayer(relay_id: nwc.relay.id, event_id: resp.req_id) {
|
||||
if damus_state.postbox.remove_relayer(relay_id: nwc.relay, event_id: resp.req_id) {
|
||||
print("nwc: got response, removed \(resp.req_id) from the postbox [\(relay)]")
|
||||
} else {
|
||||
print("nwc: \(resp.req_id) not found in the postbox, nothing to remove [\(relay)]")
|
||||
@ -308,7 +308,7 @@ class HomeModel {
|
||||
self.deleted_events.insert(ev.id)
|
||||
}
|
||||
|
||||
func handle_contact_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
||||
func handle_contact_event(sub_id: String, relay_id: RelayURL, ev: NostrEvent) {
|
||||
process_contact_event(state: self.damus_state, ev: ev)
|
||||
|
||||
if sub_id == init_subid {
|
||||
@ -382,7 +382,7 @@ class HomeModel {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func handle_event(relay_id: String, conn_event: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, conn_event: NostrConnectionEvent) {
|
||||
switch conn_event {
|
||||
case .ws_event(let ev):
|
||||
switch ev {
|
||||
@ -400,7 +400,7 @@ class HomeModel {
|
||||
let r = pool.get_relay(relay_id),
|
||||
r.descriptor.variant == .nwc,
|
||||
let nwc = WalletConnectURL(str: nwc_str),
|
||||
nwc.relay.id == relay_id
|
||||
nwc.relay == relay_id
|
||||
{
|
||||
subscribe_to_nwc(url: nwc, pool: pool)
|
||||
}
|
||||
@ -461,14 +461,14 @@ class HomeModel {
|
||||
|
||||
|
||||
/// Send the initial filters, just our contact list mostly
|
||||
func send_initial_filters(relay_id: String) {
|
||||
func send_initial_filters(relay_id: RelayURL) {
|
||||
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
|
||||
let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid)
|
||||
pool.send(.subscribe(subscription), to: [relay_id])
|
||||
}
|
||||
|
||||
/// After initial connection or reconnect, send subscription filters for the home timeline, DMs, and notifications
|
||||
func send_home_filters(relay_id: String?) {
|
||||
func send_home_filters(relay_id: RelayURL?) {
|
||||
// TODO: since times should be based on events from a specific relay
|
||||
// perhaps we could mark this in the relay pool somehow
|
||||
|
||||
@ -529,7 +529,7 @@ class HomeModel {
|
||||
pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)), to: relay_ids)
|
||||
}
|
||||
|
||||
func get_last_of_kind(relay_id: String?) -> [UInt32: NostrEvent] {
|
||||
func get_last_of_kind(relay_id: RelayURL?) -> [UInt32: NostrEvent] {
|
||||
return relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
|
||||
}
|
||||
|
||||
@ -543,7 +543,7 @@ class HomeModel {
|
||||
return Array(friends)
|
||||
}
|
||||
|
||||
func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: String? = nil) {
|
||||
func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: RelayURL? = nil) {
|
||||
// TODO: separate likes?
|
||||
var home_filter_kinds: [NostrKind] = [
|
||||
.text, .longform, .boost
|
||||
@ -619,7 +619,7 @@ class HomeModel {
|
||||
migrate_old_muted_threads_to_new_mutelist(keypair: damus_state.keypair, damus_state: damus_state)
|
||||
}
|
||||
|
||||
func get_last_event_of_kind(relay_id: String, kind: UInt32) -> NostrEvent? {
|
||||
func get_last_event_of_kind(relay_id: RelayURL, kind: UInt32) -> NostrEvent? {
|
||||
guard let m = last_event_of_kind[relay_id] else {
|
||||
last_event_of_kind[relay_id] = [:]
|
||||
return nil
|
||||
@ -883,23 +883,23 @@ func process_contact_event(state: DamusState, ev: NostrEvent) {
|
||||
}
|
||||
|
||||
func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
||||
let bootstrap_dict: [String: RelayInfo] = [:]
|
||||
let bootstrap_dict: [RelayURL: RelayInfo] = [:]
|
||||
let old_decoded = m_old_ev.flatMap { decode_json_relays($0.content) } ?? state.bootstrap_relays.reduce(into: bootstrap_dict) { (d, r) in
|
||||
d[r] = .rw
|
||||
}
|
||||
|
||||
guard let decoded: [String: RelayInfo] = decode_json_relays(ev.content) else {
|
||||
guard let decoded: [RelayURL: RelayInfo] = decode_json_relays(ev.content) else {
|
||||
return
|
||||
}
|
||||
|
||||
var changed = false
|
||||
|
||||
var new = Set<String>()
|
||||
var new = Set<RelayURL>()
|
||||
for key in decoded.keys {
|
||||
new.insert(key)
|
||||
}
|
||||
|
||||
var old = Set<String>()
|
||||
var old = Set<RelayURL>()
|
||||
for key in old_decoded.keys {
|
||||
old.insert(key)
|
||||
}
|
||||
@ -910,10 +910,8 @@ func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
||||
for d in diff {
|
||||
changed = true
|
||||
if new.contains(d) {
|
||||
if let url = RelayURL(d) {
|
||||
let descriptor = RelayDescriptor(url: url, info: decoded[d] ?? .rw)
|
||||
add_new_relay(model_cache: state.relay_model_cache, relay_filters: state.relay_filters, pool: state.pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: state.settings.developer_mode)
|
||||
}
|
||||
let descriptor = RelayDescriptor(url: d, info: decoded[d] ?? .rw)
|
||||
add_new_relay(model_cache: state.relay_model_cache, relay_filters: state.relay_filters, pool: state.pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: state.settings.developer_mode)
|
||||
} else {
|
||||
state.pool.remove_relay(d)
|
||||
}
|
||||
@ -930,7 +928,7 @@ func add_new_relay(model_cache: RelayModelCache, relay_filters: RelayFilters, po
|
||||
try? pool.add_relay(descriptor)
|
||||
let url = descriptor.url
|
||||
|
||||
let relay_id = url.id
|
||||
let relay_id = url
|
||||
guard model_cache.model(withURL: url) == nil else {
|
||||
return
|
||||
}
|
||||
@ -956,8 +954,8 @@ func add_new_relay(model_cache: RelayModelCache, relay_filters: RelayFilters, po
|
||||
}
|
||||
}
|
||||
|
||||
func fetch_relay_metadata(relay_id: String) async throws -> RelayMetadata? {
|
||||
var urlString = relay_id.replacingOccurrences(of: "wss://", with: "https://")
|
||||
func fetch_relay_metadata(relay_id: RelayURL) async throws -> RelayMetadata? {
|
||||
var urlString = relay_id.absoluteString.replacingOccurrences(of: "wss://", with: "https://")
|
||||
urlString = urlString.replacingOccurrences(of: "ws://", with: "http://")
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
|
@ -10,7 +10,7 @@ import Foundation
|
||||
class ProfileModel: ObservableObject, Equatable {
|
||||
@Published var contacts: NostrEvent? = nil
|
||||
@Published var following: Int = 0
|
||||
@Published var relays: [String: RelayInfo]? = nil
|
||||
@Published var relays: [RelayURL: RelayInfo]? = nil
|
||||
@Published var progress: Int = 0
|
||||
|
||||
private let MAX_SHARE_RELAYS = 4
|
||||
@ -109,7 +109,7 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
seen_event.insert(ev.id)
|
||||
}
|
||||
|
||||
private func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
private func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
switch ev {
|
||||
case .ws_event:
|
||||
return
|
||||
@ -143,7 +143,7 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func findRelaysHandler(relay_id: String, ev: NostrConnectionEvent) {
|
||||
private func findRelaysHandler(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
if case .nostr_event(let resp) = ev, case .event(_, let event) = resp, case .contacts = event.known_kind {
|
||||
self.relays = decode_json_relays(event.content)
|
||||
}
|
||||
@ -160,12 +160,8 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
damus.pool.unsubscribe(sub_id: findRelay_subid)
|
||||
}
|
||||
|
||||
func getRelayStrings() -> [String] {
|
||||
return relays?.keys.map {$0} ?? []
|
||||
}
|
||||
|
||||
func getCappedRelayStrings() -> [String] {
|
||||
return relays?.keys.prefix(MAX_SHARE_RELAYS).map { $0 } ?? []
|
||||
return relays?.keys.prefix(MAX_SHARE_RELAYS).map { $0.absoluteString } ?? []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,12 +45,12 @@ class SearchHomeModel: ObservableObject {
|
||||
damus_state.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays)
|
||||
}
|
||||
|
||||
func unsubscribe(to: String? = nil) {
|
||||
func unsubscribe(to: RelayURL? = nil) {
|
||||
loading = false
|
||||
damus_state.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] })
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let event) = conn_ev else {
|
||||
return
|
||||
}
|
||||
@ -129,7 +129,7 @@ enum PubkeysToLoad {
|
||||
case from_keys([Pubkey])
|
||||
}
|
||||
|
||||
func load_profiles<Y>(context: String, profiles_subid: String, relay_id: String, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn<Y>) {
|
||||
func load_profiles<Y>(context: String, profiles_subid: String, relay_id: RelayURL, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn<Y>) {
|
||||
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events, txn: txn)
|
||||
|
||||
guard !authors.isEmpty else {
|
||||
|
@ -66,7 +66,7 @@ class SearchModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
let (sub_id, done) = handle_subid_event(pool: state.pool, relay_id: relay_id, ev: ev) { sub_id, ev in
|
||||
if ev.is_textlike && ev.should_show_event {
|
||||
self.add_event(ev)
|
||||
@ -107,7 +107,7 @@ func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func handle_subid_event(pool: RelayPool, relay_id: String, ev: NostrConnectionEvent, handle: (String, NostrEvent) -> ()) -> (String?, Bool) {
|
||||
func handle_subid_event(pool: RelayPool, relay_id: RelayURL, ev: NostrConnectionEvent, handle: (String, NostrEvent) -> ()) -> (String?, Bool) {
|
||||
switch ev {
|
||||
case .ws_event:
|
||||
return (nil, false)
|
||||
|
@ -103,7 +103,7 @@ class ThreadModel: ObservableObject {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
|
||||
let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in
|
||||
guard subids.contains(sid) else {
|
||||
|
@ -39,7 +39,7 @@ class ZapsModel: ObservableObject {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let resp) = conn_ev else {
|
||||
return
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
func make_auth_request(keypair: FullKeypair, challenge_string: String, relay: Relay) -> NostrEvent? {
|
||||
let tags: [[String]] = [["relay", relay.descriptor.url.id],["challenge", challenge_string]]
|
||||
let tags: [[String]] = [["relay", relay.descriptor.url.absoluteString],["challenge", challenge_string]]
|
||||
let event = NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 22242, tags: tags)
|
||||
return event
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import Foundation
|
||||
func make_zap_request_event(keypair: FullKeypair, content: String, relays: [RelayDescriptor], target: ZapTarget, zap_type: ZapType) -> MakeZapRequest? {
|
||||
var tags = zap_target_to_tags(target)
|
||||
var relay_tag = ["relays"]
|
||||
relay_tag.append(contentsOf: relays.map { $0.url.id })
|
||||
relay_tag.append(contentsOf: relays.map { $0.url.absoluteString })
|
||||
tags.append(relay_tag)
|
||||
|
||||
var kp = keypair
|
||||
@ -79,7 +79,7 @@ func make_private_zap_request_event(identity: FullKeypair, enc_key: FullKeypair,
|
||||
func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
|
||||
let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey)
|
||||
let rw_relay_info = RelayInfo(read: true, write: true)
|
||||
var relays: [String: RelayInfo] = [:]
|
||||
var relays: [RelayURL: RelayInfo] = [:]
|
||||
|
||||
for relay in bootstrap_relays {
|
||||
relays[relay] = rw_relay_info
|
||||
|
@ -119,8 +119,8 @@ class Relay: Identifiable {
|
||||
return (flags & RelayFlags.broken.rawValue) == RelayFlags.broken.rawValue
|
||||
}
|
||||
|
||||
var id: String {
|
||||
return get_relay_id(descriptor.url)
|
||||
var id: RelayURL {
|
||||
return descriptor.url
|
||||
}
|
||||
|
||||
}
|
||||
@ -128,15 +128,3 @@ class Relay: Identifiable {
|
||||
enum RelayError: Error {
|
||||
case RelayAlreadyExists
|
||||
}
|
||||
|
||||
func get_relay_id(_ url: RelayURL) -> String {
|
||||
let trimTrailingSlashes: (String) -> String = { url in
|
||||
var trimmedUrl = url
|
||||
while trimmedUrl.hasSuffix("/") {
|
||||
trimmedUrl.removeLast()
|
||||
}
|
||||
return trimmedUrl
|
||||
}
|
||||
|
||||
return trimTrailingSlashes(url.url.absoluteString)
|
||||
}
|
||||
|
@ -21,19 +21,19 @@ final class RelayConnection: ObservableObject {
|
||||
private(set) var last_connection_attempt: TimeInterval = 0
|
||||
private(set) var last_pong: Date? = nil
|
||||
private(set) var backoff: TimeInterval = 1.0
|
||||
private lazy var socket = WebSocket(url.url)
|
||||
private lazy var socket = WebSocket(relay_url.url)
|
||||
private var subscriptionToken: AnyCancellable?
|
||||
|
||||
private var handleEvent: (NostrConnectionEvent) -> ()
|
||||
private var processEvent: (WebSocketEvent) -> ()
|
||||
private let url: RelayURL
|
||||
private let relay_url: RelayURL
|
||||
var log: RelayLog?
|
||||
|
||||
init(url: RelayURL,
|
||||
handleEvent: @escaping (NostrConnectionEvent) -> (),
|
||||
processEvent: @escaping (WebSocketEvent) -> ())
|
||||
{
|
||||
self.url = url
|
||||
self.relay_url = url
|
||||
self.handleEvent = handleEvent
|
||||
self.processEvent = processEvent
|
||||
}
|
||||
@ -48,7 +48,7 @@ final class RelayConnection: ObservableObject {
|
||||
self.last_pong = .now
|
||||
self.log?.add("Successful ping")
|
||||
} else {
|
||||
print("pong failed, reconnecting \(self.url.id)")
|
||||
print("pong failed, reconnecting \(self.relay_url.id)")
|
||||
self.isConnected = false
|
||||
self.isConnecting = false
|
||||
self.reconnect_with_backoff()
|
||||
@ -126,7 +126,7 @@ final class RelayConnection: ObservableObject {
|
||||
self.receive(message: message)
|
||||
case .disconnected(let closeCode, let reason):
|
||||
if closeCode != .normalClosure {
|
||||
print("⚠️ Warning: RelayConnection (\(self.url)) closed with code \(closeCode), reason: \(String(describing: reason))")
|
||||
print("⚠️ Warning: RelayConnection (\(self.relay_url)) closed with code \(closeCode), reason: \(String(describing: reason))")
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.isConnected = false
|
||||
@ -134,7 +134,7 @@ final class RelayConnection: ObservableObject {
|
||||
self.reconnect()
|
||||
}
|
||||
case .error(let error):
|
||||
print("⚠️ Warning: RelayConnection (\(self.url)) error: \(error)")
|
||||
print("⚠️ Warning: RelayConnection (\(self.relay_url)) error: \(error)")
|
||||
let nserr = error as NSError
|
||||
if nserr.domain == NSPOSIXErrorDomain && nserr.code == 57 {
|
||||
// ignore socket not connected?
|
||||
|
@ -13,7 +13,7 @@ import UIKit
|
||||
/// will have information to help developers debug issues.
|
||||
final class RelayLog: ObservableObject {
|
||||
private static let line_limit = 250
|
||||
private let relay_url: URL?
|
||||
private let relay_url: RelayURL?
|
||||
private lazy var formatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
@ -29,7 +29,7 @@ final class RelayLog: ObservableObject {
|
||||
/// - Parameter relay_url: the relay url the log represents. Pass nil for the url to create
|
||||
/// a RelayLog that does nothing. This is required to allow RelayLog to be used as a StateObject,
|
||||
/// because they cannot be Optional.
|
||||
init(_ relay_url: URL? = nil) {
|
||||
init(_ relay_url: RelayURL? = nil) {
|
||||
self.relay_url = relay_url
|
||||
|
||||
setUp()
|
||||
|
@ -10,17 +10,17 @@ import Network
|
||||
|
||||
struct RelayHandler {
|
||||
let sub_id: String
|
||||
let callback: (String, NostrConnectionEvent) -> ()
|
||||
let callback: (RelayURL, NostrConnectionEvent) -> ()
|
||||
}
|
||||
|
||||
struct QueuedRequest {
|
||||
let req: NostrRequestType
|
||||
let relay: String
|
||||
let relay: RelayURL
|
||||
let skip_ephemeral: Bool
|
||||
}
|
||||
|
||||
struct SeenEvent: Hashable {
|
||||
let relay_id: String
|
||||
let relay_id: RelayURL
|
||||
let evid: NoteId
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ class RelayPool {
|
||||
var handlers: [RelayHandler] = []
|
||||
var request_queue: [QueuedRequest] = []
|
||||
var seen: Set<SeenEvent> = Set()
|
||||
var counts: [String: UInt64] = [:]
|
||||
var counts: [RelayURL: UInt64] = [:]
|
||||
var ndb: Ndb
|
||||
var keypair: Keypair?
|
||||
var message_received_function: (((String, RelayDescriptor)) -> Void)?
|
||||
@ -94,7 +94,7 @@ class RelayPool {
|
||||
}
|
||||
}
|
||||
|
||||
func register_handler(sub_id: String, handler: @escaping (String, NostrConnectionEvent) -> ()) {
|
||||
func register_handler(sub_id: String, handler: @escaping (RelayURL, NostrConnectionEvent) -> ()) {
|
||||
for handler in handlers {
|
||||
// don't add duplicate handlers
|
||||
if handler.sub_id == sub_id {
|
||||
@ -105,7 +105,7 @@ class RelayPool {
|
||||
print("registering \(sub_id) handler, current: \(self.handlers.count)")
|
||||
}
|
||||
|
||||
func remove_relay(_ relay_id: String) {
|
||||
func remove_relay(_ relay_id: RelayURL) {
|
||||
var i: Int = 0
|
||||
|
||||
self.disconnect(to: [relay_id])
|
||||
@ -122,12 +122,11 @@ class RelayPool {
|
||||
}
|
||||
|
||||
func add_relay(_ desc: RelayDescriptor) throws {
|
||||
let url = desc.url
|
||||
let relay_id = get_relay_id(url)
|
||||
let relay_id = desc.url
|
||||
if get_relay(relay_id) != nil {
|
||||
throw RelayError.RelayAlreadyExists
|
||||
}
|
||||
let conn = RelayConnection(url: url, handleEvent: { event in
|
||||
let conn = RelayConnection(url: desc.url, handleEvent: { event in
|
||||
self.handle_event(relay_id: relay_id, event: event)
|
||||
}, processEvent: { wsev in
|
||||
guard case .message(let msg) = wsev,
|
||||
@ -141,7 +140,7 @@ class RelayPool {
|
||||
self.relays.append(relay)
|
||||
}
|
||||
|
||||
func setLog(_ log: RelayLog, for relay_id: String) {
|
||||
func setLog(_ log: RelayLog, for relay_id: RelayURL) {
|
||||
// add the current network state to the log
|
||||
log.add("Network state: \(network_monitor.currentPath.status)")
|
||||
|
||||
@ -156,7 +155,7 @@ class RelayPool {
|
||||
let is_connecting = c.isConnecting
|
||||
|
||||
if is_connecting && (Date.now.timeIntervalSince1970 - c.last_connection_attempt) > 5 {
|
||||
print("stale connection detected (\(relay.descriptor.url.url.absoluteString)). retrying...")
|
||||
print("stale connection detected (\(relay.descriptor.url.absoluteString)). retrying...")
|
||||
relay.connection.reconnect()
|
||||
} else if relay.is_broken || is_connecting || c.isConnected {
|
||||
continue
|
||||
@ -167,7 +166,7 @@ class RelayPool {
|
||||
}
|
||||
}
|
||||
|
||||
func reconnect(to: [String]? = nil) {
|
||||
func reconnect(to: [RelayURL]? = nil) {
|
||||
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||
for relay in relays {
|
||||
// don't try to reconnect to broken relays
|
||||
@ -175,38 +174,38 @@ class RelayPool {
|
||||
}
|
||||
}
|
||||
|
||||
func connect(to: [String]? = nil) {
|
||||
func connect(to: [RelayURL]? = nil) {
|
||||
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||
for relay in relays {
|
||||
relay.connection.connect()
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect(to: [String]? = nil) {
|
||||
func disconnect(to: [RelayURL]? = nil) {
|
||||
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||
for relay in relays {
|
||||
relay.connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
func unsubscribe(sub_id: String, to: [String]? = nil) {
|
||||
func unsubscribe(sub_id: String, to: [RelayURL]? = nil) {
|
||||
if to == nil {
|
||||
self.remove_handler(sub_id: sub_id)
|
||||
}
|
||||
self.send(.unsubscribe(sub_id), to: to)
|
||||
}
|
||||
|
||||
func subscribe(sub_id: String, filters: [NostrFilter], handler: @escaping (String, NostrConnectionEvent) -> (), to: [String]? = nil) {
|
||||
func subscribe(sub_id: String, filters: [NostrFilter], handler: @escaping (RelayURL, NostrConnectionEvent) -> (), to: [RelayURL]? = nil) {
|
||||
register_handler(sub_id: sub_id, handler: handler)
|
||||
send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to)
|
||||
}
|
||||
|
||||
func subscribe_to(sub_id: String, filters: [NostrFilter], to: [String]?, handler: @escaping (String, NostrConnectionEvent) -> ()) {
|
||||
func subscribe_to(sub_id: String, filters: [NostrFilter], to: [RelayURL]?, handler: @escaping (RelayURL, NostrConnectionEvent) -> ()) {
|
||||
register_handler(sub_id: sub_id, handler: handler)
|
||||
send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to)
|
||||
}
|
||||
|
||||
func count_queued(relay: String) -> Int {
|
||||
func count_queued(relay: RelayURL) -> Int {
|
||||
var c = 0
|
||||
for request in request_queue {
|
||||
if request.relay == relay {
|
||||
@ -217,7 +216,7 @@ class RelayPool {
|
||||
return c
|
||||
}
|
||||
|
||||
func queue_req(r: NostrRequestType, relay: String, skip_ephemeral: Bool) {
|
||||
func queue_req(r: NostrRequestType, relay: RelayURL, skip_ephemeral: Bool) {
|
||||
let count = count_queued(relay: relay)
|
||||
guard count <= 10 else {
|
||||
print("can't queue, too many queued events for \(relay)")
|
||||
@ -228,7 +227,7 @@ class RelayPool {
|
||||
request_queue.append(QueuedRequest(req: r, relay: relay, skip_ephemeral: skip_ephemeral))
|
||||
}
|
||||
|
||||
func send_raw(_ req: NostrRequestType, to: [String]? = nil, skip_ephemeral: Bool = true) {
|
||||
func send_raw(_ req: NostrRequestType, to: [RelayURL]? = nil, skip_ephemeral: Bool = true) {
|
||||
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||
|
||||
// send to local relay (nostrdb)
|
||||
@ -265,20 +264,20 @@ class RelayPool {
|
||||
}
|
||||
}
|
||||
|
||||
func send(_ req: NostrRequest, to: [String]? = nil, skip_ephemeral: Bool = true) {
|
||||
func send(_ req: NostrRequest, to: [RelayURL]? = nil, skip_ephemeral: Bool = true) {
|
||||
send_raw(.typical(req), to: to, skip_ephemeral: skip_ephemeral)
|
||||
}
|
||||
|
||||
func get_relays(_ ids: [String]) -> [Relay] {
|
||||
func get_relays(_ ids: [RelayURL]) -> [Relay] {
|
||||
// don't include ephemeral relays in the default list to query
|
||||
relays.filter { ids.contains($0.id) }
|
||||
}
|
||||
|
||||
func get_relay(_ id: String) -> Relay? {
|
||||
func get_relay(_ id: RelayURL) -> Relay? {
|
||||
relays.first(where: { $0.id == id })
|
||||
}
|
||||
|
||||
func run_queue(_ relay_id: String) {
|
||||
func run_queue(_ relay_id: RelayURL) {
|
||||
self.request_queue = request_queue.reduce(into: Array<QueuedRequest>()) { (q, req) in
|
||||
guard req.relay == relay_id else {
|
||||
q.append(req)
|
||||
@ -290,7 +289,7 @@ class RelayPool {
|
||||
}
|
||||
}
|
||||
|
||||
func record_seen(relay_id: String, event: NostrConnectionEvent) {
|
||||
func record_seen(relay_id: RelayURL, event: NostrConnectionEvent) {
|
||||
if case .nostr_event(let ev) = event {
|
||||
if case .event(_, let nev) = ev {
|
||||
let k = SeenEvent(relay_id: relay_id, evid: nev.id)
|
||||
@ -306,7 +305,7 @@ class RelayPool {
|
||||
}
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, event: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, event: NostrConnectionEvent) {
|
||||
record_seen(relay_id: relay_id, event: event)
|
||||
|
||||
// run req queue when we reconnect
|
||||
@ -349,10 +348,7 @@ class RelayPool {
|
||||
}
|
||||
}
|
||||
|
||||
func add_rw_relay(_ pool: RelayPool, _ url: String) {
|
||||
guard let url = RelayURL(url) else {
|
||||
return
|
||||
}
|
||||
func add_rw_relay(_ pool: RelayPool, _ url: RelayURL) {
|
||||
try? pool.add_relay(RelayDescriptor(url: url, info: .rw))
|
||||
}
|
||||
|
||||
|
@ -7,15 +7,28 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable {
|
||||
public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable, Identifiable, Comparable, CustomStringConvertible {
|
||||
private(set) var url: URL
|
||||
|
||||
var id: String {
|
||||
public var id: URL {
|
||||
return url
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return self.absoluteString
|
||||
}
|
||||
|
||||
public var absoluteString: String {
|
||||
return url.absoluteString
|
||||
}
|
||||
|
||||
init?(_ str: String) {
|
||||
guard let url = URL(string: str) else {
|
||||
var trimmed_url_str = str
|
||||
while trimmed_url_str.hasSuffix("/") {
|
||||
trimmed_url_str.removeLast()
|
||||
}
|
||||
|
||||
guard let url = URL(string: trimmed_url_str) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -68,6 +81,11 @@ public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable {
|
||||
hasher.combine(self.url)
|
||||
}
|
||||
|
||||
// MARK: - Comparable
|
||||
public static func < (lhs: RelayURL, rhs: RelayURL) -> Bool {
|
||||
return lhs.url.absoluteString < rhs.url.absoluteString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private struct StringKey: CodingKey {
|
||||
|
@ -9,12 +9,12 @@ import Foundation
|
||||
|
||||
|
||||
class Relayer {
|
||||
let relay: String
|
||||
let relay: RelayURL
|
||||
var attempts: Int
|
||||
var retry_after: Double
|
||||
var last_attempt: Int64?
|
||||
|
||||
init(relay: String, attempts: Int, retry_after: Double) {
|
||||
init(relay: RelayURL, attempts: Int, retry_after: Double) {
|
||||
self.relay = relay
|
||||
self.attempts = attempts
|
||||
self.retry_after = retry_after
|
||||
@ -35,7 +35,7 @@ class PostedEvent {
|
||||
var flushed_once: Bool
|
||||
let on_flush: OnFlush?
|
||||
|
||||
init(event: NostrEvent, remaining: [String], skip_ephemeral: Bool, flush_after: Date?, on_flush: OnFlush?) {
|
||||
init(event: NostrEvent, remaining: [RelayURL], skip_ephemeral: Bool, flush_after: Date?, on_flush: OnFlush?) {
|
||||
self.event = event
|
||||
self.skip_ephemeral = skip_ephemeral
|
||||
self.flush_after = flush_after
|
||||
@ -101,7 +101,7 @@ class PostBox {
|
||||
}
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, _ ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, _ ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let resp) = ev else {
|
||||
return
|
||||
}
|
||||
@ -114,7 +114,7 @@ class PostBox {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func remove_relayer(relay_id: String, event_id: NoteId) -> Bool {
|
||||
func remove_relayer(relay_id: RelayURL, event_id: NoteId) -> Bool {
|
||||
guard let ev = self.events[event_id] else {
|
||||
return false
|
||||
}
|
||||
@ -159,13 +159,13 @@ class PostBox {
|
||||
}
|
||||
}
|
||||
|
||||
func send(_ event: NostrEvent, to: [String]? = nil, skip_ephemeral: Bool = true, delay: TimeInterval? = nil, on_flush: OnFlush? = nil) {
|
||||
func send(_ event: NostrEvent, to: [RelayURL]? = nil, skip_ephemeral: Bool = true, delay: TimeInterval? = nil, on_flush: OnFlush? = nil) {
|
||||
// Don't add event if we already have it
|
||||
if events[event.id] != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let remaining = to ?? pool.our_descriptors.map { $0.url.id }
|
||||
let remaining = to ?? pool.our_descriptors.map { $0.url }
|
||||
let after = delay.map { d in Date.now.addingTimeInterval(d) }
|
||||
let posted_ev = PostedEvent(event: event, remaining: remaining, skip_ephemeral: skip_ephemeral, flush_after: after, on_flush: on_flush)
|
||||
|
||||
|
@ -37,13 +37,13 @@ func bootstrap_relays_setting_key(pubkey: Pubkey) -> String {
|
||||
return pk_setting_key(pubkey, key: "bootstrap_relays")
|
||||
}
|
||||
|
||||
func save_bootstrap_relays(pubkey: Pubkey, relays: [String]) {
|
||||
func save_bootstrap_relays(pubkey: Pubkey, relays: [RelayURL]) {
|
||||
let key = bootstrap_relays_setting_key(pubkey: pubkey)
|
||||
|
||||
UserDefaults.standard.set(relays, forKey: key)
|
||||
UserDefaults.standard.set(relays.map({ $0.absoluteString }), forKey: key)
|
||||
}
|
||||
|
||||
func load_bootstrap_relays(pubkey: Pubkey) -> [String] {
|
||||
func load_bootstrap_relays(pubkey: Pubkey) -> [RelayURL] {
|
||||
let key = bootstrap_relays_setting_key(pubkey: pubkey)
|
||||
|
||||
guard let relays = UserDefaults.standard.stringArray(forKey: key) else {
|
||||
@ -56,16 +56,18 @@ func load_bootstrap_relays(pubkey: Pubkey) -> [String] {
|
||||
return get_default_bootstrap_relays().map { $0 }
|
||||
}
|
||||
|
||||
let loaded_relays = Array(Set(relays + get_default_bootstrap_relays()))
|
||||
let relay_urls = relays.compactMap({ RelayURL($0) })
|
||||
|
||||
let loaded_relays = Array(Set(relay_urls + get_default_bootstrap_relays()))
|
||||
print("Loading custom bootstrap relays: \(loaded_relays)")
|
||||
return loaded_relays
|
||||
}
|
||||
|
||||
func get_default_bootstrap_relays() -> [String] {
|
||||
var default_bootstrap_relays = BOOTSTRAP_RELAYS
|
||||
func get_default_bootstrap_relays() -> [RelayURL] {
|
||||
var default_bootstrap_relays: [RelayURL] = BOOTSTRAP_RELAYS.compactMap({ RelayURL($0) })
|
||||
|
||||
if let user_region = Locale.current.region, let regional_bootstrap_relays = REGION_SPECIFIC_BOOTSTRAP_RELAYS[user_region] {
|
||||
default_bootstrap_relays.append(contentsOf: regional_bootstrap_relays)
|
||||
default_bootstrap_relays.append(contentsOf: regional_bootstrap_relays.compactMap({ RelayURL($0) }))
|
||||
}
|
||||
|
||||
return default_bootstrap_relays
|
||||
|
@ -9,9 +9,9 @@ import Foundation
|
||||
|
||||
struct RelayFilter: Hashable {
|
||||
let timeline: Timeline
|
||||
let relay_id: String
|
||||
let relay_id: RelayURL
|
||||
|
||||
init(timeline: Timeline, relay_id: String) {
|
||||
init(timeline: Timeline, relay_id: RelayURL) {
|
||||
self.timeline = timeline
|
||||
self.relay_id = relay_id
|
||||
}
|
||||
@ -21,13 +21,13 @@ class RelayFilters {
|
||||
private let our_pubkey: Pubkey
|
||||
private var disabled: Set<RelayFilter>
|
||||
|
||||
func is_filtered(timeline: Timeline, relay_id: String) -> Bool {
|
||||
func is_filtered(timeline: Timeline, relay_id: RelayURL) -> Bool {
|
||||
let filter = RelayFilter(timeline: timeline, relay_id: relay_id)
|
||||
let contains = disabled.contains(filter)
|
||||
return contains
|
||||
}
|
||||
|
||||
func remove(timeline: Timeline, relay_id: String) {
|
||||
func remove(timeline: Timeline, relay_id: RelayURL) {
|
||||
let filter = RelayFilter(timeline: timeline, relay_id: relay_id)
|
||||
if !disabled.contains(filter) {
|
||||
return
|
||||
@ -37,7 +37,7 @@ class RelayFilters {
|
||||
save_relay_filters(our_pubkey, filters: disabled)
|
||||
}
|
||||
|
||||
func insert(timeline: Timeline, relay_id: String) {
|
||||
func insert(timeline: Timeline, relay_id: RelayURL) {
|
||||
let filter = RelayFilter(timeline: timeline, relay_id: relay_id)
|
||||
if disabled.contains(filter) {
|
||||
return
|
||||
@ -77,13 +77,16 @@ func load_relay_filters(_ pubkey: Pubkey) -> Set<RelayFilter>? {
|
||||
guard let timeline = Timeline.init(rawValue: parts[0]) else {
|
||||
return
|
||||
}
|
||||
let filter = RelayFilter(timeline: timeline, relay_id: parts[1])
|
||||
guard let relay_id = RelayURL(parts[1]) else {
|
||||
return
|
||||
}
|
||||
let filter = RelayFilter(timeline: timeline, relay_id: relay_id)
|
||||
s.insert(filter)
|
||||
}
|
||||
}
|
||||
|
||||
func determine_to_relays(pool: RelayPool, filters: RelayFilters) -> [String] {
|
||||
func determine_to_relays(pool: RelayPool, filters: RelayFilters) -> [RelayURL] {
|
||||
return pool.our_descriptors
|
||||
.map { $0.url.url.absoluteString }
|
||||
.map { $0.url }
|
||||
.filter { !filters.is_filtered(timeline: .search, relay_id: $0) }
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ final class RelayModel: Hashable {
|
||||
|
||||
init(_ url: RelayURL, metadata: RelayMetadata) {
|
||||
self.url = url
|
||||
self.log = RelayLog(url.url)
|
||||
self.log = RelayLog(url)
|
||||
self.metadata = metadata
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,8 @@ final class RelayModelCache: ObservableObject {
|
||||
models[url]
|
||||
}
|
||||
|
||||
func model(with_relay_id url_string: String) -> RelayModel? {
|
||||
guard let url = RelayURL(url_string) else {
|
||||
return nil
|
||||
}
|
||||
return model(withURL: url)
|
||||
func model(with_relay_id url_string: RelayURL) -> RelayModel? {
|
||||
return model(withURL: url_string)
|
||||
}
|
||||
|
||||
func insert(model: RelayModel) {
|
||||
|
@ -11,8 +11,8 @@ enum Route: Hashable {
|
||||
case ProfileByKey(pubkey: Pubkey)
|
||||
case Profile(profile: ProfileModel, followers: FollowersModel)
|
||||
case Followers(followers: FollowersModel)
|
||||
case Relay(relay: String, showActionButtons: Binding<Bool>)
|
||||
case RelayDetail(relay: String, metadata: RelayMetadata?)
|
||||
case Relay(relay: RelayURL, showActionButtons: Binding<Bool>)
|
||||
case RelayDetail(relay: RelayURL, metadata: RelayMetadata?)
|
||||
case Following(following: FollowingModel)
|
||||
case MuteList
|
||||
case RelayConfig
|
||||
@ -21,7 +21,7 @@ enum Route: Hashable {
|
||||
case Config
|
||||
case EditMetadata
|
||||
case DMChat(dms: DirectMessageModel)
|
||||
case UserRelays(relays: [String])
|
||||
case UserRelays(relays: [RelayURL])
|
||||
case KeySettings(keypair: Keypair)
|
||||
case AppearanceSettings(settings: UserSettingsStore)
|
||||
case NotificationSettings(settings: UserSettingsStore)
|
||||
|
@ -38,7 +38,7 @@ func subscribe_to_nwc(url: WalletConnectURL, pool: RelayPool) {
|
||||
filter.limit = 0
|
||||
let sub = NostrSubscribe(filters: [filter], sub_id: "nwc")
|
||||
|
||||
pool.send(.subscribe(sub), to: [url.relay.id], skip_ephemeral: false)
|
||||
pool.send(.subscribe(sub), to: [url.relay], skip_ephemeral: false)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@ -50,7 +50,7 @@ func nwc_pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: Str
|
||||
|
||||
try? pool.add_relay(.nwc(url: url.relay))
|
||||
subscribe_to_nwc(url: url, pool: pool)
|
||||
post.send(ev, to: [url.relay.id], skip_ephemeral: false, delay: delay, on_flush: on_flush)
|
||||
post.send(ev, to: [url.relay], skip_ephemeral: false, delay: delay, on_flush: on_flush)
|
||||
return ev
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ struct WalletConnectURL: Equatable {
|
||||
urlComponents.scheme = "nostrwalletconnect"
|
||||
urlComponents.host = pubkey.hex()
|
||||
urlComponents.queryItems = [
|
||||
URLQueryItem(name: "relay", value: relay.id),
|
||||
URLQueryItem(name: "relay", value: relay.absoluteString),
|
||||
URLQueryItem(name: "secret", value: keypair.privkey.hex())
|
||||
]
|
||||
|
||||
|
@ -103,7 +103,7 @@ struct AddRelayView: View {
|
||||
return
|
||||
}
|
||||
|
||||
state.pool.connect(to: [new_relay])
|
||||
state.pool.connect(to: [url])
|
||||
|
||||
if let new_ev = add_relay(ev: ev, keypair: keypair, current_relays: state.pool.our_descriptors, relay: url, info: info) {
|
||||
process_contact_event(state: state, ev: ev)
|
||||
|
@ -32,7 +32,7 @@ struct EventLoaderView<Content: View>: View {
|
||||
damus_state.pool.send(.subscribe(.init(filters: filters, sub_id: subscription_uuid)))
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let nostr_response) = ev else {
|
||||
return
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class SuggestedUsersViewModel: ObservableObject {
|
||||
damus_state.pool.subscribe(sub_id: sub_id, filters: [filter], handler: handle_event)
|
||||
}
|
||||
|
||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
guard case .nostr_event(let nev) = ev else {
|
||||
return
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ struct RelayFilterView: View {
|
||||
.padding(.bottom, 0)
|
||||
|
||||
List(Array(relays), id: \.url.id) { relay in
|
||||
RelayToggle(state: state, timeline: timeline, relay_id: relay.url.url.absoluteString)
|
||||
RelayToggle(state: state, timeline: timeline, relay_id: relay.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +40,7 @@ struct RelayConfigView: View {
|
||||
let rs: [RelayDescriptor] = []
|
||||
let recommended_relay_addresses = get_default_bootstrap_relays()
|
||||
return recommended_relay_addresses.reduce(into: rs) { xs, x in
|
||||
if let url = RelayURL(x) {
|
||||
xs.append(RelayDescriptor(url: url, info: .rw))
|
||||
}
|
||||
xs.append(RelayDescriptor(url: x, info: .rw))
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +134,7 @@ struct RelayConfigView: View {
|
||||
|
||||
ForEach(relayList, id: \.url) { relay in
|
||||
Group {
|
||||
RelayView(state: state, relay: relay.url.id, showActionButtons: $showActionButtons, recommended: recommended)
|
||||
RelayView(state: state, relay: relay.url, showActionButtons: $showActionButtons, recommended: recommended)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
@ -9,14 +9,14 @@ import SwiftUI
|
||||
|
||||
struct RelayDetailView: View {
|
||||
let state: DamusState
|
||||
let relay: String
|
||||
let relay: RelayURL
|
||||
let nip11: RelayMetadata?
|
||||
|
||||
@ObservedObject var log: RelayLog
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
init(state: DamusState, relay: String, nip11: RelayMetadata?) {
|
||||
init(state: DamusState, relay: RelayURL, nip11: RelayMetadata?) {
|
||||
self.state = state
|
||||
self.relay = relay
|
||||
self.nip11 = nip11
|
||||
@ -48,8 +48,7 @@ struct RelayDetailView: View {
|
||||
}
|
||||
|
||||
let descriptors = state.pool.our_descriptors
|
||||
guard let relay_url = RelayURL(relay),
|
||||
let new_ev = remove_relay( ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else {
|
||||
guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, keypair: keypair, relay: relay) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -78,8 +77,7 @@ struct RelayDetailView: View {
|
||||
guard let ev_before_add = state.contacts.event else {
|
||||
return
|
||||
}
|
||||
guard let relay_url = RelayURL(relay),
|
||||
let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else {
|
||||
guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else {
|
||||
return
|
||||
}
|
||||
process_contact_event(state: state, ev: ev_after_add)
|
||||
@ -114,7 +112,7 @@ struct RelayDetailView: View {
|
||||
if let relay_connection {
|
||||
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
||||
HStack {
|
||||
Text(relay)
|
||||
Text(relay.absoluteString)
|
||||
Spacer()
|
||||
RelayStatusView(connection: relay_connection)
|
||||
}
|
||||
@ -166,7 +164,7 @@ struct RelayDetailView: View {
|
||||
.onReceive(handle_notify(.switched_timeline)) { notif in
|
||||
dismiss()
|
||||
}
|
||||
.navigationTitle(nip11?.name ?? relay)
|
||||
.navigationTitle(nip11?.name ?? relay.absoluteString)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.navigationBarItems(leading: BackNav())
|
||||
@ -202,6 +200,6 @@ struct RelayDetailView: View {
|
||||
struct RelayDetailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let metadata = RelayMetadata(name: "name", description: "desc", pubkey: test_pubkey, contact: "contact", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com", icon: "")
|
||||
RelayDetailView(state: test_damus_state, relay: "relay", nip11: metadata)
|
||||
RelayDetailView(state: test_damus_state, relay: RelayURL("wss://relay.damus.io")!, nip11: metadata)
|
||||
}
|
||||
}
|
||||
|
@ -66,13 +66,13 @@ struct InnerRelayPicView: View {
|
||||
}
|
||||
|
||||
struct RelayPicView: View {
|
||||
let relay: String
|
||||
let relay: RelayURL
|
||||
let icon: String?
|
||||
let size: CGFloat
|
||||
let highlight: Highlight
|
||||
let disable_animation: Bool
|
||||
|
||||
init(relay: String, icon: String? = nil, size: CGFloat, highlight: Highlight, disable_animation: Bool) {
|
||||
init(relay: RelayURL, icon: String? = nil, size: CGFloat, highlight: Highlight, disable_animation: Bool) {
|
||||
self.relay = relay
|
||||
self.icon = icon
|
||||
self.size = size
|
||||
@ -106,10 +106,10 @@ func extract_tld(_ host: String) -> String {
|
||||
return host
|
||||
}
|
||||
|
||||
func get_relay_url(relay: String, icon: String?) -> URL? {
|
||||
var favicon = relay + "/favicon.ico"
|
||||
let tld = extract_tld(relay)
|
||||
if tld != relay {
|
||||
func get_relay_url(relay: RelayURL, icon: String?) -> URL? {
|
||||
var favicon = relay.absoluteString + "/favicon.ico"
|
||||
let tld = extract_tld(relay.absoluteString)
|
||||
if tld != relay.absoluteString {
|
||||
favicon = "https://" + tld + "/favicon.ico"
|
||||
}
|
||||
let pic = icon ?? favicon
|
||||
@ -119,10 +119,10 @@ func get_relay_url(relay: String, icon: String?) -> URL? {
|
||||
struct RelayPicView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
RelayPicView(relay: "wss://relay.damus.io", size: 55, highlight: .none, disable_animation: false)
|
||||
RelayPicView(relay: "wss://nostr.wine", size: 55, highlight: .none, disable_animation: false)
|
||||
RelayPicView(relay: "wss://nos.lol", size: 55, highlight: .none, disable_animation: false)
|
||||
RelayPicView(relay: "fail", size: 55, highlight: .none, disable_animation: false)
|
||||
RelayPicView(relay: RelayURL("wss://relay.damus.io")!, size: 55, highlight: .none, disable_animation: false)
|
||||
RelayPicView(relay: RelayURL("wss://nostr.wine")!, size: 55, highlight: .none, disable_animation: false)
|
||||
RelayPicView(relay: RelayURL("wss://nos.lol")!, size: 55, highlight: .none, disable_animation: false)
|
||||
RelayPicView(relay: RelayURL("wss://fail.com")!, size: 55, highlight: .none, disable_animation: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ struct RelayStatusView: View {
|
||||
|
||||
struct RelayStatusView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let connection = test_damus_state.pool.get_relay("wss://relay.damus.io")!.connection
|
||||
let connection = test_damus_state.pool.get_relay(RelayURL("wss://relay.damus.io")!)!.connection
|
||||
RelayStatusView(connection: connection)
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ import SwiftUI
|
||||
struct RelayToggle: View {
|
||||
let state: DamusState
|
||||
let timeline: Timeline
|
||||
let relay_id: String
|
||||
let relay_id: RelayURL
|
||||
|
||||
func toggle_binding(relay_id: String) -> Binding<Bool> {
|
||||
func toggle_binding(relay_id: RelayURL) -> Binding<Bool> {
|
||||
return Binding(get: {
|
||||
!state.relay_filters.is_filtered(timeline: timeline, relay_id: relay_id)
|
||||
}, set: { on in
|
||||
@ -30,7 +30,7 @@ struct RelayToggle: View {
|
||||
RelayStatusView(connection: relay_connection)
|
||||
}
|
||||
RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay_id)?.metadata.is_paid ?? false)
|
||||
Toggle(relay_id, isOn: toggle_binding(relay_id: relay_id))
|
||||
Toggle(relay_id.absoluteString, isOn: toggle_binding(relay_id: relay_id))
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
@ -42,7 +42,7 @@ struct RelayToggle: View {
|
||||
|
||||
struct RelayToggle_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RelayToggle(state: test_damus_state, timeline: .search, relay_id: "wss://jb55.com")
|
||||
RelayToggle(state: test_damus_state, timeline: .search, relay_id: RelayURL("wss://jb55.com")!)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
@ -9,14 +9,14 @@ import SwiftUI
|
||||
|
||||
struct RelayView: View {
|
||||
let state: DamusState
|
||||
let relay: String
|
||||
let relay: RelayURL
|
||||
let recommended: Bool
|
||||
@ObservedObject private var model_cache: RelayModelCache
|
||||
|
||||
@State var relay_state: Bool
|
||||
@Binding var showActionButtons: Bool
|
||||
|
||||
init(state: DamusState, relay: String, showActionButtons: Binding<Bool>, recommended: Bool) {
|
||||
init(state: DamusState, relay: RelayURL, showActionButtons: Binding<Bool>, recommended: Bool) {
|
||||
self.state = state
|
||||
self.relay = relay
|
||||
self.recommended = recommended
|
||||
@ -26,7 +26,7 @@ struct RelayView: View {
|
||||
self._relay_state = State(initialValue: relay_state)
|
||||
}
|
||||
|
||||
static func get_relay_state(pool: RelayPool, relay: String) -> Bool {
|
||||
static func get_relay_state(pool: RelayPool, relay: RelayURL) -> Bool {
|
||||
return pool.get_relay(relay) == nil
|
||||
}
|
||||
|
||||
@ -45,18 +45,18 @@ struct RelayView: View {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text(meta?.name ?? relay)
|
||||
Text(meta?.name ?? relay.url.host() ?? relay.url.absoluteString)
|
||||
.font(.headline)
|
||||
.padding(.bottom, 2)
|
||||
.lineLimit(1)
|
||||
RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false)
|
||||
}
|
||||
Text(relay)
|
||||
Text(relay.absoluteString)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
.lineLimit(1)
|
||||
.contextMenu {
|
||||
CopyAction(relay: relay)
|
||||
CopyAction(relay: relay.absoluteString)
|
||||
|
||||
if let privkey = state.keypair.privkey {
|
||||
RemoveButton(privkey: privkey, showText: true)
|
||||
@ -113,8 +113,7 @@ struct RelayView: View {
|
||||
guard let ev_before_add = state.contacts.event else {
|
||||
return
|
||||
}
|
||||
guard let relay_url = RelayURL(relay),
|
||||
let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else {
|
||||
guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else {
|
||||
return
|
||||
}
|
||||
process_contact_event(state: state, ev: ev_after_add)
|
||||
@ -132,8 +131,7 @@ struct RelayView: View {
|
||||
|
||||
let descriptors = state.pool.our_descriptors
|
||||
guard let keypair = state.keypair.to_full(),
|
||||
let relay_url = RelayURL(relay),
|
||||
let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else {
|
||||
let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -182,6 +180,6 @@ struct RelayView: View {
|
||||
|
||||
struct RelayView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RelayView(state: test_damus_state, relay: "wss://relay.damus.io", showActionButtons: .constant(false), recommended: false)
|
||||
RelayView(state: test_damus_state, relay: RelayURL("wss://relay.damus.io")!, showActionButtons: .constant(false), recommended: false)
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ struct SaveKeysView: View {
|
||||
self.pool.connect()
|
||||
}
|
||||
|
||||
func handle_event(relay: String, ev: NostrConnectionEvent) {
|
||||
func handle_event(relay: RelayURL, ev: NostrConnectionEvent) {
|
||||
switch ev {
|
||||
case .ws_event(let wsev):
|
||||
switch wsev {
|
||||
|
@ -9,18 +9,18 @@ import SwiftUI
|
||||
|
||||
struct UserRelaysView: View {
|
||||
let state: DamusState
|
||||
let relays: [String]
|
||||
let relays: [RelayURL]
|
||||
|
||||
@State var relay_state: [(String, Bool)]
|
||||
@State var relay_state: [(RelayURL, Bool)]
|
||||
|
||||
init(state: DamusState, relays: [String]) {
|
||||
init(state: DamusState, relays: [RelayURL]) {
|
||||
self.state = state
|
||||
self.relays = relays
|
||||
let relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: relays)
|
||||
self._relay_state = State(initialValue: relay_state)
|
||||
}
|
||||
|
||||
static func make_relay_state(pool: RelayPool, relays: [String]) -> [(String, Bool)] {
|
||||
static func make_relay_state(pool: RelayPool, relays: [RelayURL]) -> [(RelayURL, Bool)] {
|
||||
return relays.map({ r in
|
||||
return (r, pool.get_relay(r) == nil)
|
||||
}).sorted { (a, b) in a.0 < b.0 }
|
||||
|
@ -55,7 +55,7 @@ struct ConnectWalletView: View {
|
||||
.fontWeight(.bold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(nwc.relay.id)
|
||||
Text(nwc.relay.absoluteString)
|
||||
.font(.body)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
@ -97,7 +97,7 @@ struct ConnectWalletView: View {
|
||||
}
|
||||
|
||||
MutinyButton() {
|
||||
openURL(URL(string:"https://app.mutinywallet.com/settings/connections")!)
|
||||
openURL(URL(string:"https://app.mutinywallet.com/settings/connections?callbackUri=nostr%2bwalletconnect&name=Damus")!)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
|
@ -34,7 +34,7 @@ struct WalletView: View {
|
||||
|
||||
Divider()
|
||||
|
||||
RelayView(state: damus_state, relay: nwc.relay.id, showActionButtons: .constant(false), recommended: false)
|
||||
RelayView(state: damus_state, relay: nwc.relay, showActionButtons: .constant(false), recommended: false)
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 125, alignment: .top)
|
||||
.padding(.horizontal, 10)
|
||||
|
84
damusTests/RelayURLTests.swift
Normal file
84
damusTests/RelayURLTests.swift
Normal file
@ -0,0 +1,84 @@
|
||||
//
|
||||
// RelayURLTests.swift
|
||||
// damusTests
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2024-03-20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
@testable import damus
|
||||
|
||||
final class RelayURLTests : XCTestCase {
|
||||
func testRelayURLTrailingSlash() {
|
||||
let relay_url_1: RelayURL = RelayURL("wss://relay.damus.io")!
|
||||
let relay_url_2: RelayURL = RelayURL("wss://relay.damus.io/")!
|
||||
|
||||
XCTAssertEqual(relay_url_1.id, relay_url_2.id, "Relays with the same address should have the same ID even if one of them was initialized with a trailing slash")
|
||||
XCTAssertEqual(relay_url_1, relay_url_2, "Relays with the same address should be equal even if one of them was initialized with a trailing slash")
|
||||
|
||||
var relays: [RelayURL: Int] = [:]
|
||||
relays[relay_url_1] = 1
|
||||
relays[relay_url_2] = 2
|
||||
|
||||
XCTAssertEqual(relays[relay_url_1], 2, "RelayURL with a trailing slash should evaluate to the same hash in a dictionary as an equivalent one without trailing slashes")
|
||||
}
|
||||
|
||||
func testRelayURLDifferentProtocols() {
|
||||
let relay_url_1: RelayURL = RelayURL("wss://relay.damus.io")!
|
||||
let relay_url_2: RelayURL = RelayURL("ws://relay.damus.io")!
|
||||
|
||||
XCTAssertNotEqual(relay_url_1.id, relay_url_2.id, "Relays with different protocols should not have the same ID")
|
||||
XCTAssertNotEqual(relay_url_1, relay_url_2, "Relays with different protocols should not be equal")
|
||||
|
||||
var relays: [RelayURL: Int] = [:]
|
||||
relays[relay_url_1] = 1
|
||||
relays[relay_url_2] = 2
|
||||
|
||||
XCTAssertNotEqual(relays[relay_url_1], relays[relay_url_2], "RelayURL with different protocols should not evaluate to the same hash in a dictionary")
|
||||
}
|
||||
|
||||
func testRelayURLDifferentDomains() {
|
||||
let relay_url_1: RelayURL = RelayURL("wss://relay.damus.io")!
|
||||
let relay_url_3: RelayURL = RelayURL("wss://example.com")!
|
||||
|
||||
XCTAssertNotEqual(relay_url_1, relay_url_3, "Relays with different domains should not be equal")
|
||||
|
||||
var relays: [RelayURL: Int] = [:]
|
||||
relays[relay_url_1] = 1
|
||||
relays[relay_url_3] = 3
|
||||
|
||||
XCTAssertNotEqual(relays[relay_url_1], relays[relay_url_3], "RelayURL with different domains should not evaluate to the same hash in a dictionary")
|
||||
}
|
||||
|
||||
func testRelayURLDifferentPaths() {
|
||||
let relay_url_1: RelayURL = RelayURL("wss://relay.damus.io")!
|
||||
let relay_url_2: RelayURL = RelayURL("wss://relay.damus.io/")!
|
||||
let relay_url_3: RelayURL = RelayURL("wss://relay.damus.io/v1")!
|
||||
let relay_url_4: RelayURL = RelayURL("wss://relay.damus.io/v2")!
|
||||
let relay_url_5: RelayURL = RelayURL("wss://relay.damus.io/v2/beta")!
|
||||
let relay_url_6: RelayURL = RelayURL("wss://relay.damus.io/v2/beta/")!
|
||||
|
||||
XCTAssertEqual(relay_url_1.id, relay_url_2.id, "Relays with the same address should have the same ID even if one of them was initialized with a trailing slash")
|
||||
XCTAssertEqual(relay_url_1, relay_url_2, "Relays with the same address should be equal even if one of them was initialized with a trailing slash")
|
||||
|
||||
XCTAssertNotEqual(relay_url_1, relay_url_3, "Relays with different paths should not be equal")
|
||||
XCTAssertNotEqual(relay_url_3, relay_url_4, "Relays with different paths should not be equal")
|
||||
XCTAssertNotEqual(relay_url_4, relay_url_5, "Relays with different subpaths should not be equal")
|
||||
XCTAssertEqual(relay_url_5, relay_url_6, "Relays with the same address should be equal if one of them is initialized with a trailing slash")
|
||||
|
||||
var relays: [RelayURL: Int] = [:]
|
||||
relays[relay_url_1] = 1
|
||||
relays[relay_url_2] = 2
|
||||
relays[relay_url_3] = 3
|
||||
relays[relay_url_4] = 4
|
||||
relays[relay_url_5] = 5
|
||||
relays[relay_url_6] = 6
|
||||
|
||||
XCTAssertEqual(relays[relay_url_1], relays[relay_url_2], "RelayURL with the same path should evaluate to the same hash in a dictionary")
|
||||
XCTAssertNotEqual(relays[relay_url_1], relays[relay_url_3], "RelayURLs with different pathsshould not evaluate to the same hash in a dictionary")
|
||||
XCTAssertNotEqual(relays[relay_url_3], relays[relay_url_4], "RelayURLs with different paths should not evaluate to the same hash in a dictionary")
|
||||
XCTAssertNotEqual(relays[relay_url_4], relays[relay_url_5], "RelayURLs with different subpaths should not evaluate to the same hash in a dictionary")
|
||||
XCTAssertEqual(relays[relay_url_5], relays[relay_url_6], "RelayURL with the same subpath should evaluate to the same hash in a dictionary even if one of them is initialized with a trailing slash")
|
||||
}
|
||||
}
|
@ -55,7 +55,7 @@ final class WalletConnectTests: XCTestCase {
|
||||
XCTAssertEqual(url.pubkey, pk)
|
||||
XCTAssertEqual(url.keypair.privkey, sec)
|
||||
XCTAssertEqual(url.keypair.pubkey, privkey_to_pubkey(privkey: sec))
|
||||
XCTAssertEqual(url.relay.id, relay)
|
||||
XCTAssertEqual(url.relay.url.absoluteString, relay)
|
||||
XCTAssertEqual(url.lud16, "jb55@jb55.com")
|
||||
|
||||
// Test an NWC url format which is NIP-47 and RFC 3986 compliant
|
||||
@ -76,7 +76,7 @@ final class WalletConnectTests: XCTestCase {
|
||||
XCTAssertEqual(url_2.pubkey, pk_2)
|
||||
XCTAssertEqual(url_2.keypair.privkey, sec_2)
|
||||
XCTAssertEqual(url_2.keypair.pubkey, privkey_to_pubkey(privkey: sec_2))
|
||||
XCTAssertEqual(url_2.relay.id, relay_2)
|
||||
XCTAssertEqual(url_2.relay.url.absoluteString, relay_2)
|
||||
}
|
||||
|
||||
func testNWCEphemeralRelay() {
|
||||
@ -92,12 +92,12 @@ final class WalletConnectTests: XCTestCase {
|
||||
XCTAssertEqual(pool.our_descriptors.count, 0)
|
||||
XCTAssertEqual(pool.all_descriptors.count, 1)
|
||||
XCTAssertEqual(pool.all_descriptors[0].variant, .nwc)
|
||||
XCTAssertEqual(pool.all_descriptors[0].url.id, "ws://127.0.0.1")
|
||||
XCTAssertEqual(pool.all_descriptors[0].url.url.absoluteString, "ws://127.0.0.1")
|
||||
XCTAssertEqual(box.events.count, 1)
|
||||
let ev = box.events.first!.value
|
||||
XCTAssertEqual(ev.skip_ephemeral, false)
|
||||
XCTAssertEqual(ev.remaining.count, 1)
|
||||
XCTAssertEqual(ev.remaining[0].relay, "ws://127.0.0.1")
|
||||
XCTAssertEqual(ev.remaining[0].relay.url.absoluteString, "ws://127.0.0.1")
|
||||
}
|
||||
|
||||
func testPerformanceExample() throws {
|
||||
|
@ -114,8 +114,8 @@ class damusTests: XCTestCase {
|
||||
func testSaveRelayFilters() {
|
||||
var filters = Set<RelayFilter>()
|
||||
|
||||
let filter1 = RelayFilter(timeline: .search, relay_id: "wss://abc.com")
|
||||
let filter2 = RelayFilter(timeline: .home, relay_id: "wss://abc.com")
|
||||
let filter1 = RelayFilter(timeline: .search, relay_id: RelayURL("wss://abc.com")!)
|
||||
let filter2 = RelayFilter(timeline: .home, relay_id: RelayURL("wss://abc.com")!)
|
||||
filters.insert(filter1)
|
||||
filters.insert(filter2)
|
||||
|
||||
|
@ -1,24 +1,5 @@
|
||||
# Contributing
|
||||
|
||||
[Email patches][git-send-email] to patches@damus.io are preferred, but we
|
||||
accept PRs on GitHub as well. Patches sent via email may include a bolt11
|
||||
lightning invoice, choosing the price you think the patch is worth, and
|
||||
we will pay it once the patch is accepted and if I think the price isn't
|
||||
unreasonable. You can also send an any-amount invoice and I will pay what
|
||||
I think it's worth if you prefer not to choose. You can include the
|
||||
bolt11 in the commit body or email so that it can be paid once it is
|
||||
applied.
|
||||
|
||||
Recommended settings when submitting code via email:
|
||||
|
||||
```
|
||||
$ git config sendemail.to "patches@damus.io"
|
||||
$ git config format.subjectPrefix "PATCH damus"
|
||||
$ git config format.signOff yes
|
||||
```
|
||||
|
||||
You can subscribe to the [patches mailing list][patches-ml] to help review code.
|
||||
|
||||
## Submitting patches
|
||||
|
||||
*Most of this comes from the linux kernel guidelines for submitting
|
||||
@ -44,20 +25,6 @@ long, that's a sign that you probably need to split up your patch. See
|
||||
the dedicated `Separate your changes` section because this is very
|
||||
important.
|
||||
|
||||
When you submit or resubmit a patch or patch series, include the complete
|
||||
patch description and justification for it. Each new version should use
|
||||
the -v2,v3,vN option on git-send-email for each new patch revision. Don't
|
||||
just say that this is version N of the patch (series). Don't expect the
|
||||
reviewer to refer back to earlier patch versions or referenced URLs to
|
||||
find the patch description and put that into the patch. I.e., the patch
|
||||
(series) and its description should be self-contained. This benefits both
|
||||
the maintainers and reviewers. Some reviewers probably didn't even
|
||||
receive earlier versions of the patch.
|
||||
|
||||
When submitting a -v2 of more than one patch, ensure that you include all of
|
||||
the original patches, don't just send a v2 of one of the patches. If you
|
||||
are dropping a patch, mention it in the `patch changelog`.
|
||||
|
||||
Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
|
||||
instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
|
||||
to do frotz", as if you are giving orders to the codebase to change
|
||||
@ -113,10 +80,6 @@ The point to remember is that each patch should make an easily understood
|
||||
change that can be verified by reviewers. Each patch should be justifiable
|
||||
on its own merits.
|
||||
|
||||
If one patch depends on another patch in order for a change to be
|
||||
complete, that is OK. Simply note **"this patch depends on patch X"**
|
||||
in your patch description.
|
||||
|
||||
When dividing your change into a series of patches, take special care to
|
||||
ensure that the Damus builds and runs properly after each patch in the
|
||||
series. Developers using ``git bisect`` to track down a problem can end
|
||||
@ -127,101 +90,12 @@ If you cannot condense your patch set into a smaller set of patches,
|
||||
then only post say 15 or so at a time and wait for review and integration.
|
||||
|
||||
Include `patch changelogs` which describe what has changed between the v1 and
|
||||
v2 version of the patch. Please put this information **after** the `---` line
|
||||
which separates the changelog from the rest of the patch. The version
|
||||
information is not part of the changelog which gets committed to the git tree.
|
||||
It is additional information for the reviewers. If it's placed above the commit
|
||||
tags, it needs manual interaction to remove it. If it is below the separator
|
||||
line, it gets automatically stripped off when applying the patch::
|
||||
|
||||
<commit message>
|
||||
...
|
||||
Signed-off-by: Author <author@mail>
|
||||
---
|
||||
V2 -> V3: Removed redundant helper function
|
||||
V1 -> V2: Cleaned up coding style and addressed review comments
|
||||
|
||||
path/to/file | 5+++--
|
||||
...
|
||||
|
||||
|
||||
### Select the recipients for your patch
|
||||
|
||||
You should always copy the appropriate people on any patch to code that
|
||||
they may have been involved with. You can use
|
||||
[git-contacts][git-contacts] to find people who have touched the code
|
||||
previously:
|
||||
|
||||
$ git format-patch --cover-letter -o patches origin/master..my-feature
|
||||
$ git send-email --dry-run --cc-cmd=git-contacts patches/*
|
||||
|
||||
patches@damus.io should be used by default for all patches.
|
||||
|
||||
William Casarin is the final arbiter of all changes accepted into the
|
||||
Damus. His email address is <jb55@jb55.com>.
|
||||
|
||||
If you have a patch that fixes an exploitable security bug, send that
|
||||
patch to jb55@jb55.com. For severe bugs, a short embargo may be
|
||||
considered to allow distributors to get the patch out to users; in such
|
||||
cases, obviously, the patch should not be sent to any public lists.
|
||||
|
||||
### No MIME, no links, no compression, no attachments. Just plain text.
|
||||
|
||||
Will and other Damus developers need to be able to read and comment
|
||||
on the changes you are submitting. It is important for a Damus
|
||||
developer to be able to "quote" your changes, using standard e-mail
|
||||
tools, so that they may comment on specific portions of your code.
|
||||
|
||||
For this reason, all patches should be submitted by e-mail "inline". The
|
||||
easiest way to do this is with `git send-email`, which is strongly
|
||||
recommended. An interactive tutorial for `git send-email` is available at
|
||||
[git-send-email][git-send-email]
|
||||
|
||||
### Respond to review comments
|
||||
|
||||
Your patch will almost certainly get comments from reviewers on ways in
|
||||
which the patch can be improved, in the form of a reply to your email. You must
|
||||
respond to those comments; ignoring reviewers is a good way to get ignored in
|
||||
return. You can simply reply to their emails to answer their comments. Review
|
||||
comments or questions that do not lead to a code change should almost certainly
|
||||
bring about a comment or changelog entry so that the next reviewer better
|
||||
understands what is going on.
|
||||
|
||||
Be sure to tell the reviewers what changes you are making and to thank them
|
||||
for their time. Code review is a tiring and time-consuming process, and
|
||||
reviewers sometimes get grumpy. Even in that case, though, respond
|
||||
politely and address the problems they have pointed out. When sending a next
|
||||
version, add a `patch changelog` to the cover letter or to individual patches
|
||||
explaining difference against previous submission (see `The canonical patch format`)
|
||||
|
||||
|
||||
### Use trimmed interleaved replies in email discussions
|
||||
|
||||
Top-posting is strongly discouraged in Damus development
|
||||
discussions. Interleaved (or "inline") replies make conversations much
|
||||
easier to follow. For more details see: [Posting style][posting-style]
|
||||
|
||||
As is frequently quoted on the mailing list:
|
||||
|
||||
A: http://en.wikipedia.org/wiki/Top_post
|
||||
Q: Were do I find info about this thing called top-posting?
|
||||
A: Because it messes up the order in which people normally read text.
|
||||
Q: Why is top-posting such a bad thing?
|
||||
A: Top-posting.
|
||||
Q: What is the most annoying thing in e-mail?
|
||||
|
||||
Similarly, please trim all unneeded quotations that aren't relevant
|
||||
to your reply. This makes responses easier to find, and saves time and
|
||||
space. For more details see: http://daringfireball.net/2007/07/on_top
|
||||
|
||||
A: No.
|
||||
Q: Should I include quotations after my reply?
|
||||
|
||||
v2 version of the patch.
|
||||
|
||||
### Sign your work - the Developer's Certificate of Origin
|
||||
|
||||
To improve tracking of who did what, especially with patches that can
|
||||
percolate to their final resting place in the kernel through several
|
||||
percolate to their final resting place in the Damus through several
|
||||
layers of maintainers, we've introduced a "sign-off" procedure on
|
||||
patches that are being emailed around.
|
||||
|
||||
@ -284,134 +158,3 @@ changelogs, please include:
|
||||
The changelog script will pick these up and give you attribution for your
|
||||
change
|
||||
|
||||
### When to use Acked-by:, Cc:, and Co-developed-by:
|
||||
|
||||
The Signed-off-by: tag indicates that the signer was involved in the
|
||||
development of the patch, or that he/she was in the patch's delivery path.
|
||||
|
||||
If a person was not directly involved in the preparation or handling of a
|
||||
patch but wishes to signify and record their approval of it then they can
|
||||
ask to have an Acked-by: line added to the patch's changelog.
|
||||
|
||||
Acked-by: is often used by the maintainer of the affected code when that
|
||||
maintainer neither contributed to nor forwarded the patch.
|
||||
|
||||
Acked-by: is not as formal as Signed-off-by:. It is a record that the acker
|
||||
has at least reviewed the patch and has indicated acceptance. Hence patch
|
||||
mergers will sometimes manually convert an acker's "yep, looks good to me"
|
||||
into an Acked-by: (but note that it is usually better to ask for an
|
||||
explicit ack).
|
||||
|
||||
Acked-by: does not necessarily indicate acknowledgement of the entire patch.
|
||||
For example, if a patch affects multiple subsystems and has an Acked-by: from
|
||||
one subsystem maintainer then this usually indicates acknowledgement of just
|
||||
the part which affects that maintainer's code. Judgement should be used here.
|
||||
When in doubt people should refer to the original discussion in the mailing
|
||||
list archives.
|
||||
|
||||
If a person has had the opportunity to comment on a patch, but has not
|
||||
provided such comments, you may optionally add a ``Cc:`` tag to the patch.
|
||||
This is the only tag which might be added without an explicit action by the
|
||||
person it names - but it should indicate that this person was copied on the
|
||||
patch. This tag documents that potentially interested parties
|
||||
have been included in the discussion.
|
||||
|
||||
Co-developed-by: states that the patch was co-created by multiple developers;
|
||||
it is used to give attribution to co-authors (in addition to the author
|
||||
attributed by the From: tag) when several people work on a single patch.
|
||||
|
||||
### Using Reported-by:, Tested-by:, Reviewed-by:, Suggested-by: and Fixes:
|
||||
|
||||
The Reported-by tag gives credit to people who find bugs and report them and it
|
||||
hopefully inspires them to help us again in the future. The tag is intended for
|
||||
bugs; please do not use it to credit feature requests. The tag should be
|
||||
followed by a Closes: tag pointing to the report, unless the report is not
|
||||
available on the web. The Link: tag can be used instead of Closes: if the patch
|
||||
fixes a part of the issue(s) being reported. Please note that if the bug was
|
||||
reported in private, then ask for permission first before using the Reported-by
|
||||
tag.
|
||||
|
||||
A Tested-by: tag indicates that the patch has been successfully tested (in
|
||||
some environment) by the person named. This tag informs maintainers that
|
||||
some testing has been performed, provides a means to locate testers for
|
||||
future patches, and ensures credit for the testers.
|
||||
|
||||
Reviewed-by:, instead, indicates that the patch has been reviewed and found
|
||||
acceptable according to the Reviewer's Statement:
|
||||
|
||||
A Reviewed-by tag is a statement of opinion that the patch is an
|
||||
appropriate modification of Damus and related libraries without any
|
||||
remaining serious technical issues. Any interested reviewer (who has
|
||||
done the work) can offer a Reviewed-by tag for a patch. This tag serves
|
||||
to give credit to reviewers and to inform maintainers of the degree of
|
||||
review which has been done on the patch. Reviewed-by: tags, when
|
||||
supplied by reviewers known to understand the subject area and to perform
|
||||
thorough reviews, will normally increase the likelihood of your patch
|
||||
getting into Damus.
|
||||
|
||||
Both Tested-by and Reviewed-by tags, once received on mailing list from tester
|
||||
or reviewer, should be added by author to the applicable patches when sending
|
||||
next versions. However if the patch has changed substantially in following
|
||||
version, these tags might not be applicable anymore and thus should be removed.
|
||||
Usually removal of someone's Tested-by or Reviewed-by tags should be mentioned
|
||||
in the patch changelog (after the '---' separator).
|
||||
|
||||
A Suggested-by: tag indicates that the patch idea is suggested by the person
|
||||
named and ensures credit to the person for the idea. Please note that this
|
||||
tag should not be added without the reporter's permission, especially if the
|
||||
idea was not posted in a public forum. That said, if we diligently credit our
|
||||
idea reporters, they will, hopefully, be inspired to help us again in the
|
||||
future.
|
||||
|
||||
### Explicit In-Reply-To headers
|
||||
|
||||
It can be helpful to manually add In-Reply-To: headers to a patch
|
||||
(e.g., when using ``git send-email``) to associate the patch with
|
||||
previous relevant discussion, e.g. to link a bug fix to the email with
|
||||
the bug report. However, for a multi-patch series, it is generally
|
||||
best to avoid using In-Reply-To: to link to older versions of the
|
||||
series. This way multiple versions of the patch don't become an
|
||||
unmanageable forest of references in email clients.
|
||||
|
||||
### Providing base tree information
|
||||
|
||||
When other developers receive your patches and start the review process,
|
||||
it is often useful for them to know where in the tree history they
|
||||
should place your work. This is particularly useful for automated CI
|
||||
processes that attempt to run a series of tests in order to establish
|
||||
the quality of your submission before the maintainer starts the review.
|
||||
|
||||
If you are using `git format-patch` to generate your patches, you can
|
||||
automatically include the base tree information in your submission by
|
||||
using the `--base` flag. The easiest and most convenient way to use
|
||||
this option is with topical branches:
|
||||
|
||||
$ git checkout -t -b my-topical-branch master
|
||||
Branch 'my-topical-branch' set up to track local branch 'master'.
|
||||
Switched to a new branch 'my-topical-branch'
|
||||
|
||||
[perform your edits and commits]
|
||||
|
||||
$ git format-patch --base=auto --cover-letter -o outgoing/ master
|
||||
outgoing/0000-cover-letter.patch
|
||||
outgoing/0001-First-Commit.patch
|
||||
outgoing/...
|
||||
|
||||
When you open `outgoing/0000-cover-letter.patch` for editing, you will
|
||||
notice that it will have the `base-commit:` trailer at the very
|
||||
bottom, which provides the reviewer and the CI tools enough information
|
||||
to properly perform `git am` without worrying about conflicts::
|
||||
|
||||
$ git checkout -b patch-review [base-commit-id]
|
||||
Switched to a new branch 'patch-review'
|
||||
$ git am patches.mbox
|
||||
Applying: First Commit
|
||||
Applying: ...
|
||||
|
||||
Please see ``man git-format-patch`` for more information about this
|
||||
option.
|
||||
|
||||
[git-contacts]: https://github.com/git/git/blob/master/contrib/contacts/git-contacts
|
||||
[git-send-email]: http://git-send-email.io
|
||||
[patches-ml]: https://damus.io/list/patches
|
||||
[posting-style]: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
|
||||
|
@ -338,13 +338,14 @@ public func nscript_pool_send_to(interp: UnsafeMutablePointer<wasm_interp>?, pre
|
||||
|
||||
guard let script = interp_nostrscript(interp: interp),
|
||||
let req_str = asm_str(cstr: preq, len: req_len),
|
||||
let to = asm_str(cstr: to, len: to_len)
|
||||
let to = asm_str(cstr: to, len: to_len),
|
||||
let to_relay_url = RelayURL(to)
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
script.pool.send_raw(.custom(req_str), to: [to], skip_ephemeral: false)
|
||||
script.pool.send_raw(.custom(req_str), to: [to_relay_url], skip_ephemeral: false)
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
Loading…
Reference in New Issue
Block a user