1
0
mirror of git://jb55.com/damus synced 2024-09-19 11:43:44 +00:00

Enable offline posting

You can now post, like, repost, reply offline

Changelog-Added: Enable offline posting
This commit is contained in:
William Casarin 2023-03-31 15:14:55 -07:00
parent 915f3901a7
commit 2596542cb6
25 changed files with 196 additions and 31 deletions

View File

@ -182,6 +182,7 @@
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */; };
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */; };
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F329D779B5005914DB /* PostBox.swift */; };
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */; };
@ -568,6 +569,7 @@
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerTimelineView.swift; sourceTree = "<group>"; };
4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncedOnChange.swift; sourceTree = "<group>"; };
4CE4F0F329D779B5005914DB /* PostBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostBox.swift; sourceTree = "<group>"; };
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = "<group>"; };
@ -907,6 +909,7 @@
4C7FF7D628233637009601DB /* Util */ = {
isa = PBXGroup;
children = (
4CE4F0F329D779B5005914DB /* PostBox.swift */,
7C0F392D29B57C8F0039859C /* Extensions */,
4CE879492995B58700F758CC /* Relays */,
4CF0ABEA29844B2F00D66079 /* AnyCodable */,
@ -1630,6 +1633,7 @@
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */,
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -234,9 +234,9 @@ struct ContentView: View {
func MaybeReportView(target: ReportTarget) -> some View {
Group {
if let ds = damus_state {
if let sec = ds.keypair.privkey {
ReportView(pool: ds.pool, target: target, privkey: sec)
if let damus_state {
if let sec = damus_state.keypair.privkey {
ReportView(postbox: damus_state.postbox, target: target, privkey: sec)
} else {
EmptyView()
}
@ -396,7 +396,7 @@ struct ContentView: View {
let target = notif.object as! FollowTarget
let pk = target.pubkey
if let ev = unfollow_user(pool: damus.pool,
if let ev = unfollow_user(postbox: damus.postbox,
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
@ -447,7 +447,7 @@ struct ContentView: View {
//let to_relays = tup.1
print("post \(post.content)")
let new_ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey)
self.damus_state?.pool.send(.event(new_ev))
self.damus_state?.postbox.send(new_ev)
case .cancel:
active_sheet = nil
print("post cancelled")
@ -502,7 +502,7 @@ struct ContentView: View {
}
damus_state?.contacts.set_mutelist(mutelist)
ds.pool.send(.event(mutelist))
ds.postbox.send(mutelist)
confirm_overwrite_mutelist = false
confirm_block = false
@ -534,7 +534,7 @@ struct ContentView: View {
return
}
damus_state?.contacts.set_mutelist(ev)
ds.pool.send(.event(ev))
ds.postbox.send(ev)
}
}
}, message: {
@ -615,7 +615,8 @@ struct ContentView: View {
relay_metadata: metadatas,
drafts: Drafts(),
events: EventCache(),
bookmarks: BookmarksManager(pubkey: pubkey)
bookmarks: BookmarksManager(pubkey: pubkey),
postbox: PostBox(pool: pool)
)
home.damus_state = self.damus_state!
@ -793,6 +794,8 @@ func find_event(state: DamusState, evid: String, search_type: SearchType, find_f
}
switch ev {
case .ok:
break
case .event(_, let ev):
has_event = true
callback(ev)

View File

@ -140,7 +140,7 @@ func follow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, pri
return ev
}
func unfollow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, privkey: String, unfollow: String) -> NostrEvent? {
func unfollow_user(postbox: PostBox, our_contacts: NostrEvent?, pubkey: String, privkey: String, unfollow: String) -> NostrEvent? {
guard let cs = our_contacts else {
return nil
}
@ -149,7 +149,7 @@ func unfollow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, p
ev.calculate_id()
ev.sign(privkey: privkey)
pool.send(.event(ev))
postbox.send(ev)
return ev
}

View File

@ -26,6 +26,7 @@ struct DamusState {
let drafts: Drafts
let events: EventCache
let bookmarks: BookmarksManager
let postbox: PostBox
var pubkey: String {
return keypair.pubkey
@ -36,6 +37,6 @@ struct DamusState {
}
static var empty: DamusState {
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""))
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()))
}
}

View File

@ -64,6 +64,8 @@ class EventsModel: ObservableObject {
handle_event(relay_id: relay_id, ev: ev)
case .notice(_):
break
case .ok:
break
case .eose(_):
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state)
}

View File

@ -94,6 +94,9 @@ class FollowersModel: ObservableObject {
} else if sub_id == self.profiles_id {
damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])
}
case .ok:
break
}
}
}

View File

@ -58,6 +58,8 @@ class FollowingModel {
break
case .nostr_event(let nev):
switch nev {
case .ok:
break
case .event(_, let ev):
if ev.kind == 0 {
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)

View File

@ -333,7 +333,11 @@ class HomeModel: ObservableObject {
self.loading = false
break
case .ok:
break
}
}
}

