- Notifications
You must be signed in to change notification settings - Fork3
feat: add login flow & session management#10
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes from1 commit
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -3,3 +3,5 @@ disabled_rules: | ||
- trailing_comma | ||
type_name: | ||
allowed_symbols: "_" | ||
identifier_name: | ||
allowed_symbols: "_" |
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import SwiftUI | ||
enum About { | ||
private static var credits: NSAttributedString { | ||
let coder = NSMutableAttributedString( | ||
string: "Coder.com", | ||
attributes: [ | ||
.foregroundColor: NSColor.labelColor, | ||
.link: NSURL(string: "https://coder.com")!, | ||
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize), | ||
] | ||
) | ||
let separator = NSAttributedString( | ||
string: " | ", | ||
attributes: [ | ||
.foregroundColor: NSColor.labelColor, | ||
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize), | ||
] | ||
) | ||
let source = NSAttributedString( | ||
string: "GitHub", | ||
attributes: [ | ||
.foregroundColor: NSColor.labelColor, | ||
.link: NSURL(string: "https://github.com/coder/coder-desktop-macos")!, | ||
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize), | ||
] | ||
) | ||
coder.append(separator) | ||
coder.append(source) | ||
return coder | ||
} | ||
static func open() { | ||
#if compiler(>=5.9) && canImport(AppKit) | ||
if #available(macOS 14, *) { | ||
NSApp.activate() | ||
} else { | ||
NSApp.activate(ignoringOtherApps: true) | ||
} | ||
#else | ||
NSApp.activate(ignoringOtherApps: true) | ||
#endif | ||
NSApp.orderFrontStandardAboutPanel(options: [ | ||
.credits: credits, | ||
]) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import SwiftUI | ||
class PreviewClient: Client { | ||
required init() {} | ||
func initialise(url _: URL, token _: String?) {} | ||
func user(_: String) async throws -> User { | ||
try await Task.sleep(for: .seconds(1)) | ||
return User( | ||
id: UUID(), | ||
username: "admin", | ||
avatar_url: "", | ||
name: "admin", | ||
email: "admin@coder.com", | ||
created_at: Date.now, | ||
updated_at: Date.now, | ||
last_seen_at: Date.now, | ||
status: "active", | ||
login_type: "none", | ||
theme_preference: "dark", | ||
organization_ids: [], | ||
roles: [] | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,65 @@ | ||||||
import Alamofire | ||||||
import Foundation | ||||||
protocol Client: ObservableObject { | ||||||
func initialise(url: URL, token: String?) | ||||||
func user(_ ident: String) async throws -> User | ||||||
} | ||||||
class CoderClient: Client { | ||||||
ethanndickson marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||||||
public var url: URL! | ||||||
ethanndickson marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||||||
public var token: String? | ||||||
let decoder: JSONDecoder | ||||||
let encoder: JSONEncoder | ||||||
ethanndickson marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||||||
required init() { | ||||||
encoder = JSONEncoder() | ||||||
encoder.dateEncodingStrategy = .iso8601withFractionalSeconds | ||||||
decoder = JSONDecoder() | ||||||
decoder.dateDecodingStrategy = .iso8601withOptionalFractionalSeconds | ||||||
} | ||||||
func initialise(url: URL, token: String? = nil) { | ||||||
self.token = token | ||||||
self.url = url | ||||||
} | ||||||
func request<T: Encodable>( | ||||||
_ path: String, | ||||||
method: HTTPMethod, | ||||||
body: T | ||||||
ethanndickson marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||||||
) async -> DataResponse<Data, AFError> { | ||||||
let url = self.url.appendingPathComponent(path) | ||||||
let headers: HTTPHeaders = [Headers.sessionToken: token ?? ""] | ||||||
return await AF.request( | ||||||
url, | ||||||
method: method, | ||||||
parameters: body, | ||||||
encoder: JSONParameterEncoder.default, | ||||||
headers: headers | ||||||
).serializingData().response | ||||||
} | ||||||
func request( | ||||||
_ path: String, | ||||||
method: HTTPMethod | ||||||
) async -> DataResponse<Data, AFError> { | ||||||
let url = self.url.appendingPathComponent(path) | ||||||
let headers: HTTPHeaders = [Headers.sessionToken: token ?? ""] | ||||||
return await AF.request( | ||||||
url, | ||||||
method: method, | ||||||
headers: headers | ||||||
).serializingData().response | ||||||
} | ||||||
} | ||||||
enum ClientError: Error { | ||||||
case unexpectedStatusCode | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Suggested change
This lets you store the status code | ||||||
case badResponse | ||||||
} | ||||||
enum Headers { | ||||||
ethanndickson marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||||||
static let sessionToken = "Coder-Session-Token" | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import Foundation | ||
// Handling for ISO8601 Timestamps with fractional seconds | ||
// Directly from https://stackoverflow.com/questions/46458487/ | ||
extension ParseStrategy where Self == Date.ISO8601FormatStyle { | ||
static var iso8601withFractionalSeconds: Self { .init(includingFractionalSeconds: true) } | ||
} | ||
extension JSONDecoder.DateDecodingStrategy { | ||
static let iso8601withOptionalFractionalSeconds = custom { | ||
let string = try $0.singleValueContainer().decode(String.self) | ||
do { | ||
return try .init(string, strategy: .iso8601withFractionalSeconds) | ||
} catch { | ||
return try .init(string, strategy: .iso8601) | ||
} | ||
} | ||
} | ||
extension FormatStyle where Self == Date.ISO8601FormatStyle { | ||
static var iso8601withFractionalSeconds: Self { .init(includingFractionalSeconds: true) } | ||
} | ||
extension JSONEncoder.DateEncodingStrategy { | ||
static let iso8601withFractionalSeconds = custom { | ||
var container = $1.singleValueContainer() | ||
try container.encode($0.formatted(.iso8601withFractionalSeconds)) | ||
} | ||
} |
Uh oh!
There was an error while loading.Please reload this page.