Files
linkding-ios/Marks/Services/MarksAuth.swift
Krishna Kumar 96ea5fe6f6
All checks were successful
CI / build-and-deploy (push) Successful in 22s
feat(ai): on-device RAG over bookmarks + Spotlight indexing + unified logging
- SpotlightIndexer: actually push BookmarkEntity into the Spotlight index via
  indexAppEntities (IndexedEntity conformance alone indexes nothing). Wired into
  every sync/mutation point. This is what lets Apple Intelligence answer
  free-form Siri questions grounded in the user's bookmarks.
- On-device RAG: BookmarkAssistant (LanguageModelSession) + BookmarkSearchTool
  + SpotlightBookmarkSearch (CSSearchQuery retrieval), surfaced via AskView with
  lightweight Markdown rendering of answers.
- Log: os.Logger facility (com.magicive.marks) replacing ad-hoc print(); shared
  with ShareExtension. ClaudeService now logs server error bodies on non-2xx.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:55:32 -05:00

53 lines
2.2 KiB
Swift

import Foundation
private let backendBaseURL = "https://chatai-realtime-proxy-production.up.railway.app"
private let deviceIdKey = "marksDeviceId"
private let tokenKey = "marksJWT"
private let tokenExpiryKey = "marksJWTExpiry"
enum MarksAuth {
static var baseURL: String { backendBaseURL }
/// Returns a valid JWT, registering if missing or within 1 hour of expiry.
static func validToken() async throws -> String {
if let token = cached(), !token.isEmpty { return token }
return try await register()
}
private static func deviceId() -> String {
if let existing = UserDefaults.standard.string(forKey: deviceIdKey) { return existing }
let new = UUID().uuidString
UserDefaults.standard.set(new, forKey: deviceIdKey)
return new
}
private static func cached() -> String? {
guard
let token = UserDefaults.standard.string(forKey: tokenKey),
let expiry = UserDefaults.standard.object(forKey: tokenExpiryKey) as? Date,
expiry > Date().addingTimeInterval(3600)
else { return nil }
return token
}
private static func register() async throws -> String {
guard let url = URL(string: "\(backendBaseURL)/api/auth/register") else {
throw URLError(.badURL)
}
let id = deviceId()
var req = URLRequest(url: url)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try JSONSerialization.data(withJSONObject: ["device_id": id])
let (data, response) = try await URLSession.shared.data(for: req)
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
guard (200...299).contains(status) else { throw APIError.badStatus(status) }
struct Resp: Decodable { let access_token: String; let expires_in: Int }
let resp = try JSONDecoder().decode(Resp.self, from: data)
UserDefaults.standard.set(resp.access_token, forKey: tokenKey)
UserDefaults.standard.set(Date().addingTimeInterval(TimeInterval(resp.expires_in)), forKey: tokenExpiryKey)
Log.auth.info("Registered device \(id.prefix(8), privacy: .public)")
return resp.access_token
}
}