View File

@ -133,6 +133,8 @@ class ProfileModel: ObservableObject, Equatable {
return
}
switch resp {
case .ok:
break
case .event(_, let ev):
add_event(ev)
case .notice(let notice):

View File

@ -68,6 +68,8 @@ class SearchHomeModel: ObservableObject {
}
case .notice(let msg):
print("search home notice: \(msg)")
case .ok:
break
case .eose(let sub_id):
loading = false

View File

@ -131,6 +131,9 @@ func handle_subid_event(pool: RelayPool, relay_id: String, ev: NostrConnectionEv
case .event(let ev_subid, let ev):
handle(ev_subid, ev)
return (ev_subid, false)
case .ok:
return (nil, false)
case .notice(let note):
if note.contains("Too many subscription filters") {

View File

@ -46,6 +46,8 @@ class ZapsModel: ObservableObject {
}
switch resp {
case .ok:
break
case .notice:
break
case .eose:

View File

@ -7,13 +7,22 @@
import Foundation
struct CommandResult {
let event_id: String
let ok: Bool
let msg: String
}
enum NostrResponse: Decodable {
case event(String, NostrEvent)
case notice(String)
case eose(String)
case ok(CommandResult)
var subid: String? {
switch self {
case .ok(_):
return nil
case .event(let sub_id, _):
return sub_id
case .eose(let sub_id):
@ -48,6 +57,19 @@ enum NostrResponse: Decodable {
let sub_id = try container.decode(String.self)
self = .eose(sub_id)
return
} else if typ == "OK" {
var cr: CommandResult
do {
let event_id = try container.decode(String.self)
let ok = try container.decode(Bool.self)
let msg = try container.decode(String.self)
cr = CommandResult(event_id: event_id, ok: ok, msg: msg)
} catch {
print(error)
throw error
}
self = .ok(cr)
//ev.pow = count_hash_leading_zero_bits(ev.id)
}
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "expected EVENT or NOTICE, got \(typ)"))

View File

@ -256,7 +256,6 @@ class RelayPool {
}
}
// handle reconnect logic, etc?
for handler in handlers {
handler.callback(relay_id, event)
}

View File

@ -6,3 +6,116 @@
//
import Foundation
class Relayer {
let relay: String
var attempts: Int
var retry_after: Double
var last_attempt: Int64?
init(relay: String, attempts: Int, retry_after: Double) {
self.relay = relay
self.attempts = attempts
self.retry_after = retry_after
self.last_attempt = nil
}
}
class PostedEvent {
let event: NostrEvent
var remaining: [Relayer]
init(event: NostrEvent, remaining: [String]) {
self.event = event
self.remaining = remaining.map {
Relayer(relay: $0, attempts: 0, retry_after: 2.0)
}
}
}
class PostBox {
let pool: RelayPool
var events: [String: PostedEvent]
init(pool: RelayPool) {
self.pool = pool
self.events = [:]
pool.register_handler(sub_id: "postbox", handler: handle_event)
}
func try_flushing_events() {
let now = Int64(Date().timeIntervalSince1970)
for kv in events {
let event = kv.value
for relayer in event.remaining {
if relayer.last_attempt == nil || (now >= (relayer.last_attempt! + Int64(relayer.retry_after))) {
print("attempt #\(relayer.attempts) to flush event '\(event.event.content)' to \(relayer.relay) after \(relayer.retry_after) seconds")
flush_event(event, to_relay: relayer)
}
}
}
}
func handle_event(relay_id: String, _ ev: NostrConnectionEvent) {
try_flushing_events()
guard case .nostr_event(let resp) = ev else {
return
}
guard case .ok(let cr) = resp else {
return
}
remove_relayer(relay_id: relay_id, event_id: cr.event_id)
}
func remove_relayer(relay_id: String, event_id: String) {
guard let ev = self.events[event_id] else {
return
}
ev.remaining = ev.remaining.filter {
$0.relay != relay_id
}
if ev.remaining.count == 0 {
self.events.removeValue(forKey: event_id)
}
}
private func flush_event(_ event: PostedEvent, to_relay: Relayer? = nil) {
var relayers = event.remaining
if let to_relay {
relayers = [to_relay]
}
for relayer in relayers {
relayer.attempts += 1
relayer.last_attempt = Int64(Date().timeIntervalSince1970)
relayer.retry_after *= 1.5
pool.send(.event(event.event), to: [relayer.relay])
}
}
func flush() {
for event in events {
flush_event(event.value)
}
}
func send(_ event: NostrEvent) {
// Don't add event if we already have it
if events[event.id] != nil {
return
}
let remaining = pool.descriptors.map {
$0.url.absoluteString
}
let posted_ev = PostedEvent(event: event, remaining: remaining)
events[event.id] = posted_ev
flush_event(posted_ev)
}
}

View File

@ -154,7 +154,7 @@ struct EventActionBar: View {
generator.impactOccurred()
damus_state.pool.send(.event(like_ev))
damus_state.postbox.send(like_ev)
}
}

View File

@ -293,7 +293,7 @@ struct ConfigView: View {
}
let ev = created_deleted_account_profile(keypair: full_kp)
state.pool.send(.event(ev))
state.postbox.send(ev)
notify(.logout, ())
}
}

View File

@ -130,7 +130,8 @@ struct DMChatView: View {
dms.draft = ""
damus_state.pool.send(.event(dm))
damus_state.postbox.send(dm)
end_editing()
}

View File

@ -102,7 +102,7 @@ struct EditMetadataView: View {
let m_metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: metadata)
if let metadata_ev = m_metadata_ev {
damus_state.pool.send(.event(metadata_ev))
damus_state.postbox.send(metadata_ev)
}
}

