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 */; };
|
D723411A2B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; };
|
||||||
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; };
|
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; };
|
||||||
D724D8272B64B40B00ABE789 /* DamusPurpleAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D724D8262B64B40B00ABE789 /* DamusPurpleAccountView.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 */; };
|
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
||||||
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
||||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; };
|
||||||
@ -2544,6 +2546,7 @@
|
|||||||
E0E024102B7C19C20075735D /* TranslationTests.swift */,
|
E0E024102B7C19C20075735D /* TranslationTests.swift */,
|
||||||
E06336A92B75832100A88E6B /* ImageMetadataTest.swift */,
|
E06336A92B75832100A88E6B /* ImageMetadataTest.swift */,
|
||||||
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */,
|
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */,
|
||||||
|
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */,
|
||||||
);
|
);
|
||||||
path = damusTests;
|
path = damusTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -3503,6 +3506,7 @@
|
|||||||
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */,
|
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */,
|
||||||
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
||||||
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
||||||
|
D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */,
|
||||||
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */,
|
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */,
|
||||||
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */,
|
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */,
|
||||||
3AAC7A022A60FE72002B50DF /* LocalizationUtilTests.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
|
let new_relay_filters = load_relay_filters(pubkey) == nil
|
||||||
for relay in bootstrap_relays {
|
for relay in bootstrap_relays {
|
||||||
if let url = RelayURL(relay) {
|
let descriptor = RelayDescriptor(url: relay, info: .rw)
|
||||||
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)
|
||||||
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)
|
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
||||||
@ -888,13 +886,13 @@ func setup_notifications() {
|
|||||||
|
|
||||||
struct FindEvent {
|
struct FindEvent {
|
||||||
let type: FindEventType
|
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)
|
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)
|
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]? {
|
func decode_json_relays(_ content: String) -> [RelayURL: RelayInfo]? {
|
||||||
return decode_json(content)
|
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? {
|
func make_relay_metadata(relays: [RelayDescriptor], keypair: FullKeypair) -> NostrEvent? {
|
||||||
let tags = relays.compactMap { r -> [String]? in
|
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) {
|
if (r.info.read ?? true) != (r.info.write ?? true) {
|
||||||
tag += r.info.read == true ? ["read"] : ["write"]
|
tag += r.info.read == true ? ["read"] : ["write"]
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
let events: EventCache
|
let events: EventCache
|
||||||
let bookmarks: BookmarksManager
|
let bookmarks: BookmarksManager
|
||||||
let postbox: PostBox
|
let postbox: PostBox
|
||||||
let bootstrap_relays: [String]
|
let bootstrap_relays: [RelayURL]
|
||||||
let replies: ReplyCounter
|
let replies: ReplyCounter
|
||||||
let wallet: WalletModel
|
let wallet: WalletModel
|
||||||
let nav: NavigationCoordinator
|
let nav: NavigationCoordinator
|
||||||
@ -37,7 +37,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
let ndb: Ndb
|
let ndb: Ndb
|
||||||
var purple: DamusPurple
|
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.pool = pool
|
||||||
self.keypair = keypair
|
self.keypair = keypair
|
||||||
self.likes = likes
|
self.likes = likes
|
||||||
|
@ -77,13 +77,13 @@ class EventsModel: ObservableObject {
|
|||||||
state.pool.unsubscribe(sub_id: sub_id)
|
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) {
|
if events.insert(ev) {
|
||||||
objectWillChange.send()
|
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
|
guard case .nostr_event(let nev) = ev, nev.subid == self.sub_id
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
|
@ -53,7 +53,7 @@ class FollowersModel: ObservableObject {
|
|||||||
has_contact.insert(ev.pubkey)
|
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)
|
let authors = find_profiles_to_fetch_from_keys(profiles: damus_state.profiles, pks: contacts ?? [], txn: txn)
|
||||||
if authors.isEmpty {
|
if authors.isEmpty {
|
||||||
return
|
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)
|
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 {
|
guard case .nostr_event(let nev) = ev else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ class FollowingModel {
|
|||||||
self.damus_state.pool.unsubscribe(sub_id: sub_id)
|
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
|
// 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
|
// NDBTODO: let's get rid of this entirely, let nostrdb handle it
|
||||||
var has_event: [String: Set<NoteId>] = [:]
|
var has_event: [String: Set<NoteId>] = [:]
|
||||||
var deleted_events: Set<NoteId> = Set()
|
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 done_init: Bool = false
|
||||||
var incoming_dms: [NostrEvent] = []
|
var incoming_dms: [NostrEvent] = []
|
||||||
let dm_debouncer = Debouncer(interval: 0.5)
|
let dm_debouncer = Debouncer(interval: 0.5)
|
||||||
@ -135,7 +135,7 @@ class HomeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@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) {
|
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -211,7 +211,7 @@ class HomeModel {
|
|||||||
pdata.status.update_status(st)
|
pdata.status.update_status(st)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_nwc_response(_ ev: NostrEvent, relay: String) {
|
func handle_nwc_response(_ ev: NostrEvent, relay: RelayURL) {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
|
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
|
||||||
guard let nwc_str = damus_state.settings.nostr_wallet_connect,
|
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,
|
// since command results are not returned for ephemeral events,
|
||||||
// remove the request from the postbox which is likely failing over and over
|
// 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)]")
|
print("nwc: got response, removed \(resp.req_id) from the postbox [\(relay)]")
|
||||||
} else {
|
} else {
|
||||||
print("nwc: \(resp.req_id) not found in the postbox, nothing to remove [\(relay)]")
|
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)
|
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)
|
process_contact_event(state: self.damus_state, ev: ev)
|
||||||
|
|
||||||
if sub_id == init_subid {
|
if sub_id == init_subid {
|
||||||
@ -382,7 +382,7 @@ class HomeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func handle_event(relay_id: String, conn_event: NostrConnectionEvent) {
|
func handle_event(relay_id: RelayURL, conn_event: NostrConnectionEvent) {
|
||||||
switch conn_event {
|
switch conn_event {
|
||||||
case .ws_event(let ev):
|
case .ws_event(let ev):
|
||||||
switch ev {
|
switch ev {
|
||||||
@ -400,7 +400,7 @@ class HomeModel {
|
|||||||
let r = pool.get_relay(relay_id),
|
let r = pool.get_relay(relay_id),
|
||||||
r.descriptor.variant == .nwc,
|
r.descriptor.variant == .nwc,
|
||||||
let nwc = WalletConnectURL(str: nwc_str),
|
let nwc = WalletConnectURL(str: nwc_str),
|
||||||
nwc.relay.id == relay_id
|
nwc.relay == relay_id
|
||||||
{
|
{
|
||||||
subscribe_to_nwc(url: nwc, pool: pool)
|
subscribe_to_nwc(url: nwc, pool: pool)
|
||||||
}
|
}
|
||||||
@ -461,14 +461,14 @@ class HomeModel {
|
|||||||
|
|
||||||
|
|
||||||
/// Send the initial filters, just our contact list mostly
|
/// 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 filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
|
||||||
let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid)
|
let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid)
|
||||||
pool.send(.subscribe(subscription), to: [relay_id])
|
pool.send(.subscribe(subscription), to: [relay_id])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// After initial connection or reconnect, send subscription filters for the home timeline, DMs, and notifications
|
/// 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
|
// TODO: since times should be based on events from a specific relay
|
||||||
// perhaps we could mark this in the relay pool somehow
|
// 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)
|
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] } ?? [:]
|
return relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +543,7 @@ class HomeModel {
|
|||||||
return Array(friends)
|
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?
|
// TODO: separate likes?
|
||||||
var home_filter_kinds: [NostrKind] = [
|
var home_filter_kinds: [NostrKind] = [
|
||||||
.text, .longform, .boost
|
.text, .longform, .boost
|
||||||
@ -619,7 +619,7 @@ class HomeModel {
|
|||||||
migrate_old_muted_threads_to_new_mutelist(keypair: damus_state.keypair, damus_state: damus_state)
|
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 {
|
guard let m = last_event_of_kind[relay_id] else {
|
||||||
last_event_of_kind[relay_id] = [:]
|
last_event_of_kind[relay_id] = [:]
|
||||||
return nil
|
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) {
|
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
|
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
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var changed = false
|
var changed = false
|
||||||
|
|
||||||
var new = Set<String>()
|
var new = Set<RelayURL>()
|
||||||
for key in decoded.keys {
|
for key in decoded.keys {
|
||||||
new.insert(key)
|
new.insert(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
var old = Set<String>()
|
var old = Set<RelayURL>()
|
||||||
for key in old_decoded.keys {
|
for key in old_decoded.keys {
|
||||||
old.insert(key)
|
old.insert(key)
|
||||||
}
|
}
|
||||||
@ -910,10 +910,8 @@ func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
|||||||
for d in diff {
|
for d in diff {
|
||||||
changed = true
|
changed = true
|
||||||
if new.contains(d) {
|
if new.contains(d) {
|
||||||
if let url = RelayURL(d) {
|
let descriptor = RelayDescriptor(url: d, info: decoded[d] ?? .rw)
|
||||||
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)
|
||||||
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 {
|
} else {
|
||||||
state.pool.remove_relay(d)
|
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)
|
try? pool.add_relay(descriptor)
|
||||||
let url = descriptor.url
|
let url = descriptor.url
|
||||||
|
|
||||||
let relay_id = url.id
|
let relay_id = url
|
||||||
guard model_cache.model(withURL: url) == nil else {
|
guard model_cache.model(withURL: url) == nil else {
|
||||||
return
|
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? {
|
func fetch_relay_metadata(relay_id: RelayURL) async throws -> RelayMetadata? {
|
||||||
var urlString = relay_id.replacingOccurrences(of: "wss://", with: "https://")
|
var urlString = relay_id.absoluteString.replacingOccurrences(of: "wss://", with: "https://")
|
||||||
urlString = urlString.replacingOccurrences(of: "ws://", with: "http://")
|
urlString = urlString.replacingOccurrences(of: "ws://", with: "http://")
|
||||||
|
|
||||||
guard let url = URL(string: urlString) else {
|
guard let url = URL(string: urlString) else {
|
||||||
|
@ -10,7 +10,7 @@ import Foundation
|
|||||||
class ProfileModel: ObservableObject, Equatable {
|
class ProfileModel: ObservableObject, Equatable {
|
||||||
@Published var contacts: NostrEvent? = nil
|
@Published var contacts: NostrEvent? = nil
|
||||||
@Published var following: Int = 0
|
@Published var following: Int = 0
|
||||||
@Published var relays: [String: RelayInfo]? = nil
|
@Published var relays: [RelayURL: RelayInfo]? = nil
|
||||||
@Published var progress: Int = 0
|
@Published var progress: Int = 0
|
||||||
|
|
||||||
private let MAX_SHARE_RELAYS = 4
|
private let MAX_SHARE_RELAYS = 4
|
||||||
@ -109,7 +109,7 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
seen_event.insert(ev.id)
|
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 {
|
switch ev {
|
||||||
case .ws_event:
|
case .ws_event:
|
||||||
return
|
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 {
|
if case .nostr_event(let resp) = ev, case .event(_, let event) = resp, case .contacts = event.known_kind {
|
||||||
self.relays = decode_json_relays(event.content)
|
self.relays = decode_json_relays(event.content)
|
||||||
}
|
}
|
||||||
@ -160,12 +160,8 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
damus.pool.unsubscribe(sub_id: findRelay_subid)
|
damus.pool.unsubscribe(sub_id: findRelay_subid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRelayStrings() -> [String] {
|
|
||||||
return relays?.keys.map {$0} ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCappedRelayStrings() -> [String] {
|
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)
|
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
|
loading = false
|
||||||
damus_state.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] })
|
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 {
|
guard case .nostr_event(let event) = conn_ev else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -129,7 +129,7 @@ enum PubkeysToLoad {
|
|||||||
case from_keys([Pubkey])
|
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)
|
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events, txn: txn)
|
||||||
|
|
||||||
guard !authors.isEmpty else {
|
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
|
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 {
|
if ev.is_textlike && ev.should_show_event {
|
||||||
self.add_event(ev)
|
self.add_event(ev)
|
||||||
@ -107,7 +107,7 @@ func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
|
|||||||
return true
|
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 {
|
switch ev {
|
||||||
case .ws_event:
|
case .ws_event:
|
||||||
return (nil, false)
|
return (nil, false)
|
||||||
|
@ -103,7 +103,7 @@ class ThreadModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@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
|
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 {
|
guard subids.contains(sid) else {
|
||||||
|
@ -39,7 +39,7 @@ class ZapsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@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 {
|
guard case .nostr_event(let resp) = conn_ev else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
func make_auth_request(keypair: FullKeypair, challenge_string: String, relay: Relay) -> NostrEvent? {
|
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)
|
let event = NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 22242, tags: tags)
|
||||||
return event
|
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? {
|
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 tags = zap_target_to_tags(target)
|
||||||
var relay_tag = ["relays"]
|
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)
|
tags.append(relay_tag)
|
||||||
|
|
||||||
var kp = keypair
|
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? {
|
func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
|
||||||
let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey)
|
let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey)
|
||||||
let rw_relay_info = RelayInfo(read: true, write: true)
|
let rw_relay_info = RelayInfo(read: true, write: true)
|
||||||
var relays: [String: RelayInfo] = [:]
|
var relays: [RelayURL: RelayInfo] = [:]
|
||||||
|
|
||||||
for relay in bootstrap_relays {
|
for relay in bootstrap_relays {
|
||||||
relays[relay] = rw_relay_info
|
relays[relay] = rw_relay_info
|
||||||
|
@ -119,8 +119,8 @@ class Relay: Identifiable {
|
|||||||
return (flags & RelayFlags.broken.rawValue) == RelayFlags.broken.rawValue
|
return (flags & RelayFlags.broken.rawValue) == RelayFlags.broken.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
var id: String {
|
var id: RelayURL {
|
||||||
return get_relay_id(descriptor.url)
|
return descriptor.url
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -128,15 +128,3 @@ class Relay: Identifiable {
|
|||||||
enum RelayError: Error {
|
enum RelayError: Error {
|
||||||
case RelayAlreadyExists
|
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_connection_attempt: TimeInterval = 0
|
||||||
private(set) var last_pong: Date? = nil
|
private(set) var last_pong: Date? = nil
|
||||||
private(set) var backoff: TimeInterval = 1.0
|
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 subscriptionToken: AnyCancellable?
|
||||||
|
|
||||||
private var handleEvent: (NostrConnectionEvent) -> ()
|
private var handleEvent: (NostrConnectionEvent) -> ()
|
||||||
private var processEvent: (WebSocketEvent) -> ()
|
private var processEvent: (WebSocketEvent) -> ()
|
||||||
private let url: RelayURL
|
private let relay_url: RelayURL
|
||||||
var log: RelayLog?
|
var log: RelayLog?
|
||||||
|
|
||||||
init(url: RelayURL,
|
init(url: RelayURL,
|
||||||
handleEvent: @escaping (NostrConnectionEvent) -> (),
|
handleEvent: @escaping (NostrConnectionEvent) -> (),
|
||||||
processEvent: @escaping (WebSocketEvent) -> ())
|
processEvent: @escaping (WebSocketEvent) -> ())
|
||||||
{
|
{
|
||||||
self.url = url
|
self.relay_url = url
|
||||||
self.handleEvent = handleEvent
|
self.handleEvent = handleEvent
|
||||||
self.processEvent = processEvent
|
self.processEvent = processEvent
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ final class RelayConnection: ObservableObject {
|
|||||||
self.last_pong = .now
|
self.last_pong = .now
|
||||||
self.log?.add("Successful ping")
|
self.log?.add("Successful ping")
|
||||||
} else {
|
} else {
|
||||||
print("pong failed, reconnecting \(self.url.id)")
|
print("pong failed, reconnecting \(self.relay_url.id)")
|
||||||
self.isConnected = false
|
self.isConnected = false
|
||||||
self.isConnecting = false
|
self.isConnecting = false
|
||||||
self.reconnect_with_backoff()
|
self.reconnect_with_backoff()
|
||||||
@ -126,7 +126,7 @@ final class RelayConnection: ObservableObject {
|
|||||||
self.receive(message: message)
|
self.receive(message: message)
|
||||||
case .disconnected(let closeCode, let reason):
|
case .disconnected(let closeCode, let reason):
|
||||||
if closeCode != .normalClosure {
|
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 {
|
DispatchQueue.main.async {
|
||||||
self.isConnected = false
|
self.isConnected = false
|
||||||
@ -134,7 +134,7 @@ final class RelayConnection: ObservableObject {
|
|||||||
self.reconnect()
|
self.reconnect()
|
||||||
}
|
}
|
||||||
case .error(let error):
|
case .error(let error):
|
||||||
print("⚠️ Warning: RelayConnection (\(self.url)) error: \(error)")
|
print("⚠️ Warning: RelayConnection (\(self.relay_url)) error: \(error)")
|
||||||
let nserr = error as NSError
|
let nserr = error as NSError
|
||||||
if nserr.domain == NSPOSIXErrorDomain && nserr.code == 57 {
|
if nserr.domain == NSPOSIXErrorDomain && nserr.code == 57 {
|
||||||
// ignore socket not connected?
|
// ignore socket not connected?
|
||||||
|
@ -13,7 +13,7 @@ import UIKit
|
|||||||
/// will have information to help developers debug issues.
|
/// will have information to help developers debug issues.
|
||||||
final class RelayLog: ObservableObject {
|
final class RelayLog: ObservableObject {
|
||||||
private static let line_limit = 250
|
private static let line_limit = 250
|
||||||
private let relay_url: URL?
|
private let relay_url: RelayURL?
|
||||||
private lazy var formatter: DateFormatter = {
|
private lazy var formatter: DateFormatter = {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateStyle = .short
|
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
|
/// - 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,
|
/// a RelayLog that does nothing. This is required to allow RelayLog to be used as a StateObject,
|
||||||
/// because they cannot be Optional.
|
/// because they cannot be Optional.
|
||||||
init(_ relay_url: URL? = nil) {
|
init(_ relay_url: RelayURL? = nil) {
|
||||||
self.relay_url = relay_url
|
self.relay_url = relay_url
|
||||||
|
|
||||||
setUp()
|
setUp()
|
||||||
|
@ -10,17 +10,17 @@ import Network
|
|||||||
|
|
||||||
struct RelayHandler {
|
struct RelayHandler {
|
||||||
let sub_id: String
|
let sub_id: String
|
||||||
let callback: (String, NostrConnectionEvent) -> ()
|
let callback: (RelayURL, NostrConnectionEvent) -> ()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct QueuedRequest {
|
struct QueuedRequest {
|
||||||
let req: NostrRequestType
|
let req: NostrRequestType
|
||||||
let relay: String
|
let relay: RelayURL
|
||||||
let skip_ephemeral: Bool
|
let skip_ephemeral: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SeenEvent: Hashable {
|
struct SeenEvent: Hashable {
|
||||||
let relay_id: String
|
let relay_id: RelayURL
|
||||||
let evid: NoteId
|
let evid: NoteId
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ class RelayPool {
|
|||||||
var handlers: [RelayHandler] = []
|
var handlers: [RelayHandler] = []
|
||||||
var request_queue: [QueuedRequest] = []
|
var request_queue: [QueuedRequest] = []
|
||||||
var seen: Set<SeenEvent> = Set()
|
var seen: Set<SeenEvent> = Set()
|
||||||
var counts: [String: UInt64] = [:]
|
var counts: [RelayURL: UInt64] = [:]
|
||||||
var ndb: Ndb
|
var ndb: Ndb
|
||||||
var keypair: Keypair?
|
var keypair: Keypair?
|
||||||
var message_received_function: (((String, RelayDescriptor)) -> Void)?
|
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 {
|
for handler in handlers {
|
||||||
// don't add duplicate handlers
|
// don't add duplicate handlers
|
||||||
if handler.sub_id == sub_id {
|
if handler.sub_id == sub_id {
|
||||||
@ -105,7 +105,7 @@ class RelayPool {
|
|||||||
print("registering \(sub_id) handler, current: \(self.handlers.count)")
|
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
|
var i: Int = 0
|
||||||
|
|
||||||
self.disconnect(to: [relay_id])
|
self.disconnect(to: [relay_id])
|
||||||
@ -122,12 +122,11 @@ class RelayPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func add_relay(_ desc: RelayDescriptor) throws {
|
func add_relay(_ desc: RelayDescriptor) throws {
|
||||||
let url = desc.url
|
let relay_id = desc.url
|
||||||
let relay_id = get_relay_id(url)
|
|
||||||
if get_relay(relay_id) != nil {
|
if get_relay(relay_id) != nil {
|
||||||
throw RelayError.RelayAlreadyExists
|
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)
|
self.handle_event(relay_id: relay_id, event: event)
|
||||||
}, processEvent: { wsev in
|
}, processEvent: { wsev in
|
||||||
guard case .message(let msg) = wsev,
|
guard case .message(let msg) = wsev,
|
||||||
@ -141,7 +140,7 @@ class RelayPool {
|
|||||||
self.relays.append(relay)
|
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
|
// add the current network state to the log
|
||||||
log.add("Network state: \(network_monitor.currentPath.status)")
|
log.add("Network state: \(network_monitor.currentPath.status)")
|
||||||
|
|
||||||
@ -156,7 +155,7 @@ class RelayPool {
|
|||||||
let is_connecting = c.isConnecting
|
let is_connecting = c.isConnecting
|
||||||
|
|
||||||
if is_connecting && (Date.now.timeIntervalSince1970 - c.last_connection_attempt) > 5 {
|
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()
|
relay.connection.reconnect()
|
||||||
} else if relay.is_broken || is_connecting || c.isConnected {
|
} else if relay.is_broken || is_connecting || c.isConnected {
|
||||||
continue
|
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
|
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||||
for relay in relays {
|
for relay in relays {
|
||||||
// don't try to reconnect to broken 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
|
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||||
for relay in relays {
|
for relay in relays {
|
||||||
relay.connection.connect()
|
relay.connection.connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func disconnect(to: [String]? = nil) {
|
func disconnect(to: [RelayURL]? = nil) {
|
||||||
let relays = to.map{ get_relays($0) } ?? self.relays
|
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||||
for relay in relays {
|
for relay in relays {
|
||||||
relay.connection.disconnect()
|
relay.connection.disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe(sub_id: String, to: [String]? = nil) {
|
func unsubscribe(sub_id: String, to: [RelayURL]? = nil) {
|
||||||
if to == nil {
|
if to == nil {
|
||||||
self.remove_handler(sub_id: sub_id)
|
self.remove_handler(sub_id: sub_id)
|
||||||
}
|
}
|
||||||
self.send(.unsubscribe(sub_id), to: to)
|
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)
|
register_handler(sub_id: sub_id, handler: handler)
|
||||||
send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to)
|
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)
|
register_handler(sub_id: sub_id, handler: handler)
|
||||||
send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to)
|
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
|
var c = 0
|
||||||
for request in request_queue {
|
for request in request_queue {
|
||||||
if request.relay == relay {
|
if request.relay == relay {
|
||||||
@ -217,7 +216,7 @@ class RelayPool {
|
|||||||
return c
|
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)
|
let count = count_queued(relay: relay)
|
||||||
guard count <= 10 else {
|
guard count <= 10 else {
|
||||||
print("can't queue, too many queued events for \(relay)")
|
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))
|
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
|
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||||
|
|
||||||
// send to local relay (nostrdb)
|
// 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)
|
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
|
// don't include ephemeral relays in the default list to query
|
||||||
relays.filter { ids.contains($0.id) }
|
relays.filter { ids.contains($0.id) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_relay(_ id: String) -> Relay? {
|
func get_relay(_ id: RelayURL) -> Relay? {
|
||||||
relays.first(where: { $0.id == id })
|
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
|
self.request_queue = request_queue.reduce(into: Array<QueuedRequest>()) { (q, req) in
|
||||||
guard req.relay == relay_id else {
|
guard req.relay == relay_id else {
|
||||||
q.append(req)
|
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 .nostr_event(let ev) = event {
|
||||||
if case .event(_, let nev) = ev {
|
if case .event(_, let nev) = ev {
|
||||||
let k = SeenEvent(relay_id: relay_id, evid: nev.id)
|
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)
|
record_seen(relay_id: relay_id, event: event)
|
||||||
|
|
||||||
// run req queue when we reconnect
|
// run req queue when we reconnect
|
||||||
@ -349,10 +348,7 @@ class RelayPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func add_rw_relay(_ pool: RelayPool, _ url: String) {
|
func add_rw_relay(_ pool: RelayPool, _ url: RelayURL) {
|
||||||
guard let url = RelayURL(url) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try? pool.add_relay(RelayDescriptor(url: url, info: .rw))
|
try? pool.add_relay(RelayDescriptor(url: url, info: .rw))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,15 +7,28 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable {
|
public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable, Identifiable, Comparable, CustomStringConvertible {
|
||||||
private(set) var url: URL
|
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
|
return url.absoluteString
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(_ str: String) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +81,11 @@ public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable {
|
|||||||
hasher.combine(self.url)
|
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 {
|
private struct StringKey: CodingKey {
|
||||||
|
@ -9,12 +9,12 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
class Relayer {
|
class Relayer {
|
||||||
let relay: String
|
let relay: RelayURL
|
||||||
var attempts: Int
|
var attempts: Int
|
||||||
var retry_after: Double
|
var retry_after: Double
|
||||||
var last_attempt: Int64?
|
var last_attempt: Int64?
|
||||||
|
|
||||||
init(relay: String, attempts: Int, retry_after: Double) {
|
init(relay: RelayURL, attempts: Int, retry_after: Double) {
|
||||||
self.relay = relay
|
self.relay = relay
|
||||||
self.attempts = attempts
|
self.attempts = attempts
|
||||||
self.retry_after = retry_after
|
self.retry_after = retry_after
|
||||||
@ -35,7 +35,7 @@ class PostedEvent {
|
|||||||
var flushed_once: Bool
|
var flushed_once: Bool
|
||||||
let on_flush: OnFlush?
|
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.event = event
|
||||||
self.skip_ephemeral = skip_ephemeral
|
self.skip_ephemeral = skip_ephemeral
|
||||||
self.flush_after = flush_after
|
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 {
|
guard case .nostr_event(let resp) = ev else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ class PostBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@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 {
|
guard let ev = self.events[event_id] else {
|
||||||
return false
|
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
|
// Don't add event if we already have it
|
||||||
if events[event.id] != nil {
|
if events[event.id] != nil {
|
||||||
return
|
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 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)
|
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")
|
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)
|
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)
|
let key = bootstrap_relays_setting_key(pubkey: pubkey)
|
||||||
|
|
||||||
guard let relays = UserDefaults.standard.stringArray(forKey: key) else {
|
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 }
|
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)")
|
print("Loading custom bootstrap relays: \(loaded_relays)")
|
||||||
return loaded_relays
|
return loaded_relays
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_default_bootstrap_relays() -> [String] {
|
func get_default_bootstrap_relays() -> [RelayURL] {
|
||||||
var default_bootstrap_relays = BOOTSTRAP_RELAYS
|
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] {
|
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
|
return default_bootstrap_relays
|
||||||
|
@ -9,9 +9,9 @@ import Foundation
|
|||||||
|
|
||||||
struct RelayFilter: Hashable {
|
struct RelayFilter: Hashable {
|
||||||
let timeline: Timeline
|
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.timeline = timeline
|
||||||
self.relay_id = relay_id
|
self.relay_id = relay_id
|
||||||
}
|
}
|
||||||
@ -21,13 +21,13 @@ class RelayFilters {
|
|||||||
private let our_pubkey: Pubkey
|
private let our_pubkey: Pubkey
|
||||||
private var disabled: Set<RelayFilter>
|
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 filter = RelayFilter(timeline: timeline, relay_id: relay_id)
|
||||||
let contains = disabled.contains(filter)
|
let contains = disabled.contains(filter)
|
||||||
return contains
|
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)
|
let filter = RelayFilter(timeline: timeline, relay_id: relay_id)
|
||||||
if !disabled.contains(filter) {
|
if !disabled.contains(filter) {
|
||||||
return
|
return
|
||||||
@ -37,7 +37,7 @@ class RelayFilters {
|
|||||||
save_relay_filters(our_pubkey, filters: disabled)
|
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)
|
let filter = RelayFilter(timeline: timeline, relay_id: relay_id)
|
||||||
if disabled.contains(filter) {
|
if disabled.contains(filter) {
|
||||||
return
|
return
|
||||||
@ -77,13 +77,16 @@ func load_relay_filters(_ pubkey: Pubkey) -> Set<RelayFilter>? {
|
|||||||
guard let timeline = Timeline.init(rawValue: parts[0]) else {
|
guard let timeline = Timeline.init(rawValue: parts[0]) else {
|
||||||
return
|
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)
|
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
|
return pool.our_descriptors
|
||||||
.map { $0.url.url.absoluteString }
|
.map { $0.url }
|
||||||
.filter { !filters.is_filtered(timeline: .search, relay_id: $0) }
|
.filter { !filters.is_filtered(timeline: .search, relay_id: $0) }
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ final class RelayModel: Hashable {
|
|||||||
|
|
||||||
init(_ url: RelayURL, metadata: RelayMetadata) {
|
init(_ url: RelayURL, metadata: RelayMetadata) {
|
||||||
self.url = url
|
self.url = url
|
||||||
self.log = RelayLog(url.url)
|
self.log = RelayLog(url)
|
||||||
self.metadata = metadata
|
self.metadata = metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,8 @@ final class RelayModelCache: ObservableObject {
|
|||||||
models[url]
|
models[url]
|
||||||
}
|
}
|
||||||
|
|
||||||
func model(with_relay_id url_string: String) -> RelayModel? {
|
func model(with_relay_id url_string: RelayURL) -> RelayModel? {
|
||||||
guard let url = RelayURL(url_string) else {
|
return model(withURL: url_string)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return model(withURL: url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(model: RelayModel) {
|
func insert(model: RelayModel) {
|
||||||
|
@ -11,8 +11,8 @@ enum Route: Hashable {
|
|||||||
case ProfileByKey(pubkey: Pubkey)
|
case ProfileByKey(pubkey: Pubkey)
|
||||||
case Profile(profile: ProfileModel, followers: FollowersModel)
|
case Profile(profile: ProfileModel, followers: FollowersModel)
|
||||||
case Followers(followers: FollowersModel)
|
case Followers(followers: FollowersModel)
|
||||||
case Relay(relay: String, showActionButtons: Binding<Bool>)
|
case Relay(relay: RelayURL, showActionButtons: Binding<Bool>)
|
||||||
case RelayDetail(relay: String, metadata: RelayMetadata?)
|
case RelayDetail(relay: RelayURL, metadata: RelayMetadata?)
|
||||||
case Following(following: FollowingModel)
|
case Following(following: FollowingModel)
|
||||||
case MuteList
|
case MuteList
|
||||||
case RelayConfig
|
case RelayConfig
|
||||||
@ -21,7 +21,7 @@ enum Route: Hashable {
|
|||||||
case Config
|
case Config
|
||||||
case EditMetadata
|
case EditMetadata
|
||||||
case DMChat(dms: DirectMessageModel)
|
case DMChat(dms: DirectMessageModel)
|
||||||
case UserRelays(relays: [String])
|
case UserRelays(relays: [RelayURL])
|
||||||
case KeySettings(keypair: Keypair)
|
case KeySettings(keypair: Keypair)
|
||||||
case AppearanceSettings(settings: UserSettingsStore)
|
case AppearanceSettings(settings: UserSettingsStore)
|
||||||
case NotificationSettings(settings: UserSettingsStore)
|
case NotificationSettings(settings: UserSettingsStore)
|
||||||
|
@ -38,7 +38,7 @@ func subscribe_to_nwc(url: WalletConnectURL, pool: RelayPool) {
|
|||||||
filter.limit = 0
|
filter.limit = 0
|
||||||
let sub = NostrSubscribe(filters: [filter], sub_id: "nwc")
|
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
|
@discardableResult
|
||||||
@ -50,7 +50,7 @@ func nwc_pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: Str
|
|||||||
|
|
||||||
try? pool.add_relay(.nwc(url: url.relay))
|
try? pool.add_relay(.nwc(url: url.relay))
|
||||||
subscribe_to_nwc(url: url, pool: pool)
|
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
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ struct WalletConnectURL: Equatable {
|
|||||||
urlComponents.scheme = "nostrwalletconnect"
|
urlComponents.scheme = "nostrwalletconnect"
|
||||||
urlComponents.host = pubkey.hex()
|
urlComponents.host = pubkey.hex()
|
||||||
urlComponents.queryItems = [
|
urlComponents.queryItems = [
|
||||||
URLQueryItem(name: "relay", value: relay.id),
|
URLQueryItem(name: "relay", value: relay.absoluteString),
|
||||||
URLQueryItem(name: "secret", value: keypair.privkey.hex())
|
URLQueryItem(name: "secret", value: keypair.privkey.hex())
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ struct AddRelayView: View {
|
|||||||
return
|
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) {
|
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)
|
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)))
|
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 {
|
guard case .nostr_event(let nostr_response) = ev else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ class SuggestedUsersViewModel: ObservableObject {
|
|||||||
damus_state.pool.subscribe(sub_id: sub_id, filters: [filter], handler: handle_event)
|
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 {
|
guard case .nostr_event(let nev) = ev else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ struct RelayFilterView: View {
|
|||||||
.padding(.bottom, 0)
|
.padding(.bottom, 0)
|
||||||
|
|
||||||
List(Array(relays), id: \.url.id) { relay in
|
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 rs: [RelayDescriptor] = []
|
||||||
let recommended_relay_addresses = get_default_bootstrap_relays()
|
let recommended_relay_addresses = get_default_bootstrap_relays()
|
||||||
return recommended_relay_addresses.reduce(into: rs) { xs, x in
|
return recommended_relay_addresses.reduce(into: rs) { xs, x in
|
||||||
if let url = RelayURL(x) {
|
xs.append(RelayDescriptor(url: x, info: .rw))
|
||||||
xs.append(RelayDescriptor(url: url, info: .rw))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +134,7 @@ struct RelayConfigView: View {
|
|||||||
|
|
||||||
ForEach(relayList, id: \.url) { relay in
|
ForEach(relayList, id: \.url) { relay in
|
||||||
Group {
|
Group {
|
||||||
RelayView(state: state, relay: relay.url.id, showActionButtons: $showActionButtons, recommended: recommended)
|
RelayView(state: state, relay: relay.url, showActionButtons: $showActionButtons, recommended: recommended)
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,14 @@ import SwiftUI
|
|||||||
|
|
||||||
struct RelayDetailView: View {
|
struct RelayDetailView: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let relay: String
|
let relay: RelayURL
|
||||||
let nip11: RelayMetadata?
|
let nip11: RelayMetadata?
|
||||||
|
|
||||||
@ObservedObject var log: RelayLog
|
@ObservedObject var log: RelayLog
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
init(state: DamusState, relay: String, nip11: RelayMetadata?) {
|
init(state: DamusState, relay: RelayURL, nip11: RelayMetadata?) {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.relay = relay
|
self.relay = relay
|
||||||
self.nip11 = nip11
|
self.nip11 = nip11
|
||||||
@ -48,8 +48,7 @@ struct RelayDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let descriptors = state.pool.our_descriptors
|
let descriptors = state.pool.our_descriptors
|
||||||
guard let relay_url = RelayURL(relay),
|
guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, keypair: keypair, relay: relay) else {
|
||||||
let new_ev = remove_relay( ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +77,7 @@ struct RelayDetailView: View {
|
|||||||
guard let ev_before_add = state.contacts.event else {
|
guard let ev_before_add = state.contacts.event else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let relay_url = RelayURL(relay),
|
guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else {
|
||||||
let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
process_contact_event(state: state, ev: ev_after_add)
|
process_contact_event(state: state, ev: ev_after_add)
|
||||||
@ -114,7 +112,7 @@ struct RelayDetailView: View {
|
|||||||
if let relay_connection {
|
if let relay_connection {
|
||||||
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(relay)
|
Text(relay.absoluteString)
|
||||||
Spacer()
|
Spacer()
|
||||||
RelayStatusView(connection: relay_connection)
|
RelayStatusView(connection: relay_connection)
|
||||||
}
|
}
|
||||||
@ -166,7 +164,7 @@ struct RelayDetailView: View {
|
|||||||
.onReceive(handle_notify(.switched_timeline)) { notif in
|
.onReceive(handle_notify(.switched_timeline)) { notif in
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
.navigationTitle(nip11?.name ?? relay)
|
.navigationTitle(nip11?.name ?? relay.absoluteString)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.navigationBarItems(leading: BackNav())
|
.navigationBarItems(leading: BackNav())
|
||||||
@ -202,6 +200,6 @@ struct RelayDetailView: View {
|
|||||||
struct RelayDetailView_Previews: PreviewProvider {
|
struct RelayDetailView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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: "")
|
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 {
|
struct RelayPicView: View {
|
||||||
let relay: String
|
let relay: RelayURL
|
||||||
let icon: String?
|
let icon: String?
|
||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
let highlight: Highlight
|
let highlight: Highlight
|
||||||
let disable_animation: Bool
|
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.relay = relay
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.size = size
|
self.size = size
|
||||||
@ -106,10 +106,10 @@ func extract_tld(_ host: String) -> String {
|
|||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_relay_url(relay: String, icon: String?) -> URL? {
|
func get_relay_url(relay: RelayURL, icon: String?) -> URL? {
|
||||||
var favicon = relay + "/favicon.ico"
|
var favicon = relay.absoluteString + "/favicon.ico"
|
||||||
let tld = extract_tld(relay)
|
let tld = extract_tld(relay.absoluteString)
|
||||||
if tld != relay {
|
if tld != relay.absoluteString {
|
||||||
favicon = "https://" + tld + "/favicon.ico"
|
favicon = "https://" + tld + "/favicon.ico"
|
||||||
}
|
}
|
||||||
let pic = icon ?? favicon
|
let pic = icon ?? favicon
|
||||||
@ -119,10 +119,10 @@ func get_relay_url(relay: String, icon: String?) -> URL? {
|
|||||||
struct RelayPicView_Previews: PreviewProvider {
|
struct RelayPicView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack {
|
VStack {
|
||||||
RelayPicView(relay: "wss://relay.damus.io", size: 55, highlight: .none, disable_animation: false)
|
RelayPicView(relay: RelayURL("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: RelayURL("wss://nostr.wine")!, size: 55, highlight: .none, disable_animation: false)
|
||||||
RelayPicView(relay: "wss://nos.lol", size: 55, highlight: .none, disable_animation: false)
|
RelayPicView(relay: RelayURL("wss://nos.lol")!, size: 55, highlight: .none, disable_animation: false)
|
||||||
RelayPicView(relay: "fail", 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 {
|
struct RelayStatusView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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)
|
RelayStatusView(connection: connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ import SwiftUI
|
|||||||
struct RelayToggle: View {
|
struct RelayToggle: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let timeline: Timeline
|
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: {
|
return Binding(get: {
|
||||||
!state.relay_filters.is_filtered(timeline: timeline, relay_id: relay_id)
|
!state.relay_filters.is_filtered(timeline: timeline, relay_id: relay_id)
|
||||||
}, set: { on in
|
}, set: { on in
|
||||||
@ -30,7 +30,7 @@ struct RelayToggle: View {
|
|||||||
RelayStatusView(connection: relay_connection)
|
RelayStatusView(connection: relay_connection)
|
||||||
}
|
}
|
||||||
RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay_id)?.metadata.is_paid ?? false)
|
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))
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ struct RelayToggle: View {
|
|||||||
|
|
||||||
struct RelayToggle_Previews: PreviewProvider {
|
struct RelayToggle_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,14 @@ import SwiftUI
|
|||||||
|
|
||||||
struct RelayView: View {
|
struct RelayView: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let relay: String
|
let relay: RelayURL
|
||||||
let recommended: Bool
|
let recommended: Bool
|
||||||
@ObservedObject private var model_cache: RelayModelCache
|
@ObservedObject private var model_cache: RelayModelCache
|
||||||
|
|
||||||
@State var relay_state: Bool
|
@State var relay_state: Bool
|
||||||
@Binding var showActionButtons: 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.state = state
|
||||||
self.relay = relay
|
self.relay = relay
|
||||||
self.recommended = recommended
|
self.recommended = recommended
|
||||||
@ -26,7 +26,7 @@ struct RelayView: View {
|
|||||||
self._relay_state = State(initialValue: relay_state)
|
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
|
return pool.get_relay(relay) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,18 +45,18 @@ struct RelayView: View {
|
|||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(meta?.name ?? relay)
|
Text(meta?.name ?? relay.url.host() ?? relay.url.absoluteString)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.padding(.bottom, 2)
|
.padding(.bottom, 2)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false)
|
RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false)
|
||||||
}
|
}
|
||||||
Text(relay)
|
Text(relay.absoluteString)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
CopyAction(relay: relay)
|
CopyAction(relay: relay.absoluteString)
|
||||||
|
|
||||||
if let privkey = state.keypair.privkey {
|
if let privkey = state.keypair.privkey {
|
||||||
RemoveButton(privkey: privkey, showText: true)
|
RemoveButton(privkey: privkey, showText: true)
|
||||||
@ -113,8 +113,7 @@ struct RelayView: View {
|
|||||||
guard let ev_before_add = state.contacts.event else {
|
guard let ev_before_add = state.contacts.event else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let relay_url = RelayURL(relay),
|
guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else {
|
||||||
let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
process_contact_event(state: state, ev: ev_after_add)
|
process_contact_event(state: state, ev: ev_after_add)
|
||||||
@ -132,8 +131,7 @@ struct RelayView: View {
|
|||||||
|
|
||||||
let descriptors = state.pool.our_descriptors
|
let descriptors = state.pool.our_descriptors
|
||||||
guard let keypair = state.keypair.to_full(),
|
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) else {
|
||||||
let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +180,6 @@ struct RelayView: View {
|
|||||||
|
|
||||||
struct RelayView_Previews: PreviewProvider {
|
struct RelayView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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()
|
self.pool.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_event(relay: String, ev: NostrConnectionEvent) {
|
func handle_event(relay: RelayURL, ev: NostrConnectionEvent) {
|
||||||
switch ev {
|
switch ev {
|
||||||
case .ws_event(let wsev):
|
case .ws_event(let wsev):
|
||||||
switch wsev {
|
switch wsev {
|
||||||
|
@ -9,18 +9,18 @@ import SwiftUI
|
|||||||
|
|
||||||
struct UserRelaysView: View {
|
struct UserRelaysView: View {
|
||||||
let state: DamusState
|
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.state = state
|
||||||
self.relays = relays
|
self.relays = relays
|
||||||
let relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: relays)
|
let relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: relays)
|
||||||
self._relay_state = State(initialValue: relay_state)
|
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 relays.map({ r in
|
||||||
return (r, pool.get_relay(r) == nil)
|
return (r, pool.get_relay(r) == nil)
|
||||||
}).sorted { (a, b) in a.0 < b.0 }
|
}).sorted { (a, b) in a.0 < b.0 }
|
||||||
|
@ -55,7 +55,7 @@ struct ConnectWalletView: View {
|
|||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
Text(nwc.relay.id)
|
Text(nwc.relay.absoluteString)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ struct ConnectWalletView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MutinyButton() {
|
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: {
|
Button(action: {
|
||||||
|
@ -34,7 +34,7 @@ struct WalletView: View {
|
|||||||
|
|
||||||
Divider()
|
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)
|
.frame(maxWidth: .infinity, minHeight: 125, alignment: .top)
|
||||||
.padding(.horizontal, 10)
|
.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.pubkey, pk)
|
||||||
XCTAssertEqual(url.keypair.privkey, sec)
|
XCTAssertEqual(url.keypair.privkey, sec)
|
||||||
XCTAssertEqual(url.keypair.pubkey, privkey_to_pubkey(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")
|
XCTAssertEqual(url.lud16, "jb55@jb55.com")
|
||||||
|
|
||||||
// Test an NWC url format which is NIP-47 and RFC 3986 compliant
|
// 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.pubkey, pk_2)
|
||||||
XCTAssertEqual(url_2.keypair.privkey, sec_2)
|
XCTAssertEqual(url_2.keypair.privkey, sec_2)
|
||||||
XCTAssertEqual(url_2.keypair.pubkey, privkey_to_pubkey(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() {
|
func testNWCEphemeralRelay() {
|
||||||
@ -92,12 +92,12 @@ final class WalletConnectTests: XCTestCase {
|
|||||||
XCTAssertEqual(pool.our_descriptors.count, 0)
|
XCTAssertEqual(pool.our_descriptors.count, 0)
|
||||||
XCTAssertEqual(pool.all_descriptors.count, 1)
|
XCTAssertEqual(pool.all_descriptors.count, 1)
|
||||||
XCTAssertEqual(pool.all_descriptors[0].variant, .nwc)
|
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)
|
XCTAssertEqual(box.events.count, 1)
|
||||||
let ev = box.events.first!.value
|
let ev = box.events.first!.value
|
||||||
XCTAssertEqual(ev.skip_ephemeral, false)
|
XCTAssertEqual(ev.skip_ephemeral, false)
|
||||||
XCTAssertEqual(ev.remaining.count, 1)
|
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 {
|
func testPerformanceExample() throws {
|
||||||
|
@ -114,8 +114,8 @@ class damusTests: XCTestCase {
|
|||||||
func testSaveRelayFilters() {
|
func testSaveRelayFilters() {
|
||||||
var filters = Set<RelayFilter>()
|
var filters = Set<RelayFilter>()
|
||||||
|
|
||||||
let filter1 = RelayFilter(timeline: .search, relay_id: "wss://abc.com")
|
let filter1 = RelayFilter(timeline: .search, relay_id: RelayURL("wss://abc.com")!)
|
||||||
let filter2 = RelayFilter(timeline: .home, relay_id: "wss://abc.com")
|
let filter2 = RelayFilter(timeline: .home, relay_id: RelayURL("wss://abc.com")!)
|
||||||
filters.insert(filter1)
|
filters.insert(filter1)
|
||||||
filters.insert(filter2)
|
filters.insert(filter2)
|
||||||
|
|
||||||
|
@ -1,24 +1,5 @@
|
|||||||
# Contributing
|
# 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
|
## Submitting patches
|
||||||
|
|
||||||
*Most of this comes from the linux kernel guidelines for submitting
|
*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
|
the dedicated `Separate your changes` section because this is very
|
||||||
important.
|
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"
|
Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
|
||||||
instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
|
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
|
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
|
change that can be verified by reviewers. Each patch should be justifiable
|
||||||
on its own merits.
|
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
|
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
|
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
|
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.
|
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
|
Include `patch changelogs` which describe what has changed between the v1 and
|
||||||
v2 version of the patch. Please put this information **after** the `---` line
|
v2 version of the patch.
|
||||||
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?
|
|
||||||
|
|
||||||
|
|
||||||
### Sign your work - the Developer's Certificate of Origin
|
### Sign your work - the Developer's Certificate of Origin
|
||||||
|
|
||||||
To improve tracking of who did what, especially with patches that can
|
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
|
layers of maintainers, we've introduced a "sign-off" procedure on
|
||||||
patches that are being emailed around.
|
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
|
The changelog script will pick these up and give you attribution for your
|
||||||
change
|
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),
|
guard let script = interp_nostrscript(interp: interp),
|
||||||
let req_str = asm_str(cstr: preq, len: req_len),
|
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 {
|
else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
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;
|
return 1;
|
||||||
|
Loading…
Reference in New Issue
Block a user