Skip to content

Swift package providing foundational types and utilities for AT Protocol (atproto) iOS applications

License

Notifications You must be signed in to change notification settings

tijs/ATProtoFoundation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ATProtoFoundation

Building blocks for AT Protocol iOS apps. Parse rich text with facets, handle OAuth authentication via a backend, and work with timeline data from Bluesky and other AT Protocol services.

When to use this package

Good fit if you:

  • Are building an iOS/macOS app that connects to Bluesky or AT Protocol services
  • Need to parse and display rich text posts with links, mentions, and hashtags
  • Want BFF (Backend-For-Frontend) OAuth pattern with HttpOnly cookies
  • Prefer a lightweight foundation over a full-featured SDK

Less ideal if you:

  • Need a complete AT Protocol client with all XRPC methods
  • Want to run your own PDS or build server-side tooling
  • Need cross-platform support beyond Apple platforms

Requirements

  • Swift 6.0+
  • iOS 18.0+ / macOS 14.0+

Installation

Swift Package Manager

Add ATProtoFoundation to your Package.swift:

dependencies: [
    .package(url: "https://github.com/tijs/ATProtoFoundation.git", from: "1.1.0")
]

Or add it via Xcode: File → Add Package Dependencies → Enter the repository URL.

Features

Models

BlueskyPostRecord & Facets

Types for working with Bluesky post records and rich text facets:

import ATProtoFoundation

// Create a record with rich text
let facet = ATProtoFacet(
    index: 10...28,
    feature: .link("https://example.com")
)
let record = BlueskyPostRecord(
    text: "Check out https://example.com for more",
    facets: [facet]
)

// Get markdown-formatted text with clickable links
let formatted = record.formattedText

Supported facet features:

  • .link(url) - External URLs
  • .mention(did) - AT Protocol user mentions
  • .hashtag(tag) - Hashtags with search URLs

Timeline Models

Types for parsing timeline API responses:

// Decode timeline data from API
let record = try JSONDecoder().decode(TimelineRecord.self, from: jsonData)

// Convert to rich BlueskyPostRecord with facets
let postRecord = BlueskyPostRecord(from: record)

Community Lexicon Models

Models for community lexicons - standardized schemas for common data types:

Bookmarks

let bookmark = Bookmark(
    subject: "https://example.com/article",
    tags: ["reading-list", "tech"]
)

Calendar Events

let event = CalendarEvent(
    name: "Swift Meetup",
    description: "Monthly developer meetup",
    startsAt: Date(),
    mode: .hybrid,
    status: .scheduled,
    locations: [.geo(GeoCoordinates(latitude: 37.7749, longitude: -122.4194))]
)

let rsvp = CalendarRSVP(subject: eventRef, status: .going)

Interactions

let like = InteractionLike(subject: postRef)

Locations

let geo = GeoCoordinates(latitude: 37.7749, longitude: -122.4194, name: "San Francisco")
let address = CommunityAddress(country: "US", locality: "San Francisco", street: "123 Main St")
let fsq = FoursquareLocation(fsqPlaceId: "4b8c3d87f964a520f7c532e3")
let h3 = H3Location(value: "8928308280fffff")

Payments

let wallet = WebMonetizationWallet(
    address: "https://ilp.uphold.com/abc123",
    note: "Support my work"
)

Lexicon Constants

Extensible constants for AT Protocol lexicon identifiers:

// Bluesky lexicons
BlueskyLexicon.feedPost        // "app.bsky.feed.post"
BlueskyLexicon.richTextLink    // "app.bsky.richtext.facet#link"
BlueskyLexicon.richTextMention // "app.bsky.richtext.facet#mention"
BlueskyLexicon.richTextTag     // "app.bsky.richtext.facet#tag"