View File

@ -26,7 +26,7 @@ struct MutelistView: View {
}
damus_state.contacts.set_mutelist(new_ev)
damus_state.pool.send(.event(new_ev))
damus_state.postbox.send(new_ev)
users = get_mutelist_users(new_ev)
} label: {
Label(NSLocalizedString("Delete", comment: "Button to remove a user from their blocklist."), systemImage: "trash")

View File

@ -111,7 +111,7 @@ struct RecommendedRelayView: View {
return
}
process_contact_event(state: damus, ev: ev_after_add)
damus.pool.send(.event(ev_after_add))
damus.postbox.send(ev_after_add)
}
}

View File

@ -46,7 +46,7 @@ struct RelayDetailView: View {
}
process_contact_event(state: state, ev: new_ev)
state.pool.send(.event(new_ev))
state.postbox.send(new_ev)
dismiss()
}) {
Text("Disconnect From Relay", comment: "Button to disconnect from the relay.")
@ -60,7 +60,7 @@ struct RelayDetailView: View {
return
}
process_contact_event(state: state, ev: ev_after_add)
state.pool.send(.event(ev_after_add))
state.postbox.send(ev_after_add)
dismiss()
}) {
Text("Connect To Relay", comment: "Button to connect to the relay.")

View File

@ -82,7 +82,7 @@ struct RelayView: View {
}
process_contact_event(state: state, ev: new_ev)
state.pool.send(.event(new_ev))
state.postbox.send(new_ev)
}) {
if showText {
Text(NSLocalizedString("Disconnect", comment: "Button to disconnect from a relay server."))

View File

@ -8,7 +8,7 @@
import SwiftUI
struct ReportView: View {
let pool: RelayPool
let postbox: PostBox
let target: ReportTarget
let privkey: String
@ -44,7 +44,7 @@ struct ReportView: View {
}
func do_send_report(type: ReportType) {
guard let ev = send_report(privkey: privkey, pool: pool, target: target, type: type) else {
guard let ev = send_report(privkey: privkey, postbox: postbox, target: target, type: type) else {
return
}
@ -92,12 +92,12 @@ struct ReportView: View {
}
}
func send_report(privkey: String, pool: RelayPool, target: ReportTarget, type: ReportType) -> NostrEvent? {
func send_report(privkey: String, postbox: PostBox, target: ReportTarget, type: ReportType) -> NostrEvent? {
let report = Report(type: type, target: target, message: "")
guard let ev = create_report_event(privkey: privkey, report: report) else {
return nil
}
pool.send(.event(ev))
postbox.send(ev)
return ev
}
@ -106,9 +106,9 @@ struct ReportView_Previews: PreviewProvider {
let ds = test_damus_state()
VStack {
ReportView(pool: ds.pool, target: ReportTarget.user(""), privkey: "")
ReportView(postbox: ds.postbox, target: ReportTarget.user(""), privkey: "")
ReportView(pool: ds.pool, target: ReportTarget.user(""), privkey: "", report_sent: true, report_id: "report_id")
ReportView(postbox: ds.postbox, target: ReportTarget.user(""), privkey: "", report_sent: true, report_id: "report_id")
}
}

View File

@ -107,13 +107,13 @@ struct SaveKeysView: View {
switch wsev {
case .connected:
let metadata = create_account_to_metadata(account)
let m_metadata_ev = make_metadata_event(keypair: account.keypair, metadata: metadata)
let m_contacts_ev = make_first_contact_event(keypair: account.keypair)
let metadata_ev = make_metadata_event(keypair: account.keypair, metadata: metadata)
let contacts_ev = make_first_contact_event(keypair: account.keypair)
if let metadata_ev = m_metadata_ev {
if let metadata_ev {
self.pool.send(.event(metadata_ev))
}
if let contacts_ev = m_contacts_ev {
if let contacts_ev {
self.pool.send(.event(contacts_ev))
}
@ -141,6 +141,8 @@ struct SaveKeysView: View {
print("event in signup?")
case .eose:
break
case .ok:
break
}
}
}