diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 302876650..58c239c3b 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -472,6 +472,7 @@ D79C4C1B2AFEB061003A41B4 /* DamusNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D79C4C142AFEB061003A41B4 /* DamusNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; D7A343EE2AD0D77C00CED48B /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */; }; D7A343F02AD0D77C00CED48B /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343EF2AD0D77C00CED48B /* SnapshotTesting */; }; + D7C6787E2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */; }; D7CCFC072B05833200323D86 /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; }; D7CCFC082B05834500323D86 /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; }; D7CCFC0B2B0585EA00323D86 /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; settings = {COMPILER_FLAGS = "-w"; }; }; @@ -1273,6 +1274,7 @@ D79C4C162AFEB061003A41B4 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; D79C4C182AFEB061003A41B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D79C4C1C2AFEB061003A41B4 /* DamusNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DamusNotificationService.entitlements; sourceTree = ""; }; + D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP98AuthenticatedRequest.swift; sourceTree = ""; }; D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = ""; }; D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURL.swift; sourceTree = ""; }; E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = ""; }; @@ -1896,6 +1898,7 @@ 4C2B7BF12A71B6540049DEE7 /* Id.swift */, D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */, D798D22B2B086C7400234419 /* NostrEvent+.swift */, + D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */, ); path = Nostr; sourceTree = ""; @@ -3028,6 +3031,7 @@ 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */, 4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */, 4CA9276C2A2910D10098A105 /* ReplyPart.swift in Sources */, + D7C6787E2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift in Sources */, 4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */, 4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */, 4C42812C298C848200DBF26F /* TranslateView.swift in Sources */, diff --git a/damus/Models/Purple/DamusPurple.swift b/damus/Models/Purple/DamusPurple.swift index 9b6692a5b..bfe76aee4 100644 --- a/damus/Models/Purple/DamusPurple.swift +++ b/damus/Models/Purple/DamusPurple.swift @@ -38,9 +38,8 @@ class DamusPurple: StoreObserverDelegate { func account_exists(pubkey: Pubkey) async -> Bool? { guard let account_data = await self.get_account_data(pubkey: pubkey) else { return nil } - if let json = try? JSONSerialization.jsonObject(with: account_data, options: []) as? [String: Any], - let id = json["id"] as? String { - return id == pubkey.hex() + if let account_info = try? JSONDecoder().decode(AccountInfo.self, from: account_data) { + return account_info.pubkey == pubkey.hex() } return false @@ -63,19 +62,27 @@ class DamusPurple: StoreObserverDelegate { func create_account(pubkey: Pubkey) async throws { let url = environment.get_base_url().appendingPathComponent("accounts") - var request = URLRequest(url: url) - request.httpMethod = "POST" - let payload: [String: String] = [ "pubkey": pubkey.hex() ] + let encoded_payload = try JSONEncoder().encode(payload) - request.httpBody = try JSONEncoder().encode(payload) - do { - let (_, _) = try await URLSession.shared.data(for: request) - return - } catch { - print("Failed to fetch data: \(error)") + Log.info("Creating account with Damus Purple server", for: .damus_purple) + + let (data, response) = try await make_nip98_authenticated_request( + method: .post, + url: url, + payload: encoded_payload, + auth_keypair: self.keypair + ) + + if let httpResponse = response as? HTTPURLResponse { + switch httpResponse.statusCode { + case 200: + Log.info("Created an account with Damus Purple server", for: .damus_purple) + default: + Log.error("Error in creating account with Damus Purple. HTTP status code: %d", for: .damus_purple, httpResponse.statusCode) + } } return @@ -95,26 +102,45 @@ class DamusPurple: StoreObserverDelegate { do { let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) - print(receiptData) - let url = environment.get_base_url().appendingPathComponent("accounts/\(keypair.pubkey.hex())/app-store-receipt") - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.httpBody = receiptData - do { - let (_, _) = try await URLSession.shared.data(for: request) - print("Sent receipt") - } catch { - print("Failed to fetch data: \(error)") + Log.info("Sending in-app purchase receipt to Damus Purple server", for: .damus_purple) + + let (data, response) = try await make_nip98_authenticated_request( + method: .post, + url: url, + payload: receiptData, + auth_keypair: self.keypair + ) + + if let httpResponse = response as? HTTPURLResponse { + switch httpResponse.statusCode { + case 200: + Log.info("Sent in-app purchase receipt to Damus Purple server successfully", for: .damus_purple) + default: + Log.error("Error in sending in-app purchase receipt to Damus Purple. HTTP status code: %d", for: .damus_purple, httpResponse.statusCode) + } } } - catch { print("Couldn't read receipt data with error: " + error.localizedDescription) } + catch { + Log.error("Couldn't read receipt data with error: %s", for: .damus_purple, error.localizedDescription) + } } } } +// MARK: API types + +extension DamusPurple { + fileprivate struct AccountInfo: Codable { + let pubkey: String + let created_at: UInt64 + let expiry: UInt64? + let active: Bool + } +} + // MARK: Helper structures extension DamusPurple { diff --git a/damus/Nostr/NIP98AuthenticatedRequest.swift b/damus/Nostr/NIP98AuthenticatedRequest.swift new file mode 100644 index 000000000..2d613813c --- /dev/null +++ b/damus/Nostr/NIP98AuthenticatedRequest.swift @@ -0,0 +1,41 @@ +// +// NIP98AuthenticatedRequest.swift +// damus +// +// Created by Daniel D’Aquino on 2023-12-15. +// + +import Foundation + +enum HTTPMethod: String { + case get = "GET" + case post = "POST" + case put = "PUT" + case delete = "DELETE" +} + +func make_nip98_authenticated_request(method: HTTPMethod, url: URL, payload: Data, auth_keypair: Keypair) async throws -> (data: Data, response: URLResponse) { + var request = URLRequest(url: url) + request.httpMethod = method.rawValue + request.httpBody = payload + + let payload_hash = sha256(payload) + let payload_hash_hex = payload_hash.map({ String(format: "%02hhx", $0) }).joined() + + let auth_note = NdbNote( + content: "", + keypair: auth_keypair, + kind: 27235, + tags: [ + ["u", url.absoluteString], + ["method", method.rawValue], + ["payload", payload_hash_hex] + ], + createdAt: UInt32(Date().timeIntervalSince1970) + ) + let auth_note_json_data: Data = try JSONEncoder().encode(auth_note) + let auth_note_base64: String = base64_encode(auth_note_json_data.bytes) + + request.setValue("Nostr " + auth_note_base64, forHTTPHeaderField: "Authorization") + return try await URLSession.shared.data(for: request) +} diff --git a/damus/Util/Log.swift b/damus/Util/Log.swift index 84b110d99..a879037f8 100644 --- a/damus/Util/Log.swift +++ b/damus/Util/Log.swift @@ -14,6 +14,7 @@ enum LogCategory: String { case render case storage case push_notifications + case damus_purple } /// Damus structured logger