Fix onboarding crash
This commit fixes a crash that occurred when clicking "follow all" during onboarding. This fix works by making `Contacts` and `PostBox` isolated into a specific Swift Actor, and updating direct and indirect usages accordingly. Changelog-Fixed: Fixed a crash that occurred when clicking "follow all" during onboarding. Closes: https://github.com/damus-io/damus/issues/3422 Co-authored-by: alltheseas <64376233+alltheseas@users.noreply.github.com> Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
@MainActor
|
||||||
struct NotificationExtensionState: HeadlessDamusState {
|
struct NotificationExtensionState: HeadlessDamusState {
|
||||||
let ndb: Ndb
|
let ndb: Ndb
|
||||||
let settings: UserSettingsStore
|
let settings: UserSettingsStore
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
// Log that we got a push notification
|
// Log that we got a push notification
|
||||||
Log.debug("Got nostr event push notification from pubkey %s", for: .push_notifications, nostr_event.pubkey.hex())
|
Log.debug("Got nostr event push notification from pubkey %s", for: .push_notifications, nostr_event.pubkey.hex())
|
||||||
|
|
||||||
guard let state = NotificationExtensionState() else {
|
Task {
|
||||||
|
guard let state = await NotificationExtensionState() else {
|
||||||
Log.debug("Failed to open nostrdb", for: .push_notifications)
|
Log.debug("Failed to open nostrdb", for: .push_notifications)
|
||||||
|
|
||||||
// Something failed to initialize so let's go for the next best thing
|
// Something failed to initialize so let's go for the next best thing
|
||||||
@@ -67,8 +68,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}()
|
}()
|
||||||
let sender_pubkey = nostr_event.pubkey
|
let sender_pubkey = nostr_event.pubkey
|
||||||
|
|
||||||
Task {
|
|
||||||
|
|
||||||
// Don't show notification details that match mute list.
|
// Don't show notification details that match mute list.
|
||||||
// TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block
|
// TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block
|
||||||
if await state.mutelist_manager.is_event_muted(nostr_event) {
|
if await state.mutelist_manager.is_event_muted(nostr_event) {
|
||||||
|
|||||||
@@ -1015,6 +1015,7 @@ func timeline_name(_ timeline: Timeline?) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
@MainActor
|
||||||
func handle_unfollow(state: DamusState, unfollow: FollowRef) async -> Bool {
|
func handle_unfollow(state: DamusState, unfollow: FollowRef) async -> Bool {
|
||||||
guard let keypair = state.keypair.to_full() else {
|
guard let keypair = state.keypair.to_full() else {
|
||||||
return false
|
return false
|
||||||
@@ -1043,6 +1044,7 @@ func handle_unfollow(state: DamusState, unfollow: FollowRef) async -> Bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
@MainActor
|
||||||
func handle_follow(state: DamusState, follow: FollowRef) async -> Bool {
|
func handle_follow(state: DamusState, follow: FollowRef) async -> Bool {
|
||||||
guard let keypair = state.keypair.to_full() else {
|
guard let keypair = state.keypair.to_full() else {
|
||||||
return false
|
return false
|
||||||
@@ -1071,9 +1073,9 @@ func handle_follow(state: DamusState, follow: FollowRef) async -> Bool {
|
|||||||
func handle_follow_notif(state: DamusState, target: FollowTarget) async -> Bool {
|
func handle_follow_notif(state: DamusState, target: FollowTarget) async -> Bool {
|
||||||
switch target {
|
switch target {
|
||||||
case .pubkey(let pk):
|
case .pubkey(let pk):
|
||||||
state.contacts.add_friend_pubkey(pk)
|
await state.contacts.add_friend_pubkey(pk)
|
||||||
case .contact(let ev):
|
case .contact(let ev):
|
||||||
state.contacts.add_friend_contact(ev)
|
await state.contacts.add_friend_contact(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await handle_follow(state: state, follow: target.follow_ref)
|
return await handle_follow(state: state, follow: target.follow_ref)
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ extension NostrNetworkManager {
|
|||||||
/// The latest contact list `NostrEvent`
|
/// The latest contact list `NostrEvent`
|
||||||
///
|
///
|
||||||
/// Note: Read-only access, because `NostrNetworkManager` does not manage contact lists.
|
/// Note: Read-only access, because `NostrNetworkManager` does not manage contact lists.
|
||||||
|
@MainActor
|
||||||
var latestContactListEvent: NostrEvent? { get }
|
var latestContactListEvent: NostrEvent? { get }
|
||||||
|
|
||||||
/// Default bootstrap relays to start with when a user relay list is not present
|
/// Default bootstrap relays to start with when a user relay list is not present
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ extension NostrNetworkManager {
|
|||||||
|
|
||||||
// MARK: - Computing the relays to connect to
|
// MARK: - Computing the relays to connect to
|
||||||
|
|
||||||
|
@MainActor
|
||||||
private func relaysToConnectTo() -> [RelayPool.RelayDescriptor] {
|
private func relaysToConnectTo() -> [RelayPool.RelayDescriptor] {
|
||||||
return self.computeRelaysToConnectTo(with: self.getBestEffortRelayList())
|
return self.computeRelaysToConnectTo(with: self.getBestEffortRelayList())
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,7 @@ extension NostrNetworkManager {
|
|||||||
/// It attempts to get a relay list from the user. If one is not available, it uses the default bootstrap list.
|
/// It attempts to get a relay list from the user. If one is not available, it uses the default bootstrap list.
|
||||||
///
|
///
|
||||||
/// This is always guaranteed to return a relay list.
|
/// This is always guaranteed to return a relay list.
|
||||||
|
@MainActor
|
||||||
func getBestEffortRelayList() -> NIP65.RelayList {
|
func getBestEffortRelayList() -> NIP65.RelayList {
|
||||||
guard let userCurrentRelayList = self.getUserCurrentRelayList() else {
|
guard let userCurrentRelayList = self.getUserCurrentRelayList() else {
|
||||||
return NIP65.RelayList(relays: delegate.bootstrapRelays)
|
return NIP65.RelayList(relays: delegate.bootstrapRelays)
|
||||||
@@ -59,6 +61,7 @@ extension NostrNetworkManager {
|
|||||||
/// Gets the user's current relay list.
|
/// Gets the user's current relay list.
|
||||||
///
|
///
|
||||||
/// It attempts to get a NIP-65 relay list from the local database, or falls back to a legacy list.
|
/// It attempts to get a NIP-65 relay list from the local database, or falls back to a legacy list.
|
||||||
|
@MainActor
|
||||||
func getUserCurrentRelayList() -> NIP65.RelayList? {
|
func getUserCurrentRelayList() -> NIP65.RelayList? {
|
||||||
if let latestRelayListEvent = try? self.getLatestNIP65RelayList() { return latestRelayListEvent }
|
if let latestRelayListEvent = try? self.getLatestNIP65RelayList() { return latestRelayListEvent }
|
||||||
if let latestRelayListEvent = try? self.getLatestKind3RelayList() { return latestRelayListEvent }
|
if let latestRelayListEvent = try? self.getLatestKind3RelayList() { return latestRelayListEvent }
|
||||||
@@ -93,6 +96,7 @@ extension NostrNetworkManager {
|
|||||||
/// Gets the latest `kind:3` relay list from NostrDB.
|
/// Gets the latest `kind:3` relay list from NostrDB.
|
||||||
///
|
///
|
||||||
/// This is `private` because it is part of internal logic. Callers should use the higher level functions.
|
/// This is `private` because it is part of internal logic. Callers should use the higher level functions.
|
||||||
|
@MainActor
|
||||||
private func getLatestKind3RelayList() throws(LoadingError) -> NIP65.RelayList? {
|
private func getLatestKind3RelayList() throws(LoadingError) -> NIP65.RelayList? {
|
||||||
guard let latestContactListEvent = delegate.latestContactListEvent else { return nil }
|
guard let latestContactListEvent = delegate.latestContactListEvent else { return nil }
|
||||||
guard let legacyContactList = try? NIP65.RelayList.fromLegacyContactList(latestContactListEvent) else { throw .relayListParseError }
|
guard let legacyContactList = try? NIP65.RelayList.fromLegacyContactList(latestContactListEvent) else { throw .relayListParseError }
|
||||||
@@ -114,6 +118,7 @@ extension NostrNetworkManager {
|
|||||||
|
|
||||||
/// Gets the creation date of the user's current relay list, with preference to NIP-65 relay lists
|
/// Gets the creation date of the user's current relay list, with preference to NIP-65 relay lists
|
||||||
/// - Returns: The current relay list's creation date
|
/// - Returns: The current relay list's creation date
|
||||||
|
@MainActor
|
||||||
private func getUserCurrentRelayListCreationDate() -> UInt32? {
|
private func getUserCurrentRelayListCreationDate() -> UInt32? {
|
||||||
if let latestNIP65RelayListEvent = self.getLatestNIP65RelayListEvent() { return latestNIP65RelayListEvent.created_at }
|
if let latestNIP65RelayListEvent = self.getLatestNIP65RelayListEvent() { return latestNIP65RelayListEvent.created_at }
|
||||||
if let latestKind3RelayListEvent = delegate.latestContactListEvent { return latestKind3RelayListEvent.created_at }
|
if let latestKind3RelayListEvent = delegate.latestContactListEvent { return latestKind3RelayListEvent.created_at }
|
||||||
@@ -134,7 +139,7 @@ extension NostrNetworkManager {
|
|||||||
func listenAndHandleRelayUpdates() async {
|
func listenAndHandleRelayUpdates() async {
|
||||||
let filter = NostrFilter(kinds: [.relay_list], authors: [delegate.keypair.pubkey])
|
let filter = NostrFilter(kinds: [.relay_list], authors: [delegate.keypair.pubkey])
|
||||||
for await noteLender in self.reader.streamIndefinitely(filters: [filter]) {
|
for await noteLender in self.reader.streamIndefinitely(filters: [filter]) {
|
||||||
let currentRelayListCreationDate = self.getUserCurrentRelayListCreationDate()
|
let currentRelayListCreationDate = await self.getUserCurrentRelayListCreationDate()
|
||||||
guard let note = noteLender.justGetACopy() else { continue }
|
guard let note = noteLender.justGetACopy() else { continue }
|
||||||
guard note.pubkey == self.delegate.keypair.pubkey else { continue } // Ensure this new list was ours
|
guard note.pubkey == self.delegate.keypair.pubkey else { continue } // Ensure this new list was ours
|
||||||
guard note.created_at > (currentRelayListCreationDate ?? 0) else { continue } // Ensure this is a newer list
|
guard note.created_at > (currentRelayListCreationDate ?? 0) else { continue } // Ensure this is a newer list
|
||||||
@@ -147,7 +152,7 @@ extension NostrNetworkManager {
|
|||||||
// MARK: - Editing the user's relay list
|
// MARK: - Editing the user's relay list
|
||||||
|
|
||||||
func upsert(relay: NIP65.RelayList.RelayItem, force: Bool = false, overwriteExisting: Bool = false) async throws(UpdateError) {
|
func upsert(relay: NIP65.RelayList.RelayItem, force: Bool = false, overwriteExisting: Bool = false) async throws(UpdateError) {
|
||||||
guard let currentUserRelayList = force ? self.getBestEffortRelayList() : self.getUserCurrentRelayList() else { throw .noInitialRelayList }
|
guard let currentUserRelayList = await force ? self.getBestEffortRelayList() : self.getUserCurrentRelayList() else { throw .noInitialRelayList }
|
||||||
guard !currentUserRelayList.relays.keys.contains(relay.url) || overwriteExisting else { throw .relayAlreadyExists }
|
guard !currentUserRelayList.relays.keys.contains(relay.url) || overwriteExisting else { throw .relayAlreadyExists }
|
||||||
var newList = currentUserRelayList.relays
|
var newList = currentUserRelayList.relays
|
||||||
newList[relay.url] = relay
|
newList[relay.url] = relay
|
||||||
@@ -155,13 +160,13 @@ extension NostrNetworkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func insert(relay: NIP65.RelayList.RelayItem, force: Bool = false) async throws(UpdateError) {
|
func insert(relay: NIP65.RelayList.RelayItem, force: Bool = false) async throws(UpdateError) {
|
||||||
guard let currentUserRelayList = force ? self.getBestEffortRelayList() : self.getUserCurrentRelayList() else { throw .noInitialRelayList }
|
guard let currentUserRelayList = await force ? self.getBestEffortRelayList() : self.getUserCurrentRelayList() else { throw .noInitialRelayList }
|
||||||
guard currentUserRelayList.relays[relay.url] == nil else { throw .relayAlreadyExists }
|
guard currentUserRelayList.relays[relay.url] == nil else { throw .relayAlreadyExists }
|
||||||
try await self.upsert(relay: relay, force: force)
|
try await self.upsert(relay: relay, force: force)
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(relayURL: RelayURL, force: Bool = false) async throws(UpdateError) {
|
func remove(relayURL: RelayURL, force: Bool = false) async throws(UpdateError) {
|
||||||
guard let currentUserRelayList = force ? self.getBestEffortRelayList() : self.getUserCurrentRelayList() else { throw .noInitialRelayList }
|
guard let currentUserRelayList = await force ? self.getBestEffortRelayList() : self.getUserCurrentRelayList() else { throw .noInitialRelayList }
|
||||||
guard currentUserRelayList.relays.keys.contains(relayURL) || force else { throw .noSuchRelay }
|
guard currentUserRelayList.relays.keys.contains(relayURL) || force else { throw .noSuchRelay }
|
||||||
var newList = currentUserRelayList.relays
|
var newList = currentUserRelayList.relays
|
||||||
newList[relayURL] = nil
|
newList[relayURL] = nil
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ class DamusState: HeadlessDamusState, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static var empty: DamusState {
|
static var empty: DamusState {
|
||||||
let empty_pub: Pubkey = .empty
|
let empty_pub: Pubkey = .empty
|
||||||
let empty_sec: Privkey = .empty
|
let empty_sec: Privkey = .empty
|
||||||
@@ -226,6 +227,7 @@ fileprivate extension DamusState {
|
|||||||
set { self.settings.latestRelayListEventIdHex = newValue }
|
set { self.settings.latestRelayListEventIdHex = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
var latestContactListEvent: NostrEvent? { self.contacts.event }
|
var latestContactListEvent: NostrEvent? { self.contacts.event }
|
||||||
var bootstrapRelays: [RelayURL] { get_default_bootstrap_relays() }
|
var bootstrapRelays: [RelayURL] { get_default_bootstrap_relays() }
|
||||||
var developerMode: Bool { self.settings.developer_mode }
|
var developerMode: Bool { self.settings.developer_mode }
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ struct DirectMessagesView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func would_filter_non_friends_from_dms(contacts: Contacts, dms: [DirectMessageModel]) -> Bool {
|
func would_filter_non_friends_from_dms(contacts: Contacts, dms: [DirectMessageModel]) -> Bool {
|
||||||
for dm in dms {
|
for dm in dms {
|
||||||
if !FriendFilter.friends_of_friends.filter(contacts: contacts, pubkey: dm.pubkey) {
|
if !FriendFilter.friends_of_friends.filter(contacts: contacts, pubkey: dm.pubkey) {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ struct EventView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// blame the porn bots for this code
|
// blame the porn bots for this code
|
||||||
|
@MainActor
|
||||||
func should_blur_images(settings: UserSettingsStore, contacts: Contacts, ev: NostrEvent, our_pubkey: Pubkey, booster_pubkey: Pubkey? = nil) -> Bool {
|
func should_blur_images(settings: UserSettingsStore, contacts: Contacts, ev: NostrEvent, our_pubkey: Pubkey, booster_pubkey: Pubkey? = nil) -> Bool {
|
||||||
if settings.undistractMode {
|
if settings.undistractMode {
|
||||||
return true
|
return true
|
||||||
@@ -80,6 +81,7 @@ func should_blur_images(settings: UserSettingsStore, contacts: Contacts, ev: Nos
|
|||||||
}
|
}
|
||||||
|
|
||||||
// blame the porn bots for this code too
|
// blame the porn bots for this code too
|
||||||
|
@MainActor
|
||||||
func should_blur_images(damus_state: DamusState, ev: NostrEvent) -> Bool {
|
func should_blur_images(damus_state: DamusState, ev: NostrEvent) -> Bool {
|
||||||
return should_blur_images(
|
return should_blur_images(
|
||||||
settings: damus_state.settings,
|
settings: damus_state.settings,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
@MainActor
|
||||||
class Contacts {
|
class Contacts {
|
||||||
private var friends: Set<Pubkey> = Set()
|
private var friends: Set<Pubkey> = Set()
|
||||||
private var friend_of_friends: Set<Pubkey> = Set()
|
private var friend_of_friends: Set<Pubkey> = Set()
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class NIP05DomainEventsModel: ObservableObject {
|
|||||||
filter.kinds = [.text, .longform, .highlight]
|
filter.kinds = [.text, .longform, .highlight]
|
||||||
|
|
||||||
var authors = Set<Pubkey>()
|
var authors = Set<Pubkey>()
|
||||||
for pubkey in state.contacts.get_friend_of_friends_list() {
|
for pubkey in await state.contacts.get_friend_of_friends_list() {
|
||||||
guard let profile = try? state.profiles.lookup(id: pubkey),
|
guard let profile = try? state.profiles.lookup(id: pubkey),
|
||||||
let nip05_str = profile.nip05,
|
let nip05_str = profile.nip05,
|
||||||
let nip05 = NIP05.parse(nip05_str),
|
let nip05 = NIP05.parse(nip05_str),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent
|
|||||||
}
|
}
|
||||||
|
|
||||||
if state.settings.notification_only_from_following,
|
if state.settings.notification_only_from_following,
|
||||||
state.contacts.follow_state(ev.pubkey) != .follows
|
await state.contacts.follow_state(ev.pubkey) != .follows
|
||||||
{
|
{
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class NotificationFilter: ObservableObject, Equatable {
|
|||||||
self.hellthread_notification_max_pubkeys = hellthread_notification_max_pubkeys
|
self.hellthread_notification_max_pubkeys = hellthread_notification_max_pubkeys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func filter(contacts: Contacts, items: [NotificationItem]) -> [NotificationItem] {
|
func filter(contacts: Contacts, items: [NotificationItem]) -> [NotificationItem] {
|
||||||
|
|
||||||
return items.reduce(into: []) { acc, item in
|
return items.reduce(into: []) { acc, item in
|
||||||
@@ -214,6 +215,7 @@ struct NotificationsView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func would_filter_non_friends_from_notifications(contacts: Contacts, state: NotificationFilterState, items: [NotificationItem]) -> Bool {
|
func would_filter_non_friends_from_notifications(contacts: Contacts, state: NotificationFilterState, items: [NotificationItem]) -> Bool {
|
||||||
for item in items {
|
for item in items {
|
||||||
// this is only valid depending on which tab we're looking at
|
// this is only valid depending on which tab we're looking at
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ enum CancelSendErr {
|
|||||||
case too_late
|
case too_late
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostBox {
|
actor PostBox {
|
||||||
private let pool: RelayPool
|
private let pool: RelayPool
|
||||||
var events: [NoteId: PostedEvent]
|
var events: [NoteId: PostedEvent]
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class PostBox {
|
|||||||
Task { await self.pool.register_handler(sub_id: "postbox", filters: nil, to: nil, handler: streamContinuation) }
|
Task { await self.pool.register_handler(sub_id: "postbox", filters: nil, to: nil, handler: streamContinuation) }
|
||||||
}
|
}
|
||||||
for await (relayUrl, connectionEvent) in stream {
|
for await (relayUrl, connectionEvent) in stream {
|
||||||
handle_event(relay_id: relayUrl, connectionEvent)
|
await handle_event(relay_id: relayUrl, connectionEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
conversationListener = nil
|
conversationListener = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func handle_profile_contact_event(_ ev: NostrEvent) {
|
func handle_profile_contact_event(_ ev: NostrEvent) {
|
||||||
process_contact_event(state: damus, ev: ev)
|
process_contact_event(state: damus, ev: ev)
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ enum FriendType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func get_friend_type(contacts: Contacts, pubkey: Pubkey) -> FriendType? {
|
func get_friend_type(contacts: Contacts, pubkey: Pubkey) -> FriendType? {
|
||||||
if contacts.is_friend_or_self(pubkey) {
|
if contacts.is_friend_or_self(pubkey) {
|
||||||
return .friend
|
return .friend
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ func get_profile_url(picture: String?, pubkey: Pubkey, profiles: Profiles) -> UR
|
|||||||
return URL(string: robohash(pubkey))!
|
return URL(string: robohash(pubkey))!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func make_preview_profiles(_ pubkey: Pubkey) -> Profiles {
|
func make_preview_profiles(_ pubkey: Pubkey) -> Profiles {
|
||||||
let profiles = Profiles(ndb: test_damus_state.ndb)
|
let profiles = Profiles(ndb: test_damus_state.ndb)
|
||||||
//let picture = "http://cdn.jb55.com/img/red-me.jpg"
|
//let picture = "http://cdn.jb55.com/img/red-me.jpg"
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ struct SearchResultsView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func search_for_string(profiles: Profiles, contacts: Contacts, search new: String) -> Search? {
|
func search_for_string(profiles: Profiles, contacts: Contacts, search new: String) -> Search? {
|
||||||
guard new.count != 0 else {
|
guard new.count != 0 else {
|
||||||
return nil
|
return nil
|
||||||
@@ -267,6 +267,7 @@ func make_hashtagable(_ str: String) -> String {
|
|||||||
return String(new.filter{$0 != " "})
|
return String(new.filter{$0 != " "})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func search_profiles(profiles: Profiles, contacts: Contacts, search: String) -> [Pubkey] {
|
func search_profiles(profiles: Profiles, contacts: Contacts, search: String) -> [Pubkey] {
|
||||||
// Search by hex pubkey.
|
// Search by hex pubkey.
|
||||||
if let pubkey = hex_decode_pubkey(search),
|
if let pubkey = hex_decode_pubkey(search),
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ enum FriendFilter: String, StringCodable {
|
|||||||
self.rawValue
|
self.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func filter(contacts: Contacts, pubkey: Pubkey) -> Bool {
|
func filter(contacts: Contacts, pubkey: Pubkey) -> Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .all:
|
case .all:
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ enum HomeResubFilter {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func filter(contacts: Contacts, ev: NostrEvent) -> Bool {
|
func filter(contacts: Contacts, ev: NostrEvent) -> Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .pubkey(let pk):
|
case .pubkey(let pk):
|
||||||
@@ -340,7 +341,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
|
|
||||||
// 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.nostrNetwork.postbox.remove_relayer(relay_id: nwc.relay, event_id: resp.req_id) {
|
if await damus_state.nostrNetwork.postbox.remove_relayer(relay_id: nwc.relay, event_id: resp.req_id) {
|
||||||
Log.debug("HomeModel: got NWC response, removed %s from the postbox", for: .nwc, resp.req_id.hex())
|
Log.debug("HomeModel: got NWC response, removed %s from the postbox", for: .nwc, resp.req_id.hex())
|
||||||
} else {
|
} else {
|
||||||
Log.debug("HomeModel: got NWC response, %s not found in the postbox, nothing to remove", for: .nwc, resp.req_id.hex())
|
Log.debug("HomeModel: got NWC response, %s not found in the postbox, nothing to remove", for: .nwc, resp.req_id.hex())
|
||||||
@@ -925,6 +926,7 @@ func update_signal_from_pool(signal: SignalModel, pool: RelayPool) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func add_contact_if_friend(contacts: Contacts, ev: NostrEvent) {
|
func add_contact_if_friend(contacts: Contacts, ev: NostrEvent) {
|
||||||
if !contacts.is_friend(ev.pubkey) {
|
if !contacts.is_friend(ev.pubkey) {
|
||||||
return
|
return
|
||||||
@@ -933,6 +935,7 @@ func add_contact_if_friend(contacts: Contacts, ev: NostrEvent) {
|
|||||||
contacts.add_friend_contact(ev)
|
contacts.add_friend_contact(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
||||||
let contacts = state.contacts
|
let contacts = state.contacts
|
||||||
let new_refs = Set<FollowRef>(ev.referenced_follows)
|
let new_refs = Set<FollowRef>(ev.referenced_follows)
|
||||||
@@ -1043,6 +1046,7 @@ func robohash(_ pk: Pubkey) -> String {
|
|||||||
return "https://robohash.org/" + pk.hex()
|
return "https://robohash.org/" + pk.hex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func load_our_stuff(state: DamusState, ev: NostrEvent) {
|
func load_our_stuff(state: DamusState, ev: NostrEvent) {
|
||||||
guard ev.pubkey == state.pubkey else {
|
guard ev.pubkey == state.pubkey else {
|
||||||
return
|
return
|
||||||
@@ -1061,6 +1065,7 @@ func load_our_stuff(state: DamusState, ev: NostrEvent) {
|
|||||||
load_our_contacts(state: state, m_old_ev: m_old_ev, ev: ev)
|
load_our_contacts(state: state, m_old_ev: m_old_ev, ev: ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func process_contact_event(state: DamusState, ev: NostrEvent) {
|
func process_contact_event(state: DamusState, ev: NostrEvent) {
|
||||||
load_our_stuff(state: state, ev: ev)
|
load_our_stuff(state: state, ev: ev)
|
||||||
add_contact_if_friend(contacts: state.contacts, ev: ev)
|
add_contact_if_friend(contacts: state.contacts, ev: ev)
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ struct WalletView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
let test_wallet_connect_url = WalletConnectURL(pubkey: test_pubkey, relay: .init("wss://relay.damus.io")!, keypair: test_damus_state.keypair.to_full()!, lud16: "jb55@sendsats.com")
|
let test_wallet_connect_url = WalletConnectURL(pubkey: test_pubkey, relay: .init("wss://relay.damus.io")!, keypair: test_damus_state.keypair.to_full()!, lud16: "jb55@sendsats.com")
|
||||||
|
|
||||||
struct WalletView_Previews: PreviewProvider {
|
struct WalletView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ struct NoteZapButton: View {
|
|||||||
return Color.orange
|
return Color.orange
|
||||||
}
|
}
|
||||||
|
|
||||||
func tap() {
|
func tap() async {
|
||||||
guard let our_zap else {
|
guard let our_zap else {
|
||||||
Task { await send_zap(damus_state: damus_state, target: target, lnurl: lnurl, is_custom: false, comment: nil, amount_sats: nil, zap_type: damus_state.settings.default_zap_type) }
|
Task { await send_zap(damus_state: damus_state, target: target, lnurl: lnurl, is_custom: false, comment: nil, amount_sats: nil, zap_type: damus_state.settings.default_zap_type) }
|
||||||
return
|
return
|
||||||
@@ -84,7 +84,7 @@ struct NoteZapButton: View {
|
|||||||
print("cancel_zap: we already have a real zap, can't cancel")
|
print("cancel_zap: we already have a real zap, can't cancel")
|
||||||
break
|
break
|
||||||
case .pending(let pzap):
|
case .pending(let pzap):
|
||||||
guard let res = cancel_zap(zap: pzap, box: damus_state.nostrNetwork.postbox, zapcache: damus_state.zaps, evcache: damus_state.events) else {
|
guard let res = await cancel_zap(zap: pzap, box: damus_state.nostrNetwork.postbox, zapcache: damus_state.zaps, evcache: damus_state.events) else {
|
||||||
|
|
||||||
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
||||||
return
|
return
|
||||||
@@ -146,7 +146,7 @@ struct NoteZapButton: View {
|
|||||||
.highPriorityGesture(TapGesture().onEnded {
|
.highPriorityGesture(TapGesture().onEnded {
|
||||||
guard !damus_state.settings.nozaps else { return }
|
guard !damus_state.settings.nozaps else { return }
|
||||||
|
|
||||||
tap()
|
Task { await tap() }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +276,7 @@ enum CancelZapErr {
|
|||||||
case not_nwc
|
case not_nwc
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancel_zap(zap: PendingZap, box: PostBox, zapcache: Zaps, evcache: EventCache) -> CancelZapErr? {
|
func cancel_zap(zap: PendingZap, box: PostBox, zapcache: Zaps, evcache: EventCache) async -> CancelZapErr? {
|
||||||
guard case .nwc(let nwc_state) = zap.state else {
|
guard case .nwc(let nwc_state) = zap.state else {
|
||||||
return .not_nwc
|
return .not_nwc
|
||||||
}
|
}
|
||||||
@@ -298,7 +298,7 @@ func cancel_zap(zap: PendingZap, box: PostBox, zapcache: Zaps, evcache: EventCac
|
|||||||
return .already_confirmed
|
return .already_confirmed
|
||||||
|
|
||||||
case .postbox_pending(let nwc_req):
|
case .postbox_pending(let nwc_req):
|
||||||
if let err = box.cancel_send(evid: nwc_req.id) {
|
if let err = await box.cancel_send(evid: nwc_req.id) {
|
||||||
return .send_err(err)
|
return .send_err(err)
|
||||||
}
|
}
|
||||||
let reqid = ZapRequestId(from_pending: zap)
|
let reqid = ZapRequestId(from_pending: zap)
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ extension View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func use_nip05_color(pubkey: Pubkey, contacts: Contacts) -> Bool {
|
func use_nip05_color(pubkey: Pubkey, contacts: Contacts) -> Bool {
|
||||||
return contacts.is_friend_or_self(pubkey) ? true : false
|
return contacts.is_friend_or_self(pubkey) ? true : false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,11 @@ let test_private_zap = Zap(event: test_note, invoice: test_zap_invoice, zapper:
|
|||||||
|
|
||||||
let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: test_note.id, author: test_note.pubkey), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
|
let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: test_note.id, author: test_note.pubkey), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
|
||||||
|
|
||||||
|
@MainActor
|
||||||
let test_following_model = FollowingModel(damus_state: test_damus_state, contacts: [test_pubkey, test_pubkey_2], hashtags: [Hashtag(hashtag: "grownostr"), Hashtag(hashtag: "zapathon")])
|
let test_following_model = FollowingModel(damus_state: test_damus_state, contacts: [test_pubkey, test_pubkey_2], hashtags: [Hashtag(hashtag: "grownostr"), Hashtag(hashtag: "zapathon")])
|
||||||
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
var test_damus_state: DamusState = ({
|
var test_damus_state: DamusState = ({
|
||||||
// Create a unique temporary directory
|
// Create a unique temporary directory
|
||||||
var tempDir: String!
|
var tempDir: String!
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ final class DamusCacheManagerTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Simple smoke test to check if clearing cache will crash the system
|
/// Simple smoke test to check if clearing cache will crash the system
|
||||||
|
@MainActor
|
||||||
func testCacheManagerSmoke() throws {
|
func testCacheManagerSmoke() throws {
|
||||||
for _ in Range(0...20) {
|
for _ in Range(0...20) {
|
||||||
DamusCacheManager.shared.clear_cache(damus_state: test_damus_state)
|
DamusCacheManager.shared.clear_cache(damus_state: test_damus_state)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ final class EventGroupViewTests: XCTestCase {
|
|||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testEventAuthorName() {
|
func testEventAuthorName() {
|
||||||
let damusState = test_damus_state
|
let damusState = test_damus_state
|
||||||
let damus_name = "17ldvg64:nq5mhr77"
|
let damus_name = "17ldvg64:nq5mhr77"
|
||||||
@@ -26,6 +27,7 @@ final class EventGroupViewTests: XCTestCase {
|
|||||||
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: ANON_PUBKEY), "Anonymous")
|
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: ANON_PUBKEY), "Anonymous")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testEventGroupUniquePubkeys() {
|
func testEventGroupUniquePubkeys() {
|
||||||
let damusState = test_damus_state
|
let damusState = test_damus_state
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ final class EventGroupViewTests: XCTestCase {
|
|||||||
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2, repost3]))), [pk1, pk2, pk3])
|
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2, repost3]))), [pk1, pk2, pk3])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testReactingToText() throws {
|
func testReactingToText() throws {
|
||||||
let enUsLocale = Locale(identifier: "en-US")
|
let enUsLocale = Locale(identifier: "en-US")
|
||||||
let damusState = test_damus_state
|
let damusState = test_damus_state
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ final class ContactCardManagerTests: XCTestCase {
|
|||||||
XCTAssertFalse(result)
|
XCTAssertFalse(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testIsFavorite_WhenPubkeyExists_ReturnsTrue() {
|
func testIsFavorite_WhenPubkeyExists_ReturnsTrue() {
|
||||||
// Given: A pubkey added to favorites
|
// Given: A pubkey added to favorites
|
||||||
let sut = ContactCardManager()
|
let sut = ContactCardManager()
|
||||||
@@ -36,6 +37,7 @@ final class ContactCardManagerTests: XCTestCase {
|
|||||||
XCTAssertTrue(result)
|
XCTAssertTrue(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testIsFavorite_WhenPubkeyDoesNotExist_ReturnsFalse() {
|
func testIsFavorite_WhenPubkeyDoesNotExist_ReturnsFalse() {
|
||||||
// Given: A different pubkey added to favorites
|
// Given: A different pubkey added to favorites
|
||||||
let sut = ContactCardManager()
|
let sut = ContactCardManager()
|
||||||
@@ -50,6 +52,7 @@ final class ContactCardManagerTests: XCTestCase {
|
|||||||
XCTAssertFalse(result)
|
XCTAssertFalse(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testToggleFavorite_WhenNotFavorite_AddsToFavorites() {
|
func testToggleFavorite_WhenNotFavorite_AddsToFavorites() {
|
||||||
// Given: A pubkey not in favorites
|
// Given: A pubkey not in favorites
|
||||||
let sut = ContactCardManager()
|
let sut = ContactCardManager()
|
||||||
@@ -64,6 +67,7 @@ final class ContactCardManagerTests: XCTestCase {
|
|||||||
XCTAssertEqual(sut.favorites.count, 1)
|
XCTAssertEqual(sut.favorites.count, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testToggleFavorite_WhenAlreadyFavorite_RemovesFromFavorites() {
|
func testToggleFavorite_WhenAlreadyFavorite_RemovesFromFavorites() {
|
||||||
// Given: A pubkey already in favorites
|
// Given: A pubkey already in favorites
|
||||||
let sut = ContactCardManager()
|
let sut = ContactCardManager()
|
||||||
@@ -105,6 +109,7 @@ final class ContactCardManagerTests: XCTestCase {
|
|||||||
XCTAssertTrue(sut.isFavorite(targetPubkey))
|
XCTAssertTrue(sut.isFavorite(targetPubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testloadEvent_WithContactCard_RemovesFromFavorites() {
|
func testloadEvent_WithContactCard_RemovesFromFavorites() {
|
||||||
// Given: A contact card event without favorite tag (unfavorite)
|
// Given: A contact card event without favorite tag (unfavorite)
|
||||||
let sut = ContactCardManager()
|
let sut = ContactCardManager()
|
||||||
@@ -219,6 +224,7 @@ final class ContactCardManagerTests: XCTestCase {
|
|||||||
XCTAssertTrue(sut.isFavorite(targetPubkey))
|
XCTAssertTrue(sut.isFavorite(targetPubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testFilter_WithFavoritePubkey_ReturnsTrue() {
|
func testFilter_WithFavoritePubkey_ReturnsTrue() {
|
||||||
// Given: A pubkey in favorites
|
// Given: A pubkey in favorites
|
||||||
let sut = ContactCardManager()
|
let sut = ContactCardManager()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import XCTest
|
|||||||
@testable import damus
|
@testable import damus
|
||||||
|
|
||||||
final class MutingTests: XCTestCase {
|
final class MutingTests: XCTestCase {
|
||||||
|
@MainActor
|
||||||
func testWordMuting() async {
|
func testWordMuting() async {
|
||||||
// Setup some test data
|
// Setup some test data
|
||||||
let test_note = NostrEvent(
|
let test_note = NostrEvent(
|
||||||
|
|||||||
@@ -344,6 +344,7 @@ class NoteContentViewTests: XCTestCase {
|
|||||||
|
|
||||||
/// Quick test that exercises the direct parsing methods (i.e. not fetching blocks from nostrdb) from `NdbBlockGroup`, and its bridging code with C.
|
/// Quick test that exercises the direct parsing methods (i.e. not fetching blocks from nostrdb) from `NdbBlockGroup`, and its bridging code with C.
|
||||||
/// The parsing logic itself already has test coverage at the nostrdb level.
|
/// The parsing logic itself already has test coverage at the nostrdb level.
|
||||||
|
@MainActor
|
||||||
func testDirectBlockParsing() {
|
func testDirectBlockParsing() {
|
||||||
let kp = test_keypair_full
|
let kp = test_keypair_full
|
||||||
let dm: NdbNote = NIP04.create_dm("Test", to_pk: kp.pubkey, tags: [], keypair: kp.to_keypair())!
|
let dm: NdbNote = NIP04.create_dm("Test", to_pk: kp.pubkey, tags: [], keypair: kp.to_keypair())!
|
||||||
@@ -360,24 +361,28 @@ class NoteContentViewTests: XCTestCase {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Pubkey_ContainsAbbreviated() throws {
|
func testMentionStr_Pubkey_ContainsAbbreviated() throws {
|
||||||
let compatibleText = createCompatibleText(test_pubkey.npub)
|
let compatibleText = createCompatibleText(test_pubkey.npub)
|
||||||
|
|
||||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "17ldvg64:nq5mhr77")
|
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "17ldvg64:nq5mhr77")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Pubkey_ContainsFullBech32() {
|
func testMentionStr_Pubkey_ContainsFullBech32() {
|
||||||
let compatableText = createCompatibleText(test_pubkey.npub)
|
let compatableText = createCompatibleText(test_pubkey.npub)
|
||||||
|
|
||||||
assertCompatibleTextHasExpectedString(compatibleText: compatableText, expected: test_pubkey.npub)
|
assertCompatibleTextHasExpectedString(compatibleText: compatableText, expected: test_pubkey.npub)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Nprofile_ContainsAbbreviated() throws {
|
func testMentionStr_Nprofile_ContainsAbbreviated() throws {
|
||||||
let compatibleText = createCompatibleText("nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p")
|
let compatibleText = createCompatibleText("nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p")
|
||||||
|
|
||||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "180cvv07:wsyjh6w6")
|
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "180cvv07:wsyjh6w6")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Nprofile_ContainsFullBech32() throws {
|
func testMentionStr_Nprofile_ContainsFullBech32() throws {
|
||||||
let bech = "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p"
|
let bech = "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p"
|
||||||
let compatibleText = createCompatibleText(bech)
|
let compatibleText = createCompatibleText(bech)
|
||||||
@@ -385,18 +390,21 @@ class NoteContentViewTests: XCTestCase {
|
|||||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: bech)
|
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: bech)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Note_ContainsAbbreviated() {
|
func testMentionStr_Note_ContainsAbbreviated() {
|
||||||
let compatibleText = createCompatibleText(test_note.id.bech32)
|
let compatibleText = createCompatibleText(test_note.id.bech32)
|
||||||
|
|
||||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "note1qqq:qqn2l0z3")
|
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "note1qqq:qqn2l0z3")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Note_ContainsFullBech32() {
|
func testMentionStr_Note_ContainsFullBech32() {
|
||||||
let compatibleText = createCompatibleText(test_note.id.bech32)
|
let compatibleText = createCompatibleText(test_note.id.bech32)
|
||||||
|
|
||||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: test_note.id.bech32)
|
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: test_note.id.bech32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Nevent_ContainsAbbreviated() {
|
func testMentionStr_Nevent_ContainsAbbreviated() {
|
||||||
let bech = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
|
let bech = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
|
||||||
let compatibleText = createCompatibleText(bech)
|
let compatibleText = createCompatibleText(bech)
|
||||||
@@ -404,6 +412,7 @@ class NoteContentViewTests: XCTestCase {
|
|||||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "nevent1q:t5nxnepm")
|
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "nevent1q:t5nxnepm")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Nevent_ContainsFullBech32() throws {
|
func testMentionStr_Nevent_ContainsFullBech32() throws {
|
||||||
let bech = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
|
let bech = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
|
||||||
let compatibleText = createCompatibleText(bech)
|
let compatibleText = createCompatibleText(bech)
|
||||||
@@ -411,6 +420,7 @@ class NoteContentViewTests: XCTestCase {
|
|||||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: bech)
|
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: bech)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Naddr_ContainsAbbreviated() {
|
func testMentionStr_Naddr_ContainsAbbreviated() {
|
||||||
let bech = "naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld"
|
let bech = "naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld"
|
||||||
let compatibleText = createCompatibleText(bech)
|
let compatibleText = createCompatibleText(bech)
|
||||||
@@ -418,6 +428,7 @@ class NoteContentViewTests: XCTestCase {
|
|||||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "naddr1qq:3cnmhuld")
|
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "naddr1qq:3cnmhuld")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testMentionStr_Naddr_ContainsFullBech32() {
|
func testMentionStr_Naddr_ContainsFullBech32() {
|
||||||
let bech = "naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld"
|
let bech = "naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld"
|
||||||
let compatibleText = createCompatibleText(bech)
|
let compatibleText = createCompatibleText(bech)
|
||||||
@@ -436,6 +447,7 @@ private func assertCompatibleTextHasExpectedString(compatibleText: CompatibleTex
|
|||||||
XCTAssertTrue(hasExpected)
|
XCTAssertTrue(hasExpected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
private func createCompatibleText(_ bechString: String) -> CompatibleText {
|
private func createCompatibleText(_ bechString: String) -> CompatibleText {
|
||||||
guard let mentionRef = Bech32Object.parse(bechString)?.toMentionRef() else {
|
guard let mentionRef = Bech32Object.parse(bechString)?.toMentionRef() else {
|
||||||
XCTFail("Failed to create MentionRef from Bech32 string")
|
XCTFail("Failed to create MentionRef from Bech32 string")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import XCTest
|
|||||||
|
|
||||||
final class RepostedTests: XCTestCase {
|
final class RepostedTests: XCTestCase {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testPeopleRepostedText() throws {
|
func testPeopleRepostedText() throws {
|
||||||
let enUsLocale = Locale(identifier: "en-US")
|
let enUsLocale = Locale(identifier: "en-US")
|
||||||
let damusState = test_damus_state
|
let damusState = test_damus_state
|
||||||
|
|||||||
@@ -94,8 +94,9 @@ final class WalletConnectTests: XCTestCase {
|
|||||||
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.url.absoluteString, "ws://127.0.0.1")
|
XCTAssertEqual(pool.all_descriptors[0].url.url.absoluteString, "ws://127.0.0.1")
|
||||||
XCTAssertEqual(box.events.count, 1)
|
let boxEventCount = await box.events.count
|
||||||
let ev = box.events.first!.value
|
XCTAssertEqual(boxEventCount, 1)
|
||||||
|
let ev = await 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.url.absoluteString, "ws://127.0.0.1")
|
XCTAssertEqual(ev.remaining[0].relay.url.absoluteString, "ws://127.0.0.1")
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ final class ZapTests: XCTestCase {
|
|||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func test_alby_zap() throws {
|
func test_alby_zap() throws {
|
||||||
let zapjson = "eyJjb250ZW50Ijoi4pqhTm9uLWN1c3RvZGlhbCB6YXAgZnJvbSBteSBBbGJ5IEh1YiIsImNyZWF0ZWRfYXQiOjE3MjQ2ODUwNDcsImlkIjoiNGM3NWFiMWU3MDk4Y2NiN2FlYjhmZjdkNDIwMjM2ZDM1N2U1OGNjZmI3OWZiZTEwMTcwNGNiMzY0OTg3YjY4YSIsImtpbmQiOjk3MzUsInB1YmtleSI6Ijc5ZjAwZDNmNWExOWVjODA2MTg5ZmNhYjAzYzFiZTRmZjgxZDE4ZWU0ZjY1M2M4OGZhYzQxZmUwMzU3MGY0MzIiLCJzaWciOiI3OWM5ZDJjN2ExZWI1NmNhZjMyOTY1ZTRkMDJlYjJiYjFmYTY3NGViMDM4ZWE2MmFjZTg2YzBiMzA2OTJhMjU0YWU0M2JhNmMzNjcyMDJkZjgxNzQ5NGNhNTg4NzRkNWI1OWMxY2VhMDdjZTk5Mjk0MmIyOWYwZmVlZmJlM2FiZCIsInRhZ3MiOltbInAiLCIxNWI1Y2Y2Y2RmNGZkMWMwMmYyOGJjY2UwZjE5N2NhZmFlNGM4YzdjNjZhM2UyZTIzYWY5ZmU2MTA4NzUzMTVlIl0sWyJlIiwiYmNiMmZjZmUxYzQ2N2M1ZWM4Mjg1ZTM4NWMzNmVjMTM4Nzk3MDljZWQ5ZDg4MDBjYjM0MGViZjIxOGMzMjEwZCJdLFsiUCIsIjA1MjFkYjk1MzEwOTZkZmY3MDBkY2Y0MTBiMDFkYjQ3YWI2NTk4ZGU3ZTVlZjJjNWEyYmQ3ZTExNjAzMTViZjYiXSxbImJvbHQxMSIsImxuYmMxMHUxcG52ZXhoM2RwdXUyZDJ6bm4wZGNra3hhdG53M2hrZzZ0cGRzczg1Y3RzeXBuOHltbWR5cGtoamd6cGQzMzhqZ3pndzQzcW5wNHEyMjhhMnp0eGt3emF5cHZ6cnNoODIzcW5nbXY5N2YydjlwdXd2dHNhZGV0eXBtdXR5c2N3cHA1dmxjbGwwMHpwcGhoMzJ3OHV0NWpwcDVhMmZtcWg4c3o3bnUyaDd2MDdyMHU1bHN3ZzVsc3NwNXh2YXFlZnpsY2t6bXYwdzg5bHIwazB5dnI1eGQybmc1MmE1cmNkYXJmbTRmMGEwd2dwdXE5cXl5c2dxY3FwY3hxeXo1dnFlcHMzOXNleDUyc2ZtdHU5Z25tNWRhcGs1bGdsZDRwcDk2dXI1YTRhbTk0MHEyNXd6ZHNycmo1MjN4eWEwcnV4YTVscjk2M2cwMjk2cjZtZGZ5MjR2NjUzZXZjcHh5cjBtbWhnd21zcXh2cmhmZCJdLFsicHJlaW1hZ2UiLCJhZDA0N2MwMmZlNWYwNTljODA4NzdkNzk0YmU4OGU0N2M2NDRlYmVkZmRmZTY2M2IyODljOTMxNmRiNDk1ZjJkIl0sWyJkZXNjcmlwdGlvbiIsIntcImtpbmRcIjo5NzM0LFwiY3JlYXRlZF9hdFwiOjE3MjQ2ODUwMzgsXCJjb250ZW50XCI6XCLimqFOb24tY3VzdG9kaWFsIHphcCBmcm9tIG15IEFsYnkgSHViXCIsXCJ0YWdzXCI6W1tcInBcIixcIjE1YjVjZjZjZGY0ZmQxYzAyZjI4YmNjZTBmMTk3Y2FmYWU0YzhjN2M2NmEzZTJlMjNhZjlmZTYxMDg3NTMxNWVcIl0sW1wicmVsYXlzXCIsXCJ3c3M6Ly9wdXJwbGVwYWcuZXMvXCIsXCJ3c3M6Ly9yZWxheS5nZXRhbGJ5LmNvbS92MVwiLFwid3NzOi8vbm9zdHIubW9tL1wiLFwid3NzOi8vbm9zdHIub3h0ci5kZXYvXCIsXCJ3c3M6Ly9ub3MubG9sL1wiLFwid3NzOi8vbm9zdHIud2luZS9cIixcIndzczovL3JlbGF5LmRhbXVzLmlvL1wiLFwid3NzOi8vcmVsYXkubm90b3NoaS53aW4vXCIsXCJ3c3M6Ly9lZGVuLm5vc3RyLmxhbmQvXCJdLFtcImFtb3VudFwiLFwiMTAwMDAwMFwiXSxbXCJlXCIsXCJiY2IyZmNmZTFjNDY3YzVlYzgyODVlMzg1YzM2ZWMxMzg3OTcwOWNlZDlkODgwMGNiMzQwZWJmMjE4YzMyMTBkXCJdXSxcInB1YmtleVwiOlwiMDUyMWRiOTUzMTA5NmRmZjcwMGRjZjQxMGIwMWRiNDdhYjY1OThkZTdlNWVmMmM1YTJiZDdlMTE2MDMxNWJmNlwiLFwiaWRcIjpcIjU3ZDg2MTIwMDc1MjFjMGI1MzJiOTFhZjI0OTgwOTVhMjUxZTYzZjQyNTE4N2U2Yzk1NzAwZmQwYTZiYWI3ZDRcIixcInNpZ1wiOlwiNzk4ZDczNTExOGJjZDE0MjI4YTEyYjZkNTI0MjNmZjI1YmI0ZWQ4Y2Q1ZGFjZjJmNTk3MWVmNTczZmRjM2ZjMDVmYzc5MzE4NWU2OTY4MmNjYTI0M2Q2NGYxNDdhNDQ5ODk2OGEwYmMyODhhZTgzZTc1YzAzZTk5ZjkzNmE2MDNcIn0iXV19Cg=="
|
let zapjson = "eyJjb250ZW50Ijoi4pqhTm9uLWN1c3RvZGlhbCB6YXAgZnJvbSBteSBBbGJ5IEh1YiIsImNyZWF0ZWRfYXQiOjE3MjQ2ODUwNDcsImlkIjoiNGM3NWFiMWU3MDk4Y2NiN2FlYjhmZjdkNDIwMjM2ZDM1N2U1OGNjZmI3OWZiZTEwMTcwNGNiMzY0OTg3YjY4YSIsImtpbmQiOjk3MzUsInB1YmtleSI6Ijc5ZjAwZDNmNWExOWVjODA2MTg5ZmNhYjAzYzFiZTRmZjgxZDE4ZWU0ZjY1M2M4OGZhYzQxZmUwMzU3MGY0MzIiLCJzaWciOiI3OWM5ZDJjN2ExZWI1NmNhZjMyOTY1ZTRkMDJlYjJiYjFmYTY3NGViMDM4ZWE2MmFjZTg2YzBiMzA2OTJhMjU0YWU0M2JhNmMzNjcyMDJkZjgxNzQ5NGNhNTg4NzRkNWI1OWMxY2VhMDdjZTk5Mjk0MmIyOWYwZmVlZmJlM2FiZCIsInRhZ3MiOltbInAiLCIxNWI1Y2Y2Y2RmNGZkMWMwMmYyOGJjY2UwZjE5N2NhZmFlNGM4YzdjNjZhM2UyZTIzYWY5ZmU2MTA4NzUzMTVlIl0sWyJlIiwiYmNiMmZjZmUxYzQ2N2M1ZWM4Mjg1ZTM4NWMzNmVjMTM4Nzk3MDljZWQ5ZDg4MDBjYjM0MGViZjIxOGMzMjEwZCJdLFsiUCIsIjA1MjFkYjk1MzEwOTZkZmY3MDBkY2Y0MTBiMDFkYjQ3YWI2NTk4ZGU3ZTVlZjJjNWEyYmQ3ZTExNjAzMTViZjYiXSxbImJvbHQxMSIsImxuYmMxMHUxcG52ZXhoM2RwdXUyZDJ6bm4wZGNra3hhdG53M2hrZzZ0cGRzczg1Y3RzeXBuOHltbWR5cGtoamd6cGQzMzhqZ3pndzQzcW5wNHEyMjhhMnp0eGt3emF5cHZ6cnNoODIzcW5nbXY5N2YydjlwdXd2dHNhZGV0eXBtdXR5c2N3cHA1dmxjbGwwMHpwcGhoMzJ3OHV0NWpwcDVhMmZtcWg4c3o3bnUyaDd2MDdyMHU1bHN3ZzVsc3NwNXh2YXFlZnpsY2t6bXYwdzg5bHIwazB5dnI1eGQybmc1MmE1cmNkYXJmbTRmMGEwd2dwdXE5cXl5c2dxY3FwY3hxeXo1dnFlcHMzOXNleDUyc2ZtdHU5Z25tNWRhcGs1bGdsZDRwcDk2dXI1YTRhbTk0MHEyNXd6ZHNycmo1MjN4eWEwcnV4YTVscjk2M2cwMjk2cjZtZGZ5MjR2NjUzZXZjcHh5cjBtbWhnd21zcXh2cmhmZCJdLFsicHJlaW1hZ2UiLCJhZDA0N2MwMmZlNWYwNTljODA4NzdkNzk0YmU4OGU0N2M2NDRlYmVkZmRmZTY2M2IyODljOTMxNmRiNDk1ZjJkIl0sWyJkZXNjcmlwdGlvbiIsIntcImtpbmRcIjo5NzM0LFwiY3JlYXRlZF9hdFwiOjE3MjQ2ODUwMzgsXCJjb250ZW50XCI6XCLimqFOb24tY3VzdG9kaWFsIHphcCBmcm9tIG15IEFsYnkgSHViXCIsXCJ0YWdzXCI6W1tcInBcIixcIjE1YjVjZjZjZGY0ZmQxYzAyZjI4YmNjZTBmMTk3Y2FmYWU0YzhjN2M2NmEzZTJlMjNhZjlmZTYxMDg3NTMxNWVcIl0sW1wicmVsYXlzXCIsXCJ3c3M6Ly9wdXJwbGVwYWcuZXMvXCIsXCJ3c3M6Ly9yZWxheS5nZXRhbGJ5LmNvbS92MVwiLFwid3NzOi8vbm9zdHIubW9tL1wiLFwid3NzOi8vbm9zdHIub3h0ci5kZXYvXCIsXCJ3c3M6Ly9ub3MubG9sL1wiLFwid3NzOi8vbm9zdHIud2luZS9cIixcIndzczovL3JlbGF5LmRhbXVzLmlvL1wiLFwid3NzOi8vcmVsYXkubm90b3NoaS53aW4vXCIsXCJ3c3M6Ly9lZGVuLm5vc3RyLmxhbmQvXCJdLFtcImFtb3VudFwiLFwiMTAwMDAwMFwiXSxbXCJlXCIsXCJiY2IyZmNmZTFjNDY3YzVlYzgyODVlMzg1YzM2ZWMxMzg3OTcwOWNlZDlkODgwMGNiMzQwZWJmMjE4YzMyMTBkXCJdXSxcInB1YmtleVwiOlwiMDUyMWRiOTUzMTA5NmRmZjcwMGRjZjQxMGIwMWRiNDdhYjY1OThkZTdlNWVmMmM1YTJiZDdlMTE2MDMxNWJmNlwiLFwiaWRcIjpcIjU3ZDg2MTIwMDc1MjFjMGI1MzJiOTFhZjI0OTgwOTVhMjUxZTYzZjQyNTE4N2U2Yzk1NzAwZmQwYTZiYWI3ZDRcIixcInNpZ1wiOlwiNzk4ZDczNTExOGJjZDE0MjI4YTEyYjZkNTI0MjNmZjI1YmI0ZWQ4Y2Q1ZGFjZjJmNTk3MWVmNTczZmRjM2ZjMDVmYzc5MzE4NWU2OTY4MmNjYTI0M2Q2NGYxNDdhNDQ5ODk2OGEwYmMyODhhZTgzZTc1YzAzZTk5ZjkzNmE2MDNcIn0iXV19Cg=="
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ final class ZapTests: XCTestCase {
|
|||||||
XCTAssertEqual(message, decrypted.content)
|
XCTAssertEqual(message, decrypted.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func testZap() throws {
|
func testZap() throws {
|
||||||
let zapjson = "eyJpZCI6IjUzNmJlZTllODNjODE4ZTNiODJjMTAxOTM1MTI4YWUyN2EwZDQyOTAwMzlhYWYyNTNlZmU1ZjA5MjMyYzE5NjIiLCJwdWJrZXkiOiI5NjMwZjQ2NGNjYTZhNTE0N2FhOGEzNWYwYmNkZDNjZTQ4NTMyNGU3MzJmZDM5ZTA5MjMzYjFkODQ4MjM4ZjMxIiwiY3JlYXRlZF9hdCI6MTY3NDIwNDUzNSwia2luZCI6OTczNSwidGFncyI6W1sicCIsIjMyZTE4Mjc2MzU0NTBlYmIzYzVhN2QxMmMxZjhlN2IyYjUxNDQzOWFjMTBhNjdlZWYzZDlmZDljNWM2OGUyNDUiXSxbImJvbHQxMSIsImxuYmMxMHUxcDN1NTR0bnNwNTcyOXF2eG5renRqamtkNTg1eW4wbDg2MzBzMm01eDZsNTZ3eXk0ZWMybnU4eHV6NjI5eHFwcDV2MnE3aHVjNGpwamgwM2Z4OHVqZXQ1Nms3OWd4cXg3bWUycGV2ejZqMms4dDhtNGxnNXZxaHA1eWc1MDU3OGNtdWoyNG1mdDNxcnNybWd3ZjMwa2U3YXY3ZDc3Z2FtZmxkazlrNHNmMzltcXhxeWp3NXFjcXBqcnpqcTJoeWVoNXEzNmx3eDZ6dHd5cmw2dm1tcnZ6NnJ1ZndqZnI4N3lremZuYXR1a200dWRzNHl6YWszc3FxOW1jcXFxcXFxcWxncXFxcTg2cXF5ZzlxeHBxeXNncWFkeWVjdmR6ZjI3MHBkMzZyc2FmbDA3azQ1ZmNqMnN5OGU1djJ0ZW5kNTB2OTU3NnV4cDNkdmp6amV1aHJlODl5cGdjbTkwZDZsbTAwNGszMHlqNGF2NW1jc3M1bnl4NHU5bmVyOWdwcHY2eXF3Il0sWyJkZXNjcmlwdGlvbiIsIntcImlkXCI6XCJiMDkyMTYzNGIxYmI4ZWUzNTg0YmJiZjJlOGQ3OTBhZDk4NTk5ZDhlMDhmODFjNzAwZGRiZTQ4MjAxNTY4Yjk3XCIsXCJwdWJrZXlcIjpcIjdmYTU2ZjVkNjk2MmFiMWUzY2Q0MjRlNzU4YzMwMDJiODY2NWY3YjBkOGRjZWU5ZmU5ZTI4OGQ3NzUxYWMxOTRcIixcImNyZWF0ZWRfYXRcIjoxNjc0MjA0NTMxLFwia2luZFwiOjk3MzQsXCJ0YWdzXCI6W1tcInBcIixcIjMyZTE4Mjc2MzU0NTBlYmIzYzVhN2QxMmMxZjhlN2IyYjUxNDQzOWFjMTBhNjdlZWYzZDlmZDljNWM2OGUyNDVcIl0sW1wicmVsYXlzXCIsXCJ3c3M6Ly9yZWxheS5zbm9ydC5zb2NpYWxcIixcIndzczovL3JlbGF5LmRhbXVzLmlvXCIsXCJ3c3M6Ly9ub3N0ci1wdWIud2VsbG9yZGVyLm5ldFwiLFwid3NzOi8vbm9zdHIudjBsLmlvXCIsXCJ3c3M6Ly9wcml2YXRlLW5vc3RyLnYwbC5pb1wiLFwid3NzOi8vbm9zdHIuemViZWRlZS5jbG91ZFwiLFwid3NzOi8vcmVsYXkubm9zdHIuaW5mby9cIl1dLFwiY29udGVudFwiOlwiXCIsXCJzaWdcIjpcImQwODQwNGU2MjVmOWM1NjMzYWZhZGQxMWMxMTBiYTg4ZmNkYjRiOWUwOTJiOTg0MGU3NDgyYThkNTM3YjFmYzExODY5MmNmZDEzMWRkODMzNTM2NDc2OWE2NzE3NTRhZDdhYTk3MzEzNjgzYTRhZDdlZmI3NjQ3NmMwNGU1ZjE3XCJ9Il0sWyJwcmVpbWFnZSIsIjNlMDJhM2FmOGM4YmNmMmEzNzUzYzg3ZjMxMTJjNjU2YTIwMTE0ZWUwZTk4ZDgyMTliYzU2ZjVlOGE3MjM1YjMiXV0sImNvbnRlbnQiOiIiLCJzaWciOiIzYWI0NGQwZTIyMjhiYmQ0ZDIzNDFjM2ZhNzQwOTZjZmY2ZjU1Y2ZkYTk5YTVkYWRjY2Y0NWM2NjQ2MzdlMjExNTFiMmY5ZGQwMDQwZjFhMjRlOWY4Njg2NzM4YjE2YmY4MTM0YmRiZTQxYTIxOGM5MTFmN2JiMzFlNTk1NzhkMSJ9Cg=="
|
let zapjson = "eyJpZCI6IjUzNmJlZTllODNjODE4ZTNiODJjMTAxOTM1MTI4YWUyN2EwZDQyOTAwMzlhYWYyNTNlZmU1ZjA5MjMyYzE5NjIiLCJwdWJrZXkiOiI5NjMwZjQ2NGNjYTZhNTE0N2FhOGEzNWYwYmNkZDNjZTQ4NTMyNGU3MzJmZDM5ZTA5MjMzYjFkODQ4MjM4ZjMxIiwiY3JlYXRlZF9hdCI6MTY3NDIwNDUzNSwia2luZCI6OTczNSwidGFncyI6W1sicCIsIjMyZTE4Mjc2MzU0NTBlYmIzYzVhN2QxMmMxZjhlN2IyYjUxNDQzOWFjMTBhNjdlZWYzZDlmZDljNWM2OGUyNDUiXSxbImJvbHQxMSIsImxuYmMxMHUxcDN1NTR0bnNwNTcyOXF2eG5renRqamtkNTg1eW4wbDg2MzBzMm01eDZsNTZ3eXk0ZWMybnU4eHV6NjI5eHFwcDV2MnE3aHVjNGpwamgwM2Z4OHVqZXQ1Nms3OWd4cXg3bWUycGV2ejZqMms4dDhtNGxnNXZxaHA1eWc1MDU3OGNtdWoyNG1mdDNxcnNybWd3ZjMwa2U3YXY3ZDc3Z2FtZmxkazlrNHNmMzltcXhxeWp3NXFjcXBqcnpqcTJoeWVoNXEzNmx3eDZ6dHd5cmw2dm1tcnZ6NnJ1ZndqZnI4N3lremZuYXR1a200dWRzNHl6YWszc3FxOW1jcXFxcXFxcWxncXFxcTg2cXF5ZzlxeHBxeXNncWFkeWVjdmR6ZjI3MHBkMzZyc2FmbDA3azQ1ZmNqMnN5OGU1djJ0ZW5kNTB2OTU3NnV4cDNkdmp6amV1aHJlODl5cGdjbTkwZDZsbTAwNGszMHlqNGF2NW1jc3M1bnl4NHU5bmVyOWdwcHY2eXF3Il0sWyJkZXNjcmlwdGlvbiIsIntcImlkXCI6XCJiMDkyMTYzNGIxYmI4ZWUzNTg0YmJiZjJlOGQ3OTBhZDk4NTk5ZDhlMDhmODFjNzAwZGRiZTQ4MjAxNTY4Yjk3XCIsXCJwdWJrZXlcIjpcIjdmYTU2ZjVkNjk2MmFiMWUzY2Q0MjRlNzU4YzMwMDJiODY2NWY3YjBkOGRjZWU5ZmU5ZTI4OGQ3NzUxYWMxOTRcIixcImNyZWF0ZWRfYXRcIjoxNjc0MjA0NTMxLFwia2luZFwiOjk3MzQsXCJ0YWdzXCI6W1tcInBcIixcIjMyZTE4Mjc2MzU0NTBlYmIzYzVhN2QxMmMxZjhlN2IyYjUxNDQzOWFjMTBhNjdlZWYzZDlmZDljNWM2OGUyNDVcIl0sW1wicmVsYXlzXCIsXCJ3c3M6Ly9yZWxheS5zbm9ydC5zb2NpYWxcIixcIndzczovL3JlbGF5LmRhbXVzLmlvXCIsXCJ3c3M6Ly9ub3N0ci1wdWIud2VsbG9yZGVyLm5ldFwiLFwid3NzOi8vbm9zdHIudjBsLmlvXCIsXCJ3c3M6Ly9wcml2YXRlLW5vc3RyLnYwbC5pb1wiLFwid3NzOi8vbm9zdHIuemViZWRlZS5jbG91ZFwiLFwid3NzOi8vcmVsYXkubm9zdHIuaW5mby9cIl1dLFwiY29udGVudFwiOlwiXCIsXCJzaWdcIjpcImQwODQwNGU2MjVmOWM1NjMzYWZhZGQxMWMxMTBiYTg4ZmNkYjRiOWUwOTJiOTg0MGU3NDgyYThkNTM3YjFmYzExODY5MmNmZDEzMWRkODMzNTM2NDc2OWE2NzE3NTRhZDdhYTk3MzEzNjgzYTRhZDdlZmI3NjQ3NmMwNGU1ZjE3XCJ9Il0sWyJwcmVpbWFnZSIsIjNlMDJhM2FmOGM4YmNmMmEzNzUzYzg3ZjMxMTJjNjU2YTIwMTE0ZWUwZTk4ZDgyMTliYzU2ZjVlOGE3MjM1YjMiXV0sImNvbnRlbnQiOiIiLCJzaWciOiIzYWI0NGQwZTIyMjhiYmQ0ZDIzNDFjM2ZhNzQwOTZjZmY2ZjU1Y2ZkYTk5YTVkYWRjY2Y0NWM2NjQ2MzdlMjExNTFiMmY5ZGQwMDQwZjFhMjRlOWY4Njg2NzM4YjE2YmY4MTM0YmRiZTQxYTIxOGM5MTFmN2JiMzFlNTk1NzhkMSJ9Cg=="
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user