65 lines
2.2 KiB
Swift
65 lines
2.2 KiB
Swift
import ExpoModulesCore
|
|
|
|
/**
|
|
* Starts a short-lived 127.0.0.1 listener so a provider can redirect the browser back to the app.
|
|
*
|
|
* The browser runs on this device, so the provider's loopback redirect reaches the listener, which
|
|
* answers with a redirect to `usage://oauth/<provider>`. The system authorization session
|
|
* (`ASWebAuthenticationSession`) intercepts that URL and returns it to JavaScript, which validates
|
|
* it and exchanges the code. Nothing is exchanged or stored here: the native side only forwards a
|
|
* request that has the expected shape, so a stray or malformed request never consumes the session.
|
|
*
|
|
* `start` resolves with `{ port }` or `{ error }`; the JavaScript side owns port fallback and
|
|
* always calls `stop` (also on cancel, timeout and unmount).
|
|
*/
|
|
public final class UsageOauthLoopbackModule: Module {
|
|
private let serialQueue = DispatchQueue(label: "com.nextroad-dev.aiusage.oauth-loopback.module")
|
|
private var server: LoopbackServer?
|
|
|
|
public func definition() -> ModuleDefinition {
|
|
Name("UsageOauthLoopback")
|
|
|
|
AsyncFunction("start") { (provider: String, port: Int, promise: Promise) in
|
|
guard (provider == "codex" || provider == "openrouter"), port > 0, port <= 65535,
|
|
let candidate = UInt16(exactly: port)
|
|
else {
|
|
promise.resolve(["error": "invalid-request"])
|
|
return
|
|
}
|
|
self.serialQueue.async {
|
|
self.server?.stop()
|
|
self.server = nil
|
|
let server = LoopbackServer(provider: provider)
|
|
self.server = server
|
|
server.start(port: candidate) { result in
|
|
switch result {
|
|
case .success(let bound):
|
|
promise.resolve(["port": Int(bound)])
|
|
case .failure:
|
|
self.serialQueue.async {
|
|
if self.server === server {
|
|
self.server = nil
|
|
}
|
|
}
|
|
promise.resolve(["error": "port-unavailable"])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AsyncFunction("stop") {
|
|
self.serialQueue.sync {
|
|
self.server?.stop()
|
|
self.server = nil
|
|
}
|
|
}
|
|
|
|
OnDestroy {
|
|
self.serialQueue.sync {
|
|
self.server?.stop()
|
|
self.server = nil
|
|
}
|
|
}
|
|
}
|
|
}
|