// Community lexicons
CommunityLexicon.bookmark           // "community.lexicon.bookmarks.bookmark"
CommunityLexicon.calendarEvent      // "community.lexicon.calendar.event"
CommunityLexicon.calendarRSVP       // "community.lexicon.calendar.rsvp"
CommunityLexicon.interactionLike    // "community.lexicon.interaction.like"
CommunityLexicon.locationGeo        // "community.lexicon.location.geo"
CommunityLexicon.locationAddress    // "community.lexicon.location.address"
CommunityLexicon.locationFoursquare // "community.lexicon.location.fsq"
CommunityLexicon.locationH3         // "community.lexicon.location.hthree"
CommunityLexicon.webMonetization    // "community.lexicon.payments.webMonetization"

Rich Text Processing

Detect and create facets from text:

let processor = RichTextProcessor()
let facets = processor.detectFacets(in: "Check out https://example.com and @alice.bsky.social #swift")
// Returns RichTextFacet array with links, mentions, and hashtags

Authentication

BFF OAuth

Backend-For-Frontend (BFF) pattern OAuth implementation using HttpOnly cookies. Mobile apps can't safely store private keys, so the recommended approach is to have a backend server manage OAuth sessions and use session cookies to authenticate the mobile client.

For the server-side implementation, see @tijs/atproto-oauth and its mobile authentication guide:

let config = OAuthConfiguration(
    baseURL: URL(string: "https://your-backend.app")!,
    userAgent: "YourApp/1.0 (iOS)",
    sessionCookieName: "sid",
    cookieDomain: "your-backend.app",
    callbackURLScheme: "your-app",
    sessionDuration: 86400 * 7,
    refreshThreshold: 3600,
    maxRetryAttempts: 3,
    maxRetryDelay: 8.0
)

let coordinator = MobileOAuthCoordinator(
    storage: KeychainCredentialsStorage(),
    config: config
)

// Start OAuth flow
let authURL = try await coordinator.startOAuthFlow()

// Handle callback
try await coordinator.completeOAuthFlow(callbackURL: url)

// Refresh session
let credentials = try await coordinator.refreshSession()

Credentials Storage

Protocol-based storage with multiple implementations:

// Keychain storage (recommended for production)
let storage = KeychainCredentialsStorage()

// In-memory storage (for testing)
let storage = InMemoryCredentialsStorage()

// Custom storage - implement CredentialsStorageProtocol

Configuration

Centralized OAuth configuration:

let config = OAuthConfiguration(
    baseURL: URL(string: "https://your-backend.app")!,
    userAgent: "YourApp/1.0 (iOS)",
    sessionCookieName: "sid",
    cookieDomain: "your-backend.app",
    callbackURLScheme: "your-app",
    sessionDuration: 86400,
    refreshThreshold: 3600,
    maxRetryAttempts: 3,
    maxRetryDelay: 8.0
)

Networking

BFFAPIClient

HTTP client with automatic session management:

let client = BFFAPIClient(
    credentialsStorage: storage,
    config: config  // Use same OAuthConfiguration as coordinator
)

// GET request
let data = try await client.get(endpoint: "/api/endpoint")

// POST request with JSON
let response = try await client.post(
    endpoint: "/api/endpoint",
    body: someEncodable
)

Utilities

Date Parsing

Flexible ISO8601 date parsing for API responses:

// Handles both formats:
// - With fractional seconds: "2025-01-15T12:30:45.123Z"
// - Without fractional seconds: "2025-01-15T12:30:45Z"
let date = ISO8601DateFormatter.flexibleDate(from: dateString)

Logging

Protocol-based logging with debug and silent implementations:

// Debug logger (prints to console in debug builds)
let logger = DebugLogger()

// Silent logger (no output)
let logger = SilentLogger()

// Custom logger - implement Logger protocol

Architecture

ATProtoFoundation follows these principles:

  • Protocol-first: All major components use protocols for testability
  • Dependency injection: Services accept dependencies through initializers
  • Sendable by default: Types are designed for Swift 6 strict concurrency
  • No external dependencies: Uses only Foundation and Security frameworks

Testing

Run tests:

swift test

The package includes comprehensive tests for all components using Swift Testing framework.

License

MIT License - see LICENSE file for details.

About

Swift package providing foundational types and utilities for AT Protocol (atproto) iOS applications

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages