Files
damus/damus/Models/Purple/DamusPurple.swift
Daniel D’Aquino 5ca5420ce2 Damus Purple: Add npub authentication for account management API calls
Testing
---------

PASS

Device: iPhone 15 Pro simulator
iOS: 17.2
Damus: This commit
damus-api: 626fb9665d8d6c576dd635d5224869cd9b69d190
Server: Ubuntu 22.04 (VM)
Setup:
1. On the server, delete the `mdb` database files to start from scratch
2. In iOS, reinstall the app if necessary to make sure there are no in-app purchases
3. Enable subscriptions support via developer settings with localhost test mode and restart app
4. Start server with mock parameters (Run `npm run dev`)

Steps:
1. Open top bar and click on "Purple"
2. Purple screen should appear and show both benefits and the purchase options. PASS
3. Click on "monthly". An Apple screen to confirm purchase should appear. PASS
4. Welcome screen with animation should appear. PASS
5. Click continue and restart app (Due to known issue tracked at damus-io#1814)
6. Post something
7. Gold star should appear beside your name
8. Look at the server logs. There should be some requests to create the account (POST), to send the receipt (POST), and to get account status
9. Go to purple view. There should be some information about the subscription, as well as a "manage" button. PASS
10. Click on "manage" button. An iOS sheet should appear allow the user to unsubscribe or manage their subscription to Damus Purple.

Closes: https://github.com/damus-io/damus/issues/1809
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
2023-12-24 09:30:26 -08:00

161 lines
5.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// DamusPurple.swift
// damus
//
// Created by Daniel DAquino on 2023-12-08.
//
import Foundation
class DamusPurple: StoreObserverDelegate {
let environment: ServerEnvironment
let keypair: Keypair
var starred_profiles_cache: [Pubkey: Bool]
init(environment: ServerEnvironment, keypair: Keypair) {
self.environment = environment
self.keypair = keypair
self.starred_profiles_cache = [:]
}
// MARK: Functions
func is_profile_subscribed_to_purple(pubkey: Pubkey) async -> Bool? {
if let cached_result = self.starred_profiles_cache[pubkey] {
return cached_result
}
guard let data = await self.get_account_data(pubkey: pubkey) else { return nil }
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let active = json["active"] as? Bool {
self.starred_profiles_cache[pubkey] = active
return active
}
return nil
}
func account_exists(pubkey: Pubkey) async -> Bool? {
guard let account_data = await self.get_account_data(pubkey: pubkey) else { return nil }
if let account_info = try? JSONDecoder().decode(AccountInfo.self, from: account_data) {
return account_info.pubkey == pubkey.hex()
}
return false
}
func get_account_data(pubkey: Pubkey) async -> Data? {
let url = environment.get_base_url().appendingPathComponent("accounts/\(pubkey.hex())")
var request = URLRequest(url: url)
request.httpMethod = "GET"
do {
let (data, _) = try await URLSession.shared.data(for: request)
return data
} catch {
print("Failed to fetch data: \(error)")
}
return nil
}
func create_account(pubkey: Pubkey) async throws {
let url = environment.get_base_url().appendingPathComponent("accounts")
let payload: [String: String] = [
"pubkey": pubkey.hex()
]
let encoded_payload = try JSONEncoder().encode(payload)
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
}
func create_account_if_not_existing(pubkey: Pubkey) async throws {
guard await !(self.account_exists(pubkey: pubkey) ?? false) else { return }
try await self.create_account(pubkey: pubkey)
}
func send_receipt() async {
// Get the receipt if it's available.
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
try? await create_account_if_not_existing(pubkey: keypair.pubkey)
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let url = environment.get_base_url().appendingPathComponent("accounts/\(keypair.pubkey.hex())/app-store-receipt")
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 {
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 {
enum ServerEnvironment {
case local_test
case production
func get_base_url() -> URL {
switch self {
case .local_test:
Constants.PURPLE_API_TEST_BASE_URL
case .production:
Constants.PURPLE_API_PRODUCTION_BASE_URL
}
}
}
}