supabase / supabase-flutter

Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products.
https://supabase.com/
MIT License
698 stars 163 forks source link

Windows Provider Authentication #239

Open Sesa1988 opened 1 year ago

Sesa1988 commented 1 year ago

Describe the bug I'm trying to make windows provider auth work. It works on all mobile platforms and macOS.

I'm getting following error on build:

error GE5CFE876: The method 'setMockMethodCallHandler' isn't defined for the class 'MethodChannel'. [C:\Solutions\Flutter\cryptowulf\build\windows\flutter\flutter_assemble.vcxproj] C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.CppCommon.targets(245,5): error MSB8066: Custom build for 'C:\Solutions\Flutter\cryptowulf\build\windows\CMakeFiles\30c876474e0d9b104aaa7a0057369414\flutter_windows.dll.rule;C:\Solutions\Flutter\cryptowulf\build\windows\CMakeFiles\325871e82e40f1229f3ac302a8257194\flutter_assemble.rule' exited with code 1. [C:\Solutions\Flutter\cryptowulf\build\windows\flutter\flutter_assemble.vcxproj] Exception: Build process failed.

Without using multi_instance_handler the build works but no back redirect to the app happens.

browser_message

It is also not clear to me what we have to do with that plugin, just close the second instance like this?

if (await isFirstInstance(arguments) == false) {
      exit(0); 
}

pubspec supabase_flutter: ^1.0.0-dev.9 url_protocol: ^1.0.0 multi_instance_handler: ^1.0.0

main_desktop.dart

if (PlatformHelper.isWindows()) {
    registerProtocolHandler(
      'com.sesa.cryptowulf.' + EnvironmentHelper.envName + '://callback',
      arguments: ['-url', '%s'],
    );
    if (await isFirstInstance(arguments) == false) {
      exit(0);
    }
  }

auth service (for all platforms)

@override
  Future<bool> signInWithGoogle() async {
    return await _supabaseClientService.get().auth.signInWithProvider(
          Provider.google,
          options: const AuthOptions(
            redirectTo:
                'com.sesa.cryptowulf.${EnvironmentHelper.envName}://callback',
          ),
        );
  }
win32_window.cpp ``` #include "app_links_windows/app_links_windows_plugin.h" #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { if (SendAppLinkToInstance(title)) { return false; } Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } bool Win32Window::SendAppLinkToInstance(const std::wstring& title) { // Find our exact window HWND hwnd = ::FindWindow(kWindowClassName, title.c_str()); if (hwnd) { // Dispatch new link to current window SendAppLink(hwnd); // (Optional) Restore our window to front in same state WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) }; GetWindowPlacement(hwnd, &place); switch(place.showCmd) { case SW_SHOWMAXIMIZED: ShowWindow(hwnd, SW_SHOWMAXIMIZED); break; case SW_SHOWMINIMIZED: ShowWindow(hwnd, SW_RESTORE); break; default: ShowWindow(hwnd, SW_NORMAL); break; } SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); SetForegroundWindow(hwnd); // END Restore // Window has been found, don't create another one. return true; } return false; } ```
win32_window.h ``` #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; // Dispatches link if any. // This method enables our app to be with a single instance too. // This is optional but mandatory if you want to catch further links in same app. bool SendAppLinkToInstance(const std::wstring& title); }; #endif // RUNNER_WIN32_WINDOW_H_ ```

Edit: maybe this has some relation, at this point I dont know what version ai tested for what.

https://github.com/supabase-community/supabase-flutter/issues/261

Sesa1988 commented 1 year ago

I forked the repos, updated the dependencies and the build error is gone.

For flutter_multi_instance_handler I forked another fork with a windows fix and updated the dependencies then but Im not sure if this is the right way, or if it should work on the original repositories or how to continue here.

url_protocol:
    git:
      url: https://github.com/Sesa1988/flutter_url_protocol.git
      ref: main
  # multi_instance_handler: ^1.0.0
  multi_instance_handler:
    git:
      url: https://github.com/Sesa1988/flutter_multi_instance_handler_windows_fix.git
      ref: main

Update: This still does not work, (https://github.com/supabase-community/supabase-flutter/issues/261) did not fixed this. @dshukertjr

Sesa1988 commented 1 year ago

Could you reproduce this issue with my failing build and configuration? @dshukertjr

dshukertjr commented 1 year ago

Without multi_instance_handler, does the app just not open at all? What opens instead?

Unfortunately I don't have a windows device handy, so I cannot reproduce it myself, but we use app_links under the hood to handle deep links, so might be worth opening an issue there?

Sesa1988 commented 1 year ago

Without multi_instance_handler, does the app just not open at all? What opens instead?

Unfortunately I don't have a windows device handy, so I cannot reproduce it myself, but we use app_links under the hood to handle deep links, so might be worth opening an issue there?

I'm not even sure if my windows specific changes are correct, especially the copy&paste part with the cpp and .h files. Without this package the build works but the redirection back.

I tried to fix the dependencies with my forks but only the build error is gone but authentication still does not work.

I think there is not enough documentation for authentication especially for all platforms. Maybe even a flutter example app with all auth features implemented for mobile, desktop and web would be great.

Sesa1988 commented 1 year ago

Without multi_instance_handler, does the app just not open at all? What opens instead?

Unfortunately I don't have a windows device handy, so I cannot reproduce it myself, but we use app_links under the hood to handle deep links, so might be worth opening an issue there?

Thats the build error I get with url_protocol: ^1.0.0, multi_instance_handler: ^1.0.0, supabase_flutter: ^1.1.0

/C:/devtools/flutter/.pub-cache/hosted/pub.dartlang.org/multi_instance_handler-1.0.0/lib/src/controller.dart(56,13): error GE5CFE876: The method 'setMockMethodCallHandler' isn't defined for the class 'MethodChannel'. [C:\Solutions\Flutter\cryptowulf\build\windows\flutter\flutter_assemble.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.CppCommon.targets(245,5): error MSB8066: Custom build for 'C:\Solutions\Flutter\cryptowulf\build\windows\CMakeFiles\30c876474e0d9b104aaa7a0057369414\flutter_windows.dll.rule;C:\Solutions\Flutter\cryptowulf\build\windows\CMakeFiles\325871e82e40f1229f3ac302a8257194\flutter_assemble.rule' exited with code 1. [C:\Solutions\Flutter\cryptowulf\build\windows\flutter\flutter_assemble.vcxproj]
Exception: Build process failed.

I forked a branch from a guy that fixed this build error on his branch, at least it looks like this from the changes he made but the authentication still does not work.

url_protocol:
    git:
      url: https://github.com/Sesa1988/flutter_url_protocol.git
      ref: main
  multi_instance_handler:
    git:
      url: https://github.com/Sesa1988/flutter_multi_instance_handler_windows_fix.git
      ref: main

Maybe my configuration is just wrong? At least it looks like it should work this way.