appleapi

package module
v1.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 21, 2025 License: MIT Imports: 11 Imported by: 1

README

appleapi-core

appleapi-core is a lightweight Go library providing the foundational components for authenticating with Apple's JWT-secured APIs (such as APNs or DeviceCheck).
It offers a reusable HTTP client that automatically handles token generation, caching, and signing.

Design Philosophy

This library is designed as a core building block for interacting with Apple’s JWT-based services.
It provides the fundamental mechanisms for authentication and HTTP communication, intended to be embedded or wrapped by higher-level, service-specific clients (for example, a client for the App Store Connect API or the Apple Music API).

These wrapper clients are responsible for implementing specific API endpoints and business logic, while appleapi-core manages the authentication token lifecycle and underlying HTTP transport.
The Host field in the Client serves as a convenience mechanism for higher-level API clients, allowing them to define a consistent base endpoint for Apple service requests.

This separation keeps the core library clean, reusable, and focused on authentication and transport concerns.

Features

  • Automatic JWT generation and caching with TokenProvider.
  • Configurable HTTP/2 client built on Go’s standard http.Transport.
  • Supports .p8 private keys (PKCS#8) for token signing.
  • Minimal external dependencies.
  • Clean, modular design separating token generation from the HTTP client.

Installation

go get github.com/takimoto3/appleapi-core

Usage

The following example demonstrates how to create a client and make a request to an Apple API.

package main

import (
	"fmt"
	"log"
	"log/slog"
	"net/http"
	"os"

	"github.com/takimoto3/appleapi-core"
	"github.com/takimoto3/appleapi-core/token"
)

func main() {
	// Your Apple Developer credentials
	keyID := "YOUR_KEY_ID"
	teamID := "YOUR_TEAM_ID"
	pkcs8FilePath := "/path/to/your/AuthKey.p8"

	// 1. Load your private key
	privateKey, err := token.LoadPKCS8File(pkcs8FilePath)
	if err != nil {
		log.Fatalf("failed to load private key: %v", err)
	}

	// 2. Create a TokenProvider
	// It is recommended to pass a logger for better visibility.
	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
	tp := token.NewProvider(keyID, teamID, privateKey, token.WithLogger(logger))

	// 3. Create the appleapi Client
	// The host parameter is optional and can be left empty if not needed.
	client, err := appleapi.NewClient(appleapi.DefaultHTTPClientInitializer(), "", tp, appleapi.WithLogger(logger))
	if err != nil {
		log.Fatalf("failed to create client: %v", err)
	}
	defer client.CloseIdleConnections()

	// 4. Create and execute a request
	// The client automatically adds the "Authorization: Bearer <token>" header.
	req, err := http.NewRequest("GET", "https://api.some-apple-service.com/v1/some/endpoint", nil)
	if err != nil {
		log.Fatalf("failed to create request: %v", err)
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	// 5. Check the response
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("unexpected status code: %d", resp.StatusCode)
	}

	fmt.Printf("Request successful! Status: %s\n", resp.Status)
	// ... process response body
}

Advanced Usage: Client Tracing

This feature leverages Go’s net/http/httptrace package to provide detailed insight into the client’s HTTP lifecycle (DNS resolution, TLS handshake, connection reuse, and more).

You can enable detailed request lifecycle logging by using the WithClientTrace option.
The DefaultClientTrace helper provides an easy way to enable default tracing with your logger.

// (Inside main function, after logger is created)

// 3. Create the appleapi Client with tracing
// DefaultClientTrace provides a pre-configured trace that logs to the client's logger.
client, err := appleapi.NewClient(appleapi.DefaultHTTPClientInitializer(), "", tp,
    appleapi.WithLogger(logger),
    appleapi.WithClientTrace(func(l *slog.Logger) *httptrace.ClientTrace {
        // The log level can be slog.LevelDebug, slog.LevelInfo, etc.
        return appleapi.DefaultClientTrace(l, slog.LevelDebug)
    }),
)
if err != nil {
    log.Fatalf("failed to create client: %v", err)
}
defer client.CloseIdleConnections()

// Now, when you make a request with this client,
// detailed trace logs will be output.
resp, err := client.Do(req)
if err != nil {
    log.Fatalf("request failed: %v", err)
}

Example trace output:

level=DEBUG msg="DNS Start" host="api.some-apple-service.com"
level=DEBUG msg="DNS Done" addrs="[203.0.113.42]"
level=DEBUG msg="Connect Start" network="tcp" addr="203.0.113.42:443"
level=DEBUG msg="Connect Done" network="tcp" addr="203.0.113.42:443" err="<nil>"
level=DEBUG msg="Got First Response Byte"

License

This project is licensed under the MIT License.
See the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultClientTrace

func DefaultClientTrace(logger *slog.Logger, level slog.Level) *httptrace.ClientTrace

DefaultClientTrace returns a ClientTrace with all callbacks implemented using the provided Logger. Unused callbacks can be set to nil by the caller.

Types

type Client

type Client struct {
	Host          string                 // Base URL for Apple API
	Development   bool                   // Enable development mode
	HTTPClient    *http.Client           // Underlying HTTP client
	TokenProvider token.Provider         // Responsible for providing tokens
	Logger        *slog.Logger           // Structured logger
	Trace         *httptrace.ClientTrace // HTTP request trace hooks
}

Client represents an HTTP client with Apple authentication support.

func NewClient

func NewClient(initializer HTTPClientInitializer, host string, tp token.Provider, opts ...Option) (*Client, error)

NewClient creates a new Client with a custom HTTP initializer and options.

func (*Client) CloseIdleConnections

func (c *Client) CloseIdleConnections()

CloseIdleConnections closes idle connections in the HTTP client.

func (*Client) Do

func (c *Client) Do(req *http.Request) (*http.Response, error)

Do sends an HTTP request with a Bearer token and optional HTTP trace.

type HTTPClientInitializer added in v0.1.1

type HTTPClientInitializer func() (*http.Client, error)

HTTPClientInitializer is a function that returns a configured *http.Client.

func ConfigureHTTPClientInitializer added in v0.1.1

func ConfigureHTTPClientInitializer(cfg *HTTPConfig) HTTPClientInitializer

ConfigureHTTPClientInitializer returns an HTTP client configured based on the given HTTPConfig.

func DefaultHTTPClientInitializer added in v0.1.1

func DefaultHTTPClientInitializer() HTTPClientInitializer

DefaultHTTPClientInitializer returns a default HTTP client with TLS1.3 and HTTP/2 enabled.

type HTTPConfig added in v0.1.1

type HTTPConfig struct {
	HTTPTimeout         time.Duration // Maximum duration for a complete HTTP request
	ReadIdleTimeout     time.Duration // Idle period before sending an HTTP/2 PING frame
	KeepAlive           time.Duration // Interval for TCP keep-alive probes
	DialTimeout         time.Duration // Timeout for establishing new TCP connections
	MaxConnsPerHost     int           // Maximum total connections per host (idle + active)
	IdleConnTimeout     time.Duration // Max time an idle connection is kept alive
	MaxIdleConnsPerHost int           // Maximum idle connections per host
	TLSConfig           *tls.Config   // TLS settings for HTTPS connections
}

HTTPConfig defines transport and timeout settings used by clients.

func DefaultConfig added in v1.1.0

func DefaultConfig() HTTPConfig

GetDefaultConfigValue returns a copy of the default configuration. The returned configuration is independent, and modifications to it will not affect the package's internal state.

type Option

type Option struct {
	// contains filtered or unexported fields
}

Option defines a configurable option for Client, including its execution order.

func WithClientTimeout added in v0.1.1

func WithClientTimeout(timeout time.Duration) Option

WithClientTimeout sets a custom HTTP client timeout.

func WithClientTrace

func WithClientTrace(f func(*slog.Logger) *httptrace.ClientTrace) Option

WithClientTrace sets a custom HTTP trace function.

func WithDevelopment

func WithDevelopment() Option

WithDevelopment enables development mode.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets a custom structured logger.

func WithTransport

func WithTransport(tr http.RoundTripper) Option

WithTransport sets a custom HTTP transport.

type OptionOrder added in v0.1.1

type OptionOrder int

OptionOrder defines the execution order for Client options. Options are applied in ascending order of these constants.

const (
	Development OptionOrder = iota + 1
	Logger
	Transport
	ClientTimeout
	ClientTrace // Depends on Logger being already set
)

type UnixTime added in v1.0.0

type UnixTime time.Time

UnixTime represents a time in milliseconds since Unix epoch (UTC).

func (UnixTime) MarshalJSON added in v1.0.0

func (t UnixTime) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface for UnixTime. It marshals the time into a Unix timestamp in milliseconds.

func (UnixTime) String added in v1.0.0

func (t UnixTime) String() string

String returns the UnixTime as a formatted string (RFC3339Nano).

func (UnixTime) Time added in v1.0.0

func (t UnixTime) Time() time.Time

Time returns the UnixTime as a standard time.Time.

func (*UnixTime) UnmarshalJSON added in v1.0.0

func (t *UnixTime) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface for UnixTime. It unmarshals a Unix timestamp in milliseconds into a UnixTime.

Directories

Path Synopsis