Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions Sources/InkletMac/Models/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,10 @@ final class AppModel {
return devices.first { $0.id == device.id } ?? device
}

/// Claims a hardware display with the bind URI it handed over during
/// Bluetooth setup, and puts it at the top of the list.
func bindDisplay(uri: String) async throws -> Device {
guard let credential = DisplayBindCredential(uri: uri) else {
/// Claims a hardware display with the credential it handed over during
/// Bluetooth setup (`inklet://bind?…`), and puts it at the top of the list.
func bindDisplay(credential raw: String) async throws -> Device {
guard let credential = DisplayBindCredential(uri: raw) else {
throw DisplayBindError.invalidCredential
}
let dto: DeviceDTO
Expand Down
2 changes: 1 addition & 1 deletion Sources/InkletMac/Views/PairDisplayView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct PairDisplayView: View {
.background(Ink.bg)
.navigationTitle("Pair Display")
.onAppear {
flow.bindHandler = { uri in try await model.bindDisplay(uri: uri).id }
flow.bindHandler = { credential in try await model.bindDisplay(credential: credential).id }
flow.start()
}
.onDisappear { flow.stop() }
Expand Down
8 changes: 4 additions & 4 deletions Sources/InkletSetupKit/SetupFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
/// pick display (or scan its QR) → enter the code on its screen → pick the
/// Wi-Fi it should join → wait for it to join → claim it for this account.
///
/// Claiming uses the display's bind credential (the `inklet://bind?…` URI its
/// Claiming uses the display's credential (the `inklet://bind?…` URI its
/// NFC tag carries), handed over inside the code-secured session; the app
/// supplies `bindHandler` to post it to `POST /api/devices/bind/nfc`.
@MainActor
Expand Down Expand Up @@ -41,7 +41,7 @@ public final class InkletSetupFlow: ObservableObject {
/// next action.
@Published public var errorMessage: String?

/// Claims the display with its bind URI; returns the new device id if known.
/// Claims the display with its credential; returns the new device id if known.
public var bindHandler: (@MainActor (String) async throws -> String?)?

private var session: InkletSetupSession?
Expand Down Expand Up @@ -138,13 +138,13 @@ public final class InkletSetupFlow: ObservableObject {
self.session = nil
central.stopScanning()

guard let uri = info?.bind, !uri.isEmpty, let bindHandler else {
guard let credential = info?.credential, !credential.isEmpty, let bindHandler else {
step = .finished(.wifiOnly)
return
}
step = .binding
do {
step = .finished(.bound(deviceID: try await bindHandler(uri)))
step = .finished(.bound(deviceID: try await bindHandler(credential)))
} catch {
let message = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription
step = .finished(.bindFailed(message))
Expand Down
8 changes: 4 additions & 4 deletions Sources/InkletSetupKit/SetupModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ public struct InkletWiFiNetwork: Identifiable, Hashable, Sendable {
}
}

/// What the display reports about itself over the secured session. `bind` is
/// the display's claim credential — the same `inklet://bind?v=2&…` URI its NFC
/// tag carries — when the factory issued one.
/// What the display reports about itself over the secured session.
/// `credential` is its claim credential — `inklet://bind?v=2&…`, the same one
/// its NFC tag carries — when it has been issued one.
public struct InkletDisplayInfo: Decodable, Equatable, Sendable {
public let hwId: String
public let thingName: String
public let firmware: String?
public let bind: String?
public let credential: String?
}

/// The setup screen's QR code: Espressif's standard provisioning JSON,
Expand Down
10 changes: 10 additions & 0 deletions Tests/InkletSetupKitTests/SetupKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,13 @@ private func hex(_ s: String) -> Data {
#expect(InkletSetupQR("inklet://bind?v=2") == nil)
#expect(InkletSetupQR(#"{"name":"INKLET-2078","pop":"X","transport":"softap"}"#) == nil)
}

// The display hands its claim credential over as "credential"; absent means
// none was issued.
@Test func deviceInfoReadsTheCredential() throws {
let uri = "inklet://bind?v=2&kid=nfc-master-2026-01&hw=92be8ee85ee74c7b9ea7de00b6eb3801&cv=1&p=fsArTXVRLZ4Oc9EqH-zb0w"
let issued = #"{"hwId":"92be","thingName":"inklet-92be","credential":"\#(uri)"}"#
let none = #"{"hwId":"92be","thingName":"inklet-92be","firmware":"x"}"#
#expect(try JSONDecoder().decode(InkletDisplayInfo.self, from: Data(issued.utf8)).credential == uri)
#expect(try JSONDecoder().decode(InkletDisplayInfo.self, from: Data(none.utf8)).credential == nil)
}
Loading