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

Add Linux Support #78

Closed
mergesort opened this issue Apr 13, 2023 · 12 comments
Closed

Add Linux Support #78

mergesort opened this issue Apr 13, 2023 · 12 comments
Labels
enhancement New feature or request

Comments

@mergesort
Copy link

Feature request

Add support for supabase-swift on Linux.

Is your feature request related to a problem? Please describe.

I'm trying to build a Swift on the server application and was hoping to use the Supabase SDK to do so, but not able to right now.

Describe the solution you'd like

It looks like it mostly should work, but ran into an issue around some URLSession APIs that don't seem to be available on Linux. I believe it should be something you can work around, I found this blog post discussing the subject, and while I don't know the library's history I saw some #if canImport(FoundationNetworking) checks in the code base already so I suspect some Linux support was at least attempted.

Describe alternatives you've considered

Using the REST API, but making this change would benefit more than just me, it would open up a whole class of possibilities for anyone using Supabase from the server.

Additional context

This is the failed build log for the issue I'm running into.

/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:4:25: error: cannot find type 'URLRequest' in scope
  func fetch(_ request: URLRequest) async throws -> (Data, HTTPURLResponse)
						^~~~~~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:5:26: error: cannot find type 'URLRequest' in scope
  func upload(_ request: URLRequest, from data: Data) async throws -> (Data, HTTPURLResponse)
						 ^~~~~~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageApi.swift:52:32: error: type of expression is ambiguous without more context
	let (data, response) = try await http.fetch(request)
						   ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageApi.swift:53:32: error: type of expression is ambiguous without more context
	if let mimeType = response.mimeType {
					  ~~~~~~~~~^~~~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageApi.swift:92:32: error: type of expression is ambiguous without more context
	let (data, response) = try await http.upload(request, from: formData.data)
						   ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:4:60: error: 'HTTPURLResponse' is unavailable: This type has moved to the FoundationNetworking module. Import that module to use it.
  func fetch(_ request: URLRequest) async throws -> (Data, HTTPURLResponse)
														   ^~~~~~~~~~~~~~~
Foundation.HTTPURLResponse:2:18: note: 'HTTPURLResponse' has been explicitly marked unavailable here
public typealias HTTPURLResponse = AnyObject
				 ^
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:5:78: error: 'HTTPURLResponse' is unavailable: This type has moved to the FoundationNetworking module. Import that module to use it.
  func upload(_ request: URLRequest, from data: Data) async throws -> (Data, HTTPURLResponse)
																			 ^~~~~~~~~~~~~~~
Foundation.HTTPURLResponse:2:18: note: 'HTTPURLResponse' has been explicitly marked unavailable here
public typealias HTTPURLResponse = AnyObject
				 ^
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:11:32: error: cannot find type 'URLRequest' in scope
  public func fetch(_ request: URLRequest) async throws -> (Data, HTTPURLResponse) {
							   ^~~~~~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:11:67: error: 'HTTPURLResponse' is unavailable: This type has moved to the FoundationNetworking module. Import that module to use it.
  public func fetch(_ request: URLRequest) async throws -> (Data, HTTPURLResponse) {
																  ^~~~~~~~~~~~~~~
Foundation.HTTPURLResponse:2:18: note: 'HTTPURLResponse' has been explicitly marked unavailable here
public typealias HTTPURLResponse = AnyObject
				 ^
error: Error Domain=NSCocoaErrorDomain Code=260 "The file doesn’t exist."
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:35:16: error: cannot find type 'URLRequest' in scope
	_ request: URLRequest,
			   ^~~~~~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:37:28: error: 'HTTPURLResponse' is unavailable: This type has moved to the FoundationNetworking module. Import that module to use it.
  ) async throws -> (Data, HTTPURLResponse) {
						   ^~~~~~~~~~~~~~~
Foundation.HTTPURLResponse:2:18: note: 'HTTPURLResponse' has been explicitly marked unavailable here
public typealias HTTPURLResponse = AnyObject
				 ^
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:13:33: error: type 'URLSession' (aka 'AnyObject') has no member 'shared'
	  let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
					 ~~~~~~~~~~ ^~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:27:40: error: tuple type '(_, HTTPURLResponse)' (aka '(_, AnyObject)') is not convertible to tuple type '(Data, HTTPURLResponse)' (aka '(Data, AnyObject)')
		continuation.resume(returning: (data, httpResponse))
									   ^
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:39:29: error: type 'URLSession' (aka 'AnyObject') has no member 'shared'
	  let task = URLSession.shared.uploadTask(with: request, from: data) { data, response, error in
				 ~~~~~~~~~~ ^~~~~~
/__w/SupabaseSample/SupabaseSample/.build/checkouts/storage-swift/Sources/SupabaseStorage/StorageHTTPClient.swift:53:40: error: tuple type '(_, HTTPURLResponse)' (aka '(_, AnyObject)') is not convertible to tuple type '(Data, HTTPURLResponse)' (aka '(Data, AnyObject)')
		continuation.resume(returning: (data, httpResponse))
@mergesort mergesort added the enhancement New feature or request label Apr 13, 2023
@mergesort
Copy link
Author

I decided to take a crack at it here after realizing that it was probably just a missing FoundationNetworking import and was able to get these errors to go away, but then started to get errors in the realtime-swift package.

/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:138:21: error: cannot find type 'URLSessionWebSocketTask' in scope
  private var task: URLSessionWebSocketTask?
					^~~~~~~~~~~~~~~~~~~~~~~
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:209:22: error: cannot find type 'URLSessionWebSocketTask' in scope
	webSocketTask _: URLSessionWebSocketTask,
					 ^~~~~~~~~~~~~~~~~~~~~~~
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:222:22: error: cannot find type 'URLSessionWebSocketTask' in scope
	webSocketTask _: URLSessionWebSocketTask,
					 ^~~~~~~~~~~~~~~~~~~~~~~
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:223:29: error: cannot find type 'URLSessionWebSocketTask' in scope
	didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
							^~~~~~~~~~~~~~~~~~~~~~~
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:130:56: error: cannot find type 'URLSessionWebSocketDelegate' in scope
public class URLSessionTransport: NSObject, Transport, URLSessionWebSocketDelegate {
													   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
error: Error Domain=NSCocoaErrorDomain Code=260 "The file doesn’t exist."
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:177:61: error: argument type 'URLSessionTransport' does not conform to expected type 'URLSessionDelegate'
	session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
															^
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:177:61: note: add missing conformance to 'URLSessionDelegate' to class 'URLSessionTransport'
	session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
															^
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:178:21: error: value of type 'URLSession' has no member 'webSocketTask'
	task = session?.webSocketTask(with: url)
		   ~~~~~~~~ ^~~~~~~~~~~~~
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:191:27: error: cannot find 'URLSessionWebSocketTask' in scope
	guard let closeCode = URLSessionWebSocketTask.CloseCode(rawValue: code) else {
						  ^~~~~~~~~~~~~~~~~~~~~~~
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:200:17: error: cannot infer contextual base in reference to member 'data'
	task?.send(.data(data)) { _ in
			   ~^~~~
/__w/SupbaseSample/SupbaseSample/.build/checkouts/realtime-swift/Sources/Realtime/Transport.swift:245:11: error: type of expression is ambiguous without more context
	task?.receive { result in
	~~~~~~^~~~~~~~~~~~~~~~~~~

Some initial research tells me that URLSessionWebSocketTask may not be available because it's a part of Network.framework which isn't available on Linux, I was wondering if that's a dead end or if it might be possible to get it working another way?

@Jeehut
Copy link

Jeehut commented Jan 18, 2024

@mergesort I believe this was fixed in #184. This can be closed, no? @brianmichel

@mergesort
Copy link
Author

Yep, I think so! Thank you so much @brianmichel. 🙇🏻‍♂️

@grdsdev
Copy link
Collaborator

grdsdev commented Jan 18, 2024

I'll make a new release with the Windows/Linux support soon.

@grdsdev
Copy link
Collaborator

grdsdev commented Jan 25, 2024

@mergesort the 2.2.1 release is out containing this feature, if you want to give it a try.

@mergesort
Copy link
Author

mergesort commented Mar 19, 2024

@grdsdev Thanks a lot for the hard work! I'm trying to integrate Supabase on my server and not quite sure what values to provide for the SupabaseClientOptions parameter.

My SupabaseClient looks something like this, but I'm not sure conceptually what I should be providing for the DatabaseOptions, AuthOptions, and GlobalOptions parameters.

private lazy var client: SupabaseClient = {
    #if os(Linux)
    SupabaseClient(
        supabaseURL: self.supabaseURL,
        supabaseKey: self.apiKey,
        options: SupabaseClientOptions(db: ..., auth: ..., global: ...)
    )
    #else
    SupabaseClient(
        supabaseURL: self.supabaseURL,
        supabaseKey: self.apiKey
    )
    #endif
}()

Is there a default implementation, or an example of what information I should be providing in a Vapor app that's looking to interface with Supabase?

@mergesort
Copy link
Author

I ended up creating a dummy storage which looks like this.

#if os(Linux)
private extension SupabaseController {
    static let defaultOptions = SupabaseClientOptions(
        db: SupabaseClientOptions.DatabaseOptions(),
        auth: SupabaseClientOptions.AuthOptions(
            storage: EmptyStorage()
        ),
        global: SupabaseClientOptions.GlobalOptions()
    )
}

private struct EmptyStorage: AuthLocalStorage {
    func store(key: String, value: Data) throws {}
    func retrieve(key: String) throws -> Data? { nil }
    func remove(key: String) throws {}
}
#endif

It seems to work but I wanted to make sure I wasn't doing anything wrong or unexpected, does that look right to you?

@grdsdev
Copy link
Collaborator

grdsdev commented Mar 20, 2024

Hi @mergesort

The only required parameter for Linux is the auth.storage and that is used basically for storing/retrieving the access token. I'm not sure what would be the best implementation for this on a server scenario.

The other options are all optional, you could use it to set up things like the default JSON encoder and decoder.

Your EmptyStorage implementation won't work as it will always return no token when the API requests for authentication. I'd suggest you use at least an implementation that stores the values in an in-memory dictionary.

private class MemoryStorage: AuthLocalStorage {
    var storage: [String: Data] = [:]

    func store(key: String, value: Data) throws {
        storage[key] = value
    }

    func retrieve(key: String) throws -> Data? {
        storage[key]
    }

    func remove(key: String) throws {
        storage[key] = nil
    }
}

Also, make sure to synchronize access to the storage variable.

Let me know if that clarifies things a bit.

Thanks!

@mergesort
Copy link
Author

I'm not actually using Supabase for anything authentication-related so that probably wouldn't have caused a problem nor occurred to me but your explanation and approach makes sense. Thanks a lot @grdsdev !

@grdsdev
Copy link
Collaborator

grdsdev commented Mar 20, 2024

What you're using Supabase for?

You may want to use only the sub-package separately then, instead of SupabaseClient.

@mergesort
Copy link
Author

At the moment I'm using it to store files and retrieve files from Supabase buckets, but I'm not ruling out adding more functionality in the future. I believe I need SupabaseClient to use it's underlying Storage, but is there a sub-package that would be more suitable?

@grdsdev
Copy link
Collaborator

grdsdev commented Mar 21, 2024

Got it.

Supabase is formed of 5 sub-packages, PostgREST (Database), Auth, Realtime, Storage, and Functions.

If you're using only storage, you can add .product(name: "Storage", package: "supabase-swift"), and use SupabaseStorageClient directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants