Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify signing and notifications APIs #17

Merged
merged 7 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .spi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
version: 1
builder:
configs:
- documentation_targets: [PassKit, Passes, Orders]
swift_version: 6.0
- documentation_targets: [PassKit, Passes, Orders]
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let package = Package(
.package(url: "https://github.com/vapor/vapor.git", from: "4.106.1"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.12.0"),
.package(url: "https://github.com/vapor/apns.git", from: "4.2.0"),
.package(url: "https://github.com/vapor-community/Zip.git", from: "2.2.3"),
.package(url: "https://github.com/vapor-community/Zip.git", from: "2.2.4"),
.package(url: "https://github.com/apple/swift-certificates.git", from: "1.6.1"),
// used in tests
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.8.0"),
Expand Down
3 changes: 1 addition & 2 deletions Sources/Orders/Orders.docc/Extensions/OrdersService.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

### Essentials

- ``generateOrderContent(for:on:)``
- ``build(order:on:)``
- ``register(migrations:)``

### Push Notifications

- ``sendPushNotifications(for:on:)``
- ``sendPushNotificationsForOrder(id:of:on:)``
14 changes: 8 additions & 6 deletions Sources/Orders/Orders.docc/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@ final class OrderDelegate: OrdersDelegate {

Next, initialize the ``OrdersService`` inside the `configure.swift` file.
This will implement all of the routes that Apple Wallet expects to exist on your server.
In the `signingFilesDirectory` you specify there must be the `WWDR.pem`, `certificate.pem` and `key.pem` files.
If they are named like that you're good to go, otherwise you have to specify the custom name.

> Tip: Obtaining the three certificates files could be a bit tricky. You could get some guidance from [this guide](https://github.com/alexandercerutti/passkit-generator/wiki/Generating-Certificates) and [this video](https://www.youtube.com/watch?v=rJZdPoXHtzI). Those guides are for Wallet passes, but the process is similar for Wallet orders.

Expand All @@ -164,7 +162,9 @@ public func configure(_ app: Application) async throws {
let ordersService = try OrdersService(
app: app,
delegate: orderDelegate,
signingFilesDirectory: "Certificates/Orders/"
pemWWDRCertificate: Environment.get("PEM_WWDR_CERTIFICATE")!,
pemCertificate: Environment.get("PEM_CERTIFICATE")!,
pemPrivateKey: Environment.get("PEM_PRIVATE_KEY")!
)
}
```
Expand Down Expand Up @@ -203,7 +203,9 @@ public func configure(_ app: Application) async throws {
>(
app: app,
delegate: orderDelegate,
signingFilesDirectory: "Certificates/Orders/"
pemWWDRCertificate: Environment.get("PEM_WWDR_CERTIFICATE")!,
pemCertificate: Environment.get("PEM_CERTIFICATE")!,
pemPrivateKey: Environment.get("PEM_PRIVATE_KEY")!
)
}
```
Expand Down Expand Up @@ -284,7 +286,7 @@ struct OrdersController: RouteCollection {

> Note: You'll have to register the `OrdersController` in the `configure.swift` file, in order to pass it the ``OrdersService`` object.

Then use the object inside your route handlers to generate the order bundle with the ``OrdersService/generateOrderContent(for:on:)`` method and distribute it with the "`application/vnd.apple.order`" MIME type.
Then use the object inside your route handlers to generate the order bundle with the ``OrdersService/build(order:on:)`` method and distribute it with the "`application/vnd.apple.order`" MIME type.

```swift
fileprivate func orderHandler(_ req: Request) async throws -> Response {
Expand All @@ -297,7 +299,7 @@ fileprivate func orderHandler(_ req: Request) async throws -> Response {
throw Abort(.notFound)
}

let bundle = try await ordersService.generateOrderContent(for: orderData.order, on: req.db)
let bundle = try await ordersService.build(order: orderData.order, on: req.db)
let body = Response.Body(data: bundle)
var headers = HTTPHeaders()
headers.add(name: .contentType, value: "application/vnd.apple.order")
Expand Down
15 changes: 0 additions & 15 deletions Sources/Orders/OrdersDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,6 @@ public protocol OrdersDelegate: AnyObject, Sendable {
/// - Returns: A URL path which points to the template data for the order.
func template<O: OrderModel>(for order: O, db: any Database) async throws -> String

/// Generates the SSL `signature` file.
///
/// If you need to implement custom S/Mime signing you can use this
/// method to do so. You must generate a detached DER signature of the `manifest.json` file.
///
/// - Parameter root: The location of the `manifest.json` and where to write the `signature` to.
/// - Returns: Return `true` if you generated a custom `signature`, otherwise `false`.
func generateSignatureFile(in root: URL) -> Bool

/// Encode the order into JSON.
///
/// This method should generate the entire order JSON. You are provided with
Expand All @@ -51,9 +42,3 @@ public protocol OrdersDelegate: AnyObject, Sendable {
order: O, db: any Database, encoder: JSONEncoder
) async throws -> Data
}

extension OrdersDelegate {
public func generateSignatureFile(in root: URL) -> Bool {
return false
}
}
32 changes: 10 additions & 22 deletions Sources/Orders/OrdersError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ public struct OrdersError: Error, Sendable {
/// The type of the errors that can be thrown by Apple Wallet orders.
public struct ErrorType: Sendable, Hashable, CustomStringConvertible {
enum Base: String, Sendable {
case templateNotDirectory
case pemCertificateMissing
case pemPrivateKeyMissing
case opensslBinaryMissing
case noSourceFiles
case noOpenSSLExecutable
}

let base: Base
Expand All @@ -22,14 +20,10 @@ public struct OrdersError: Error, Sendable {
self.base = base
}

/// The template path is not a directory.
public static let templateNotDirectory = Self(.templateNotDirectory)
/// The `pemCertificate` file is missing.
public static let pemCertificateMissing = Self(.pemCertificateMissing)
/// The `pemPrivateKey` file is missing.
public static let pemPrivateKeyMissing = Self(.pemPrivateKeyMissing)
/// The path to the `openssl` binary is incorrect.
public static let opensslBinaryMissing = Self(.opensslBinaryMissing)
/// The path for the source files is not a directory.
public static let noSourceFiles = Self(.noSourceFiles)
/// The `openssl` executable is missing.
public static let noOpenSSLExecutable = Self(.noOpenSSLExecutable)

/// A textual representation of this error.
public var description: String {
Expand All @@ -54,17 +48,11 @@ public struct OrdersError: Error, Sendable {
self.backing = .init(errorType: errorType)
}

/// The template path is not a directory.
public static let templateNotDirectory = Self(errorType: .templateNotDirectory)
/// The path for the source files is not a directory.
public static let noSourceFiles = Self(errorType: .noSourceFiles)

/// The `pemCertificate` file is missing.
public static let pemCertificateMissing = Self(errorType: .pemCertificateMissing)

/// The `pemPrivateKey` file is missing.
public static let pemPrivateKeyMissing = Self(errorType: .pemPrivateKeyMissing)

/// The path to the `openssl` binary is incorrect.
public static let opensslBinaryMissing = Self(errorType: .opensslBinaryMissing)
/// The `openssl` executable is missing.
public static let noOpenSSLExecutable = Self(errorType: .noOpenSSLExecutable)
}

extension OrdersError: CustomStringConvertible {
Expand Down
48 changes: 18 additions & 30 deletions Sources/Orders/OrdersService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,34 @@ public final class OrdersService: Sendable {
/// - Parameters:
/// - app: The `Vapor.Application` to use in route handlers and APNs.
/// - delegate: The ``OrdersDelegate`` to use for order generation.
/// - signingFilesDirectory: The path of the directory where the signing files (`wwdrCertificate`, `pemCertificate`, `pemPrivateKey`) are located.
/// - wwdrCertificate: The name of Apple's WWDR.pem certificate as contained in `signingFilesDirectory` path. Defaults to `WWDR.pem`.
/// - pemCertificate: The name of the PEM Certificate for signing the pass as contained in `signingFilesDirectory` path. Defaults to `certificate.pem`.
/// - pemPrivateKey: The name of the PEM Certificate's private key for signing the pass as contained in `signingFilesDirectory` path. Defaults to `key.pem`.
/// - pemPrivateKeyPassword: The password to the private key file. If the key is not encrypted it must be `nil`. Defaults to `nil`.
/// - sslBinary: The location of the `openssl` command as a file path.
/// - pushRoutesMiddleware: The `Middleware` to use for push notification routes. If `nil`, push routes will not be registered.
/// - logger: The `Logger` to use.
/// - pemWWDRCertificate: Apple's WWDR.pem certificate in PEM format.
/// - pemCertificate: The PEM Certificate for signing orders.
/// - pemPrivateKey: The PEM Certificate's private key for signing orders.
/// - pemPrivateKeyPassword: The password to the private key. If the key is not encrypted it must be `nil`. Defaults to `nil`.
/// - openSSLPath: The location of the `openssl` command as a file path.
public init(
app: Application,
delegate: any OrdersDelegate,
signingFilesDirectory: String,
wwdrCertificate: String = "WWDR.pem",
pemCertificate: String = "certificate.pem",
pemPrivateKey: String = "key.pem",
pemPrivateKeyPassword: String? = nil,
sslBinary: String = "/usr/bin/openssl",
pushRoutesMiddleware: (any Middleware)? = nil,
logger: Logger? = nil
logger: Logger? = nil,
pemWWDRCertificate: String,
pemCertificate: String,
pemPrivateKey: String,
pemPrivateKeyPassword: String? = nil,
openSSLPath: String = "/usr/bin/openssl"
) throws {
self.service = try .init(
app: app,
delegate: delegate,
signingFilesDirectory: signingFilesDirectory,
wwdrCertificate: wwdrCertificate,
pushRoutesMiddleware: pushRoutesMiddleware,
logger: logger,
pemWWDRCertificate: pemWWDRCertificate,
pemCertificate: pemCertificate,
pemPrivateKey: pemPrivateKey,
pemPrivateKeyPassword: pemPrivateKeyPassword,
sslBinary: sslBinary,
pushRoutesMiddleware: pushRoutesMiddleware,
logger: logger
openSSLPath: openSSLPath
)
}

Expand All @@ -56,9 +53,10 @@ public final class OrdersService: Sendable {
/// - Parameters:
/// - order: The order to generate the content for.
/// - db: The `Database` to use.
///
/// - Returns: The generated order content.
public func generateOrderContent(for order: Order, on db: any Database) async throws -> Data {
try await service.generateOrderContent(for: order, on: db)
public func build(order: Order, on db: any Database) async throws -> Data {
try await service.build(order: order, on: db)
}

/// Adds the migrations for Wallet orders models.
Expand All @@ -71,16 +69,6 @@ public final class OrdersService: Sendable {
migrations.add(OrdersErrorLog())
}

/// Sends push notifications for a given order.
///
/// - Parameters:
/// - id: The `UUID` of the order to send the notifications for.
/// - typeIdentifier: The type identifier of the order.
/// - db: The `Database` to use.
public func sendPushNotificationsForOrder(id: UUID, of typeIdentifier: String, on db: any Database) async throws {
try await service.sendPushNotificationsForOrder(id: id, of: typeIdentifier, on: db)
}

/// Sends push notifications for a given order.
///
/// - Parameters:
Expand Down
Loading
Loading