// OneS1ghtBridge.swift: bridge for calling OneS1ght from Objective-C
import Foundation
import OneS1ght
@MainActor @objc public final class OneS1ghtBridge: NSObject {
// MARK: Initialization
@objc public static func initialize(sdkKey: String, completion: @escaping (NSError?) -> Void) {
Task {
do { try await OneS1ght.initialize(sdkKey: sdkKey); completion(nil) }
catch { completion(nsError(error)) }
}
}
/// status: "authorized" | "denied" | "unsupported"
@objc public static func requestPermission(_ completion: @escaping (String) -> Void) {
Task { completion(String(describing: await OneS1ght.requestPermission())) }
}
@objc public static func setLanguage(_ code: String?) {
OneS1ght.setLanguage(code)
}
@objc public static func reset(_ completion: @escaping () -> Void) {
Task { await OneS1ght.reset(); completion() }
}
/// level: "log" | "info" | "warn" | "error"
@objc public static func onDebugLog(_ handler: @escaping (String, String) -> Void) {
OneS1ght.onDebugLog = { level, message in handler(String(describing: level), message) }
}
// MARK: State
@objc public static var isInitialized: Bool { OneS1ght.isInitialized }
/// "available" | "osVersionTooLow" | "deviceNotSupported"
@objc public static var deviceAvailability: String { String(describing: OneS1ght.deviceAvailability) }
@objc public static var isDeviceAvailable: Bool { OneS1ght.isDeviceAvailable }
@objc public static var googleMapKey: String? { OneS1ght.googleMapKey }
@objc public static var sdkVersion: String { OneS1ght.sdkVersion }
// MARK: Profiles
@objc public static func createProfile(_ attributes: [String: String],
completion: @escaping (String?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.createProfile(attributes), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func fetchProfile(_ profileId: String,
completion: @escaping ([String: String]?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.fetchProfile(profileId), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func replaceProfile(_ profileId: String, attributes: [String: String],
completion: @escaping (NSError?) -> Void) {
Task {
do { try await OneS1ght.replaceProfile(profileId, attributes: attributes); completion(nil) }
catch { completion(nsError(error)) }
}
}
@objc public static func deleteProfile(_ profileId: String, completion: @escaping (NSError?) -> Void) {
Task {
do { try await OneS1ght.deleteProfile(profileId); completion(nil) }
catch { completion(nsError(error)) }
}
}
@objc public static func identify(profileId: String?) {
OneS1ght.identify(profileId: profileId)
}
// MARK: Space lookup — models are passed as NSDictionary
@objc public static func buildings(_ completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.buildings().map(dict), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func building(id: String, completion: @escaping ([String: Any]?, NSError?) -> Void) {
Task {
do { completion(dict(try await OneS1ght.building(id: id)), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func floors(buildingId: String,
completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.floors(buildingId: buildingId).map(dict), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func floor(buildingId: String, floorId: String,
completion: @escaping ([String: Any]?, NSError?) -> Void) {
Task {
do { completion(dict(try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func zones(buildingId: String, floorId: String,
completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.zones(buildingId: buildingId, floorId: floorId).map(dict), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func zone(buildingId: String, floorId: String, zoneId: String,
completion: @escaping ([String: Any]?, NSError?) -> Void) {
Task {
do {
let zone = try await OneS1ght.zone(buildingId: buildingId, floorId: floorId, zoneId: zoneId)
completion(dict(zone), nil)
} catch { completion(nil, nsError(error)) }
}
}
@objc public static func locators(buildingId: String, floorId: String,
completion: @escaping ([String: Any]?, NSError?) -> Void) {
Task {
do { completion(dict(try await OneS1ght.locators(buildingId: buildingId, floorId: floorId)), nil) }
catch { completion(nil, nsError(error)) }
}
}
// MARK: Setting the floor
/// A nil floorId clears the floor
@objc public static func setFloorMap(buildingId: String?, floorId: String?,
completion: @escaping (NSError?) -> Void) {
Task {
do {
var floor: Floor?
if let buildingId, let floorId {
floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
}
try await OneS1ght.setFloorMap(floor, buildingId: buildingId)
completion(nil)
} catch { completion(nsError(error)) }
}
}
@objc public static func refreshZones(_ completion: @escaping ([[String: Any]]) -> Void) {
Task { completion(await OneS1ght.refreshZones().map(dict)) }
}
// MARK: Positioning session
@objc public static func begin(completion: @escaping (NSError?) -> Void) {
Task {
do { try await OneS1ght.floorSession().begin(); completion(nil) }
catch { completion(nsError(error)) }
}
}
@objc public static func end(completion: @escaping () -> Void) {
Task {
if let session = try? OneS1ght.floorSession() { await session.end() }
completion()
}
}
@objc public static func pause() { (try? OneS1ght.floorSession())?.pause() }
@objc public static func resume() { (try? OneS1ght.floorSession())?.resume() }
@objc public static var isRunning: Bool { (try? OneS1ght.floorSession())?.isRunning ?? false }
@objc public static var isPaused: Bool { (try? OneS1ght.floorSession())?.isPaused ?? false }
// MARK: Session events
/// Meters, local to the floor plan
@objc public static func onPosition(_ handler: @escaping (Double, Double, Double) -> Void) {
(try? OneS1ght.floorSession())?.onPosition = { c in handler(c.x, c.y, c.z) }
}
@objc public static func onZoneEnter(_ handler: @escaping ([String: Any]) -> Void) {
(try? OneS1ght.floorSession())?.onZoneEnter = { zone in handler(dict(zone)) }
}
@objc public static func onZoneExit(_ handler: @escaping ([String: Any]) -> Void) {
(try? OneS1ght.floorSession())?.onZoneExit = { zone in handler(dict(zone)) }
}
@objc public static func onZoneDwell(_ handler: @escaping ([String: Any], Double) -> Void) {
(try? OneS1ght.floorSession())?.onZoneDwell = { zone, seconds in handler(dict(zone), seconds) }
}
/// triggers: [{ triggerId, type, payload }]
@objc public static func onTriggers(_ handler: @escaping (String, [[String: Any]]) -> Void) {
(try? OneS1ght.floorSession())?.onTriggers = { zoneId, triggers in
handler(zoneId, triggers.map { t in
var d: [String: Any] = ["triggerId": t.triggerId, "type": t.type]
if let payload = t.payload { d["payload"] = payload }
return d
})
}
}
@objc public static func onFloorDetected(_ handler: @escaping (String?) -> Void) {
(try? OneS1ght.floorSession())?.onFloorDetected = { floorId in handler(floorId) }
}
/// reason: "ended" | "engineFailed"
@objc public static func onStopped(_ handler: @escaping (String) -> Void) {
(try? OneS1ght.floorSession())?.onStopped = { reason in handler(String(describing: reason)) }
}
/// kind: "zonesChanged" | "planChanged" | "rulesChanged" | "sdkConfigChanged" | "resyncNeeded"
/// targetId: floor ID for zonesChanged·planChanged, zone ID for rulesChanged
@objc public static func onConfigChanged(_ handler: @escaping (String, String?) -> Void) {
(try? OneS1ght.floorSession())?.onConfigChanged = { change in
switch change {
case .zonesChanged(let floorId): handler("zonesChanged", floorId)
case .planChanged(let floorId): handler("planChanged", floorId)
case .rulesChanged(let zoneId): handler("rulesChanged", zoneId)
case .sdkConfigChanged: handler("sdkConfigChanged", nil)
case .resyncNeeded: handler("resyncNeeded", nil)
@unknown default: handler("unknown", nil)
}
}
}
// MARK: Data upload
@objc public static func uploadPendingPositions(_ completion: @escaping () -> Void) {
Task { await OneS1ght.uploadPendingPositions(); completion() }
}
@objc public static func discardPendingPositions() {
OneS1ght.discardPendingPositions()
}
// MARK: Conversion
private static func dict(_ b: Building) -> [String: Any] {
var d: [String: Any] = ["id": b.id, "name": b.name]
if let count = b.floorCount { d["floorCount"] = count }
return d
}
private static func dict(_ f: Floor) -> [String: Any] {
var d: [String: Any] = ["id": f.id, "name": f.name, "hasPlan": f.hasPlan,
"originX": f.originX, "originY": f.originY,
"widthM": f.widthM, "heightM": f.heightM]
if let image = f.image { d["image"] = image }
return d
}
private static func dict(_ z: Zone) -> [String: Any] {
var d: [String: Any] = ["id": z.id, "name": z.name,
"polygon": z.polygon.map { [$0.x, $0.y] }]
if let dwell = z.dwellSeconds { d["dwellSeconds"] = dwell }
return d
}
private static func dict(_ l: FloorLocators) -> [String: Any] {
var d: [String: Any] = ["locators": l.locators.map {
["address": $0.address, "x": $0.x, "y": $0.y, "z": $0.z] as [String: Any]
}]
if let sessionId = l.sessionId { d["sessionId"] = sessionId }
return d
}
/// Swift error → NSError. domain "OneS1ght", code 3001, userInfo["sdkCode"] = "E3001"
private static func nsError(_ error: Error) -> NSError {
let code: SdkErrorCode
let message: String
switch error {
case let e as SdkError: code = e.code; message = e.code.summary
case let e as ApiError: code = e.code; message = e.description
default: return error as NSError
}
return NSError(domain: "OneS1ght",
code: Int(code.rawValue.dropFirst()) ?? 0,
userInfo: [NSLocalizedDescriptionKey: message,
"sdkCode": code.rawValue])
}
}