├── .documentation ├── .gitignore ├── docfx.json ├── index.md └── toc.yml ├── .env.sample ├── .github ├── icon.png ├── supabase-csharp.png └── workflows │ ├── build-and-test.yml │ ├── build-documentation.yaml │ └── release.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Examples ├── BlazorWebAssemblySupabaseTemplate │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── App.razor │ ├── BlazorWebAssemblySupabaseTemplate.csproj │ ├── Dtos │ │ ├── BaseModelApp.cs │ │ ├── Todo.cs │ │ └── TodoPrivate.cs │ ├── Pages │ │ ├── Auth │ │ │ ├── Login.razor │ │ │ ├── Login.razor.cs │ │ │ ├── LoginPage.razor │ │ │ └── LoginPage.razor.cs │ │ ├── Crud │ │ │ ├── CrudPage.razor │ │ │ └── CrudPage.razor.cs │ │ ├── CrudPrivate │ │ │ ├── CrudPagePrivate.razor │ │ │ └── CrudPagePrivate.razor.cs │ │ ├── FileUploadFolder │ │ │ ├── FileUpload.razor │ │ │ └── FileUpload.razor.cs │ │ └── Index.razor │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Providers │ │ ├── CustomAuthStateProvider.cs │ │ └── CustomSupabaseSessionHandler.cs │ ├── README.MD │ ├── Services │ │ ├── AuthService.cs │ │ ├── DatabaseService.cs │ │ └── StorageService.cs │ ├── Shared │ │ ├── MainLayout.razor │ │ ├── MainLayout.razor.cs │ │ └── MainLayout.razor.css │ ├── Supabase │ │ └── DatabasePolicies.sql │ ├── _Imports.razor │ ├── firebase.json │ └── wwwroot │ │ ├── css │ │ └── app.css │ │ ├── favicon.png │ │ ├── icon-192.png │ │ ├── icon-512.png │ │ ├── index.html │ │ ├── manifest.json │ │ ├── sample-data │ │ └── weather.json │ │ ├── service-worker.js │ │ └── service-worker.published.js ├── SupabaseExample │ ├── Assets │ │ └── supabase-csharp.png │ ├── Models │ │ ├── Channel.cs │ │ └── Movie.cs │ ├── Program.cs │ ├── SupabaseExample.csproj │ └── db │ │ ├── 00-schema.sql │ │ └── 01-dummy-data.sql └── Xamarin.Forms │ ├── SupabaseExample.sln │ ├── SupabaseExampleXA.Android │ ├── Assets │ │ └── AboutAssets.txt │ ├── MainActivity.cs │ ├── Properties │ │ ├── AndroidManifest.xml │ │ └── AssemblyInfo.cs │ ├── Resources │ │ ├── AboutResources.txt │ │ ├── Resource.designer.cs │ │ ├── layout │ │ │ ├── Tabbar.xml │ │ │ └── Toolbar.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── icon.xml │ │ │ └── icon_round.xml │ │ ├── mipmap-hdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-mdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ └── SupabaseExampleXA.Android.csproj │ ├── SupabaseExampleXA.iOS │ ├── AppDelegate.cs │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon1024.png │ │ │ ├── Icon120.png │ │ │ ├── Icon152.png │ │ │ ├── Icon167.png │ │ │ ├── Icon180.png │ │ │ ├── Icon20.png │ │ │ ├── Icon29.png │ │ │ ├── Icon40.png │ │ │ ├── Icon58.png │ │ │ ├── Icon60.png │ │ │ ├── Icon76.png │ │ │ ├── Icon80.png │ │ │ └── Icon87.png │ ├── Entitlements.plist │ ├── Info.plist │ ├── Main.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Resources │ │ ├── Default-568h@2x.png │ │ ├── Default-Portrait.png │ │ ├── Default-Portrait@2x.png │ │ ├── Default.png │ │ ├── Default@2x.png │ │ └── LaunchScreen.storyboard │ └── SupabaseExampleXA.iOS.csproj │ └── SupabaseExampleXA │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── ChannelListPage.xaml │ ├── ChannelListPage.xaml.cs │ ├── LoadingPage.xaml │ ├── LoadingPage.xaml.cs │ ├── MessageListPage.xaml │ ├── MessageListPage.xaml.cs │ ├── Models │ ├── Channel.cs │ ├── Message.cs │ └── User.cs │ └── SupabaseExampleXA.csproj ├── LICENSE ├── README.md ├── Supabase.sln ├── Supabase ├── Client.cs ├── DefaultSupabaseSessionHandler.cs ├── Extensions │ └── DictionaryExtension.cs ├── Interfaces │ ├── ISupabaseClient.cs │ ├── ISupabaseFunctions.cs │ └── ISupabaseTable.cs ├── StatelessClient.cs ├── Supabase.csproj ├── SupabaseModel.cs ├── SupabaseOptions.cs └── SupabaseTable.cs ├── SupabaseTests ├── Assets │ └── supabase-csharp.png ├── Client.cs ├── Models │ ├── Channel.cs │ ├── Stub.cs │ └── User.cs ├── StatelessClient.cs ├── Stubs │ ├── FakeAuthClient.cs │ ├── FakeFunctionsClient.cs │ ├── FakeRealtimeClient.cs │ ├── FakeRestClient.cs │ └── FakeStorageClient.cs ├── SupabaseTests.csproj └── db │ ├── 00-schema.sql │ ├── 01-auth-schema.sql │ ├── 02-rest-schema.sql │ └── 03-dummy-data.sql └── docker-compose.yml /.documentation/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | /_site 10 | /api 11 | -------------------------------------------------------------------------------- /.documentation/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "src": "../Supabase", 7 | "files": [ 8 | "**/*.csproj" 9 | ] 10 | }, 11 | { 12 | "src": "../modules/gotrue-csharp/Gotrue", 13 | "files": [ 14 | "**/*.csproj" 15 | ] 16 | }, 17 | { 18 | "src": "../modules/realtime-csharp/Realtime", 19 | "files": [ 20 | "**/*.csproj" 21 | ] 22 | }, 23 | { 24 | "src": "../modules/postgrest-csharp/Postgrest", 25 | "files": [ 26 | "**/*.csproj" 27 | ] 28 | }, 29 | { 30 | "src": "../modules/storage-csharp/Storage", 31 | "files": [ 32 | "**/*.csproj" 33 | ] 34 | }, 35 | { 36 | "src": "../modules/functions-csharp/Functions", 37 | "files": [ 38 | "**/*.csproj" 39 | ] 40 | }, 41 | { 42 | "src": "../modules/core-csharp/Core", 43 | "files": [ 44 | "**/*.csproj" 45 | ] 46 | } 47 | ], 48 | "dest": "api" 49 | } 50 | ], 51 | "build": { 52 | "content": [ 53 | { 54 | "files": [ 55 | "**/*.{md,yml}" 56 | ], 57 | "exclude": [ 58 | "_site/**" 59 | ] 60 | } 61 | ], 62 | "resource": [ 63 | { 64 | "files": [ 65 | "images/**" 66 | ] 67 | } 68 | ], 69 | "output": "_site", 70 | "template": [ 71 | "default", 72 | "modern" 73 | ], 74 | "globalMetadata": { 75 | "_appName": "supabase-csharp", 76 | "_appTitle": "supabase-csharp", 77 | "_enableSearch": true 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /.documentation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | _layout: landing 3 | --- 4 | 5 | # supabase-csharp 6 | -------------------------------------------------------------------------------- /.documentation/toc.yml: -------------------------------------------------------------------------------- 1 | - name: API 2 | href: api/ 3 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | AWS_ACCESS_KEY= 2 | AWS_SECRET_ACCESS_KEY= 3 | AWS_BUCKET= 4 | AWS_REGION=us-east-1 -------------------------------------------------------------------------------- /.github/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/.github/icon.png -------------------------------------------------------------------------------- /.github/supabase-csharp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/.github/supabase-csharp.png -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | 13 | env: 14 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 15 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 16 | AWS_BUCKET: ${{ secrets.AWS_BUCKET }} 17 | AWS_REGION: ${{ secrets.AWS_REGION }} 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v3 24 | with: 25 | dotnet-version: 8.x 26 | 27 | - name: Restore dependencies 28 | run: dotnet restore 29 | 30 | - name: Build 31 | run: dotnet build --configuration Release --no-restore 32 | 33 | - name: Initialize Testing Stack 34 | run: docker compose up -d 35 | 36 | - name: Test 37 | run: dotnet test --no-restore 38 | -------------------------------------------------------------------------------- /.github/workflows/build-documentation.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release/* # Default release branch 8 | 9 | jobs: 10 | docs: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | submodules: "true" 16 | persist-credentials: false 17 | 18 | - name: Setup .NET 19 | uses: actions/setup-dotnet@v3 20 | with: 21 | dotnet-version: 8.x 22 | 23 | - name: Install docfx 24 | run: dotnet tool update -g docfx 25 | 26 | - name: Build documentation 27 | run: docfx .documentation/docfx.json 28 | 29 | - name: Deploy 🚀 30 | uses: JamesIves/github-pages-deploy-action@v4 31 | with: 32 | folder: .documentation/_site 33 | token: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish NuGet Package 2 | 3 | on: 4 | push: 5 | branches: 6 | - release/* # Default release branch 7 | 8 | jobs: 9 | publish: 10 | name: build, pack & publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v3 17 | with: 18 | dotnet-version: 8.x 19 | 20 | - name: Wait for tests to succeed 21 | uses: lewagon/wait-on-check-action@v1.3.1 22 | with: 23 | ref: ${{ github.ref }} 24 | check-name: build-and-test 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | wait-interval: 10 27 | 28 | - name: Restore dependencies 29 | run: dotnet restore 30 | 31 | - name: Build 32 | run: dotnet build --configuration Release --no-restore 33 | 34 | - name: Generate package 35 | run: dotnet pack ./Supabase/Supabase.csproj --configuration Release 36 | 37 | - name: Publish on version change 38 | run: dotnet nuget push "**/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} 39 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/gotrue-csharp"] 2 | path = modules/gotrue-csharp 3 | url = https://github.com/supabase-community/gotrue-csharp 4 | [submodule "modules/postgrest-csharp"] 5 | path = modules/postgrest-csharp 6 | url = https://github.com/supabase-community/postgrest-csharp 7 | [submodule "modules/realtime-csharp"] 8 | path = modules/realtime-csharp 9 | url = https://github.com/supabase-community/realtime-csharp 10 | [submodule "modules/storage-csharp"] 11 | path = modules/storage-csharp 12 | url = https://github.com/supabase-community/storage-csharp 13 | [submodule "modules/functions-csharp"] 14 | path = modules/functions-csharp 15 | url = https://github.com/supabase-community/functions-csharp 16 | [submodule "modules/core-csharp"] 17 | path = modules/core-csharp 18 | url = git@github.com:supabase-community/core-csharp.git 19 | [submodule "Documentation"] 20 | path = Documentation 21 | url = git@github.com:supabase-community/supabase-csharp.wiki.git 22 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "blazorwasmsupabasetemplate" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | # MaterialeShop 255 | src/MaterialeShop.Web/Logs/* 256 | src/MaterialeShop.Web.Host/Logs/* 257 | src/MaterialeShop.Web.Public/Logs/* 258 | src/MaterialeShop.Web.Public.Host/Logs/* 259 | src/MaterialeShop.AuthServer/Logs/* 260 | src/MaterialeShop.HttpApi.Host/Logs/* 261 | src/MaterialeShop.HttpApi.Host/Logs/* 262 | src/MaterialeShop.DbMigrator/Logs/* 263 | src/MaterialeShop.Blazor.Server/Logs/* 264 | src/MaterialeShop.Blazor.Server.Tiered/Logs/* 265 | 266 | # Use abp install-libs to restore. 267 | **/wwwroot/libs/* 268 | 269 | # IdentityServer temp files 270 | tempkey.rsa 271 | tempkey.jwk 272 | 273 | !**/packages/*Theme* 274 | 275 | .history -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch and Debug Standalone Blazor WebAssembly App", 6 | "type": "blazorwasm", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Not found 13 | 14 |

Sorry, there's nothing at this address.

15 |
16 |
17 |
18 |
-------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/BlazorWebAssemblySupabaseTemplate.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | service-worker-assets.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Dtos/BaseModelApp.cs: -------------------------------------------------------------------------------- 1 | using Postgrest.Attributes; 2 | using Postgrest.Models; 3 | 4 | public class BaseModelApp : BaseModel 5 | { 6 | [PrimaryKey("id", false)] // Key is Autogenerated 7 | public int Id { get; set; } 8 | 9 | // There is a rule in the definition of this table to set to false as default value to this field. 10 | // So, It's necessary to set default value in the class creation bacause the insert method would send this value as null. 11 | // This way, the sent row wouldn't be set to false automaticaly by Postgre default value 12 | // because the field was sent with null value. 13 | // To fix this, the null fields should not be serialized in the request. 14 | [Column("SoftDeleted")] 15 | public bool? SoftDeleted { get; set; } = false; 16 | 17 | [Column("SoftDeletedAt")] 18 | public DateTime? SoftDeletedAt { get; set; } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Dtos/Todo.cs: -------------------------------------------------------------------------------- 1 | using Postgrest.Attributes; 2 | 3 | namespace BlazorWebAssemblySupabaseTemplate.Dtos; 4 | 5 | [Table("Todo")] 6 | public class Todo : BaseModelApp 7 | { 8 | [Column("Title")] 9 | public string? Title { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Dtos/TodoPrivate.cs: -------------------------------------------------------------------------------- 1 | using Postgrest.Attributes; 2 | 3 | namespace BlazorWebAssemblySupabaseTemplate.Dtos; 4 | 5 | [Table("TodoPrivate")] 6 | public class TodoPrivate : BaseModelApp 7 | { 8 | [Column("title")] 9 | public string? Title { get; set; } 10 | 11 | [Column("user_id")] 12 | public string UserId { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/Auth/Login.razor: -------------------------------------------------------------------------------- 1 | @inject AuthService AuthService 2 | @inject NavigationManager NavigationManager 3 | @inject ISnackbar Snackbar 4 | 5 | 6 | 7 | 8 | Welcome 9 |
10 | 11 | 12 |
13 | Login 15 |
16 |
17 |
-------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/Auth/Login.razor.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorWebAssemblySupabaseTemplate.Pages.Auth; 2 | 3 | public partial class Login 4 | { 5 | protected string Email {get; set;} = "cliente1@gmail.com"; 6 | protected string Password {get; set;} = "senhasdadasdaasd"; 7 | 8 | public async Task OnClickLogin() 9 | { 10 | await AuthService.Login(Email, Password); 11 | Snackbar.Add("Login successful"); 12 | NavigationManager.NavigateTo($"/"); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/Auth/LoginPage.razor: -------------------------------------------------------------------------------- 1 | @page "/login" 2 | 3 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/Auth/LoginPage.razor.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorWebAssemblySupabaseTemplate.Pages.Auth; 2 | 3 | public partial class LoginPage 4 | { 5 | 6 | } 7 | 8 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/Crud/CrudPage.razor: -------------------------------------------------------------------------------- 1 | @page "/crud" 2 | @using Dtos 3 | 4 | @inject DatabaseService DatabaseService 5 | @inject NavigationManager NavigationManager 6 | @inject ISnackbar Snackbar 7 | 8 |
9 | 10 | Todos 11 |
12 | 13 | 14 | 15 | 16 |

New item

17 |
18 |
19 | 20 | 22 | 23 | 24 | 26 | @if (_processingNewItem) 27 | { 28 | 29 | Processing 30 | } 31 | else 32 | { 33 | Save 34 | } 35 | 36 | 37 | 38 |
39 |
40 | 41 |
42 | 43 | @if (TodoListFiltered == null) 44 | { 45 | 46 | 47 | 48 | Title 49 | Action 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | } 60 | else if (TodoListFiltered.Count == 0) 61 | { 62 | 63 | 64 | 65 | Title 66 | Action 67 | 68 | 69 | 70 |
71 | There is no items in this table. 72 |
73 | 74 |
75 | } 76 | else 77 | { 78 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | Title 89 | 90 | 91 | 92 | Action 93 | 94 | 95 | 96 | @context?.Title 97 | 98 | @* *@ 102 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | } 111 | 112 |
-------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/Crud/CrudPage.razor.cs: -------------------------------------------------------------------------------- 1 | using BlazorWebAssemblySupabaseTemplate.Dtos; 2 | using MudBlazor; 3 | 4 | namespace BlazorWebAssemblySupabaseTemplate.Pages.Crud; 5 | 6 | public partial class CrudPage 7 | { 8 | protected override async Task OnInitializedAsync() 9 | { 10 | await GetTable(); 11 | } 12 | 13 | // ---------------- SELECT TABLE 14 | private IReadOnlyList? TodoList { get; set; } 15 | private IReadOnlyList? TodoListFiltered { get; set; } 16 | private MudTable? _table; 17 | protected async Task GetTable() 18 | { 19 | // await Task.Delay(10000); 20 | IReadOnlyList todos = await DatabaseService.From(); 21 | TodoList = todos; 22 | TodoListFiltered = todos; 23 | await InvokeAsync(StateHasChanged); 24 | } 25 | 26 | // ---------------- SEARCH 27 | private void OnValueChangedSearch(string text) 28 | { 29 | TodoListFiltered = TodoList?.Where(row => row.Title.Contains(text)).ToList(); 30 | } 31 | 32 | // ---------------- DELETE 33 | private async Task OnClickDelete(Todo item) 34 | { 35 | await DatabaseService.SoftDelete(item); 36 | 37 | await GetTable(); 38 | } 39 | 40 | // ---------------- CREATE NEW 41 | 42 | protected Todo Model = new(); 43 | private bool _success = false; 44 | string[] _errors = { }; 45 | MudForm? _form; 46 | private bool _processingNewItem = false; 47 | private async Task OnClickSave() 48 | { 49 | _processingNewItem = true; 50 | await DatabaseService.Insert(Model); 51 | Model = new(); 52 | await GetTable(); 53 | _success = false; 54 | _processingNewItem = false; 55 | } 56 | } -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor: -------------------------------------------------------------------------------- 1 | @page "/crud-private" 2 | @using Dtos 3 | @using Blazored.LocalStorage 4 | 5 | @inject DatabaseService DatabaseService 6 | @inject NavigationManager NavigationManager 7 | @inject ISnackbar Snackbar 8 | @inject ILocalStorageService LocalStorage 9 | 10 |
11 | 12 | Todos private by RLS 13 |
14 | The data in this page is filtered automatically by Row Level Security policies. 15 | This way, you need to be logged in to see the content of this table and to insert an item. 16 | Also, each user logged in can only SELECT (get from database) and UPDATE its own rows. 17 | See the code file DatabasePolicies.sql on GitHub to get to know the policies. 18 | 19 |
20 | 21 | 22 | 23 | 24 |

New item

25 |
26 |
27 | 28 | 30 | 31 | 32 | 34 | @if (_processingNewItem) 35 | { 36 | 37 | Processing 38 | } 39 | else 40 | { 41 | Save 42 | } 43 | 44 | 45 | 46 |
47 |
48 | 49 |
50 | 51 | @if (TodoListFiltered == null) 52 | { 53 | 54 | 55 | 56 | Title 57 | Action 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | } 72 | else if (TodoListFiltered.Count == 0) 73 | { 74 | 75 | 76 | 77 | Title 78 | Action 79 | 80 | 81 | 82 |
83 | There is no items in this table. 84 |
85 | 86 |
87 | } 88 | else 89 | { 90 | 92 | 93 | 96 | 97 | 98 | 99 | 100 | Title 101 | 102 | 103 | 104 | 105 | User_id 106 | 107 | 108 | 109 | Action 110 | 111 | 112 | 113 | @context?.Title 114 | @context?.UserId 115 | 116 | @* *@ 120 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | } 129 | 130 |
-------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor.cs: -------------------------------------------------------------------------------- 1 | using BlazorWebAssemblySupabaseTemplate.Dtos; 2 | using BlazorWebAssemblySupabaseTemplate.Services; 3 | using Microsoft.AspNetCore.Components; 4 | using MudBlazor; 5 | using Supabase.Gotrue; 6 | 7 | namespace BlazorWebAssemblySupabaseTemplate.Pages.CrudPrivate; 8 | 9 | public partial class CrudPagePrivate 10 | { 11 | [Inject] 12 | protected AuthService AuthService { get; set; } 13 | [Inject] 14 | protected IDialogService DialogService { get; set; } 15 | 16 | protected User? UserLoggedIn { get; set; } 17 | 18 | protected override async Task OnInitializedAsync() 19 | { 20 | UserLoggedIn = await AuthService.GetUser(); 21 | await GetTable(); 22 | } 23 | 24 | // ---------------- SELECT TABLE 25 | private IReadOnlyList? TodoList { get; set; } 26 | private IReadOnlyList? TodoListFiltered { get; set; } 27 | private MudTable? _table; 28 | protected async Task GetTable() 29 | { 30 | // await Task.Delay(10000); 31 | IReadOnlyList todos = await DatabaseService.From(); 32 | TodoList = todos; 33 | TodoListFiltered = todos; 34 | await InvokeAsync(StateHasChanged); 35 | } 36 | 37 | // ---------------- SEARCH 38 | private void OnValueChangedSearch(string text) 39 | { 40 | TodoListFiltered = TodoList?.Where(row => row.Title.Contains(text)).ToList(); 41 | } 42 | 43 | // ---------------- DELETE 44 | private async Task OnClickDelete(TodoPrivate item) 45 | { 46 | if (UserLoggedIn is not null) 47 | { 48 | await DatabaseService.SoftDelete(item); 49 | await GetTable(); 50 | } 51 | else 52 | { 53 | await DialogService.ShowMessageBox( 54 | "Warning", 55 | "You need to be logged In to create or change an item in this table." 56 | ); 57 | } 58 | } 59 | 60 | // ---------------- CREATE NEW 61 | 62 | protected TodoPrivate Model = new(); 63 | private bool _success = false; 64 | string[] _errors = { }; 65 | MudForm? _form; 66 | private bool _processingNewItem = false; 67 | private async Task OnClickSave() 68 | { 69 | if (UserLoggedIn is not null && UserLoggedIn?.Id is not null) 70 | { 71 | Console.WriteLine("UserLoggedIn?.Id"); 72 | Console.WriteLine(UserLoggedIn?.Id); 73 | 74 | Model.UserId = UserLoggedIn?.Id; 75 | 76 | _processingNewItem = true; 77 | try 78 | { 79 | await DatabaseService.Insert(Model); 80 | Model = new(); 81 | await GetTable(); 82 | } 83 | catch (Exception ex) 84 | { 85 | await DialogService.ShowMessageBox( 86 | "Warning", 87 | "This request was not completed because of some problem. Error message: " 88 | +ex.Message 89 | ); 90 | } 91 | _success = false; 92 | _processingNewItem = false; 93 | } 94 | else 95 | { 96 | await DialogService.ShowMessageBox( 97 | "Warning", 98 | "You need to be logged In to create or change an item in this table." 99 | ); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/FileUploadFolder/FileUpload.razor: -------------------------------------------------------------------------------- 1 | @page "/uploadfile" 2 | 3 | @inject StorageService StorageService 4 | @inject ISnackbar Snackbar 5 | @inject IJSRuntime Js 6 | @inject IDialogService DialogService 7 | 8 |
9 | 10 | File upload example 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | Upload Files 26 | 27 | 28 | 29 | Max file size: @_maxFileSizeInMb MB 30 | 31 | 32 |
33 |
34 | List of files uploaded 35 |
36 | 37 | @if (FileObjects == null) 38 | { 39 | 40 | 41 | 42 | Title 43 | Action 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | } 54 | else if (FileObjects.Count <= 1) 55 | { 56 | 57 | 58 | 59 | Title 60 | Action 61 | 62 | 63 | 64 |
65 | There is no items in this table. 66 |
67 | 68 |
69 | 70 | } else 71 | { 72 | 73 | 74 | 75 | File name 76 | Created At 77 | 78 | 79 | 80 | @foreach (var row in FileObjects) 81 | { 82 | if (row.Name != ".emptyFolderPlaceholder") 83 | { 84 | 85 | 86 | 88 | @row.Name 89 | 90 | @row.CreatedAt 91 | 92 | } 93 | } 94 | 95 | 96 | } 97 | 98 | 99 | 100 | 101 |
102 | 103 | You need to be logged in to upload a file. 104 |
105 | Login 106 |
107 |
108 |
109 | 110 | 111 | 112 | 113 |
-------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/FileUploadFolder/FileUpload.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Forms; 2 | 3 | namespace BlazorWebAssemblySupabaseTemplate.Pages.FileUploadFolder; 4 | 5 | public partial class FileUpload 6 | { 7 | protected override async Task OnInitializedAsync() 8 | { 9 | await GetFilesFromBucket(); 10 | } 11 | 12 | public List? FileObjects; 13 | private async Task GetFilesFromBucket() 14 | { 15 | FileObjects = await StorageService.GetFilesFromBucket("userfiles"); 16 | } 17 | 18 | static long _maxFileSizeInMb = 15; 19 | long _maxFileSize = 1024 * 1024 * _maxFileSizeInMb; 20 | private async Task UploadFilesAsync(IBrowserFile file) 21 | { 22 | Console.WriteLine("file.Name"); 23 | Console.WriteLine(file.Name); 24 | 25 | try 26 | { 27 | var streamData = file.OpenReadStream(_maxFileSize); 28 | 29 | var filename = await StorageService.UploadFile("userfiles", streamData, file.Name); 30 | 31 | Snackbar.Add( "File uploaded: "+filename.Split("/").Last() ); 32 | 33 | await GetFilesFromBucket(); 34 | await InvokeAsync(StateHasChanged); 35 | } 36 | catch (IOException ex) 37 | { 38 | Snackbar.Add( "Error: Max file size exceeded." ); 39 | } 40 | } 41 | 42 | private async Task DownloadClick(Supabase.Storage.FileObject row) 43 | { 44 | var result = await DialogService.ShowMessageBox( 45 | "Warning", 46 | "The download feature is disabled because of security risks, but it could be tested with your own risk by downloading the source code and running it. " 47 | ); 48 | 49 | // byte[] downloadedBytes = await StorageService.DownloadFile("userfiles", row.Name); 50 | 51 | // await JS.InvokeVoidAsync("downloadFileFromStream", row.Name, downloadedBytes); 52 | } 53 | } -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Index 4 | 5 |
6 | 7 | 8 |

Hello, world!

9 | 10 |
11 |

This is a example project to show how to connect to Supabase using supabase-csharp library.

13 | 14 |
15 |
16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | Auth test area. 32 | Congratulations! You are logged in! 33 | 34 | 35 | Auth test area. 36 |
37 | You are not allowed to see this content 38 |
39 | Login 40 |
41 |
42 |
43 | 44 |
45 |
46 | 47 |

Demo created by Rhuan Barros.

48 |
49 |
Credits
50 | 51 |

52 | 53 | https://github.com/supabase-community/supabase-csharp 54 | 55 |

56 |

57 | 58 | https://github.com/patrickgod/BlazorAuthenticationTutorial 59 | 60 |

61 |

62 | 63 | https://github.com/d11-jwaring/SupabaseRealtimeBlazorWASM/tree/master 64 | 65 |

66 |

67 | 68 | https://swimburger.net/blog/dotnet/how-to-deploy-blazor-webassembly-to-firebase-hosting 69 | 70 |

71 | 72 | 73 |
-------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | using BlazorWebAssemblySupabaseTemplate; 4 | using Microsoft.AspNetCore.Components.Authorization; 5 | using BlazorWebAssemblySupabaseTemplate.Providers; 6 | using BlazorWebAssemblySupabaseTemplate.Services; 7 | using Blazored.LocalStorage; 8 | using MudBlazor.Services; 9 | 10 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 11 | builder.RootComponents.Add("#app"); 12 | builder.RootComponents.Add("head::after"); 13 | 14 | builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 15 | 16 | builder.Services.AddMudServices(); 17 | builder.Services.AddBlazoredLocalStorage(); 18 | 19 | 20 | // ---------- BLAZOR AUTH 21 | builder.Services.AddScoped( 22 | provider => new CustomAuthStateProvider( 23 | provider.GetRequiredService(), 24 | provider.GetRequiredService(), 25 | provider.GetRequiredService>() 26 | ) 27 | ) 28 | ; 29 | builder.Services.AddAuthorizationCore(); 30 | 31 | // ---------- SUPABASE 32 | var url = "https://pylnesfgmytjegzzculn.supabase.co"; 33 | var key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB5bG5lc2ZnbXl0amVnenpjdWxuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjgyOTMwMzcsImV4cCI6MTk4Mzg2OTAzN30.kI29Q_qYWDH5SD6oi5NTwHG6Pxy1e1AUfR8s_ga45lE"; 34 | 35 | builder.Services.AddScoped( 36 | provider => new Supabase.Client( 37 | url, 38 | key, 39 | new Supabase.SupabaseOptions 40 | { 41 | AutoRefreshToken = true, 42 | AutoConnectRealtime = true, 43 | SessionHandler = new CustomSupabaseSessionHandler( 44 | provider.GetRequiredService(), 45 | provider.GetRequiredService>() 46 | ) 47 | } 48 | ) 49 | ); 50 | 51 | builder.Services.AddScoped(); 52 | builder.Services.AddScoped(); 53 | builder.Services.AddScoped(); 54 | 55 | await builder.Build().RunAsync(); -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:35974", 7 | "sslPort": 44392 8 | } 9 | }, 10 | "profiles": { 11 | "http": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 16 | "applicationUrl": "http://localhost:5013", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 26 | "applicationUrl": "https://localhost:7152;http://localhost:5013", 27 | "environmentVariables": { 28 | "ASPNETCORE_ENVIRONMENT": "Development" 29 | } 30 | }, 31 | "IIS Express": { 32 | "commandName": "IISExpress", 33 | "launchBrowser": true, 34 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 35 | "environmentVariables": { 36 | "ASPNETCORE_ENVIRONMENT": "Development" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomAuthStateProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using System.Text.Json; 3 | using Blazored.LocalStorage; 4 | using Microsoft.AspNetCore.Components.Authorization; 5 | 6 | // Credits https://github.com/patrickgod/BlazorAuthenticationTutorial 7 | 8 | namespace BlazorWebAssemblySupabaseTemplate.Providers; 9 | 10 | public class CustomAuthStateProvider : AuthenticationStateProvider 11 | { 12 | private readonly ILocalStorageService _localStorage; 13 | private readonly Supabase.Client _client; 14 | 15 | private readonly ILogger _logger; 16 | 17 | public CustomAuthStateProvider( 18 | ILocalStorageService localStorage, 19 | Supabase.Client client, 20 | ILogger logger 21 | ) 22 | { 23 | logger.LogInformation("------------------- CONSTRUCTOR -------------------"); 24 | 25 | _localStorage = localStorage; 26 | _client = client; 27 | _logger = logger; 28 | } 29 | 30 | public override async Task GetAuthenticationStateAsync() 31 | { 32 | _logger.LogInformation("------------------- GetAuthenticationStateAsync -------------------"); 33 | 34 | // Sets client auth and connects to realtime (if enabled) 35 | await _client.InitializeAsync(); 36 | 37 | var identity = new ClaimsIdentity(); 38 | // _http.DefaultRequestHeaders.Authorization = null; 39 | 40 | if (!string.IsNullOrEmpty(_client.Auth.CurrentSession?.AccessToken)) 41 | { 42 | identity = new ClaimsIdentity(ParseClaimsFromJwt(_client.Auth.CurrentSession.AccessToken), "jwt"); 43 | // _http.DefaultRequestHeaders.Authorization = 44 | // new AuthenticationHeaderValue("Bearer", token.Replace("\"", "")); 45 | } 46 | 47 | var user = new ClaimsPrincipal(identity); 48 | var state = new AuthenticationState(user); 49 | 50 | NotifyAuthenticationStateChanged(Task.FromResult(state)); 51 | 52 | return state; 53 | } 54 | 55 | public static IEnumerable ParseClaimsFromJwt(string jwt) 56 | { 57 | var payload = jwt.Split('.')[1]; 58 | var jsonBytes = ParseBase64WithoutPadding(payload); 59 | var keyValuePairs = JsonSerializer.Deserialize>(jsonBytes); 60 | return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())); 61 | } 62 | 63 | private static byte[] ParseBase64WithoutPadding(string base64) 64 | { 65 | switch (base64.Length % 4) 66 | { 67 | case 2: base64 += "=="; break; 68 | case 3: base64 += "="; break; 69 | } 70 | return Convert.FromBase64String(base64); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomSupabaseSessionHandler.cs: -------------------------------------------------------------------------------- 1 | using Blazored.LocalStorage; 2 | using Supabase.Gotrue; 3 | using Supabase.Gotrue.Interfaces; 4 | 5 | namespace BlazorWebAssemblySupabaseTemplate.Providers; 6 | 7 | public class CustomSupabaseSessionHandler : IGotrueSessionPersistence 8 | { 9 | private readonly ILocalStorageService _localStorage; 10 | private readonly ILogger _logger; 11 | private const string SessionKey = "SUPABASE_SESSION"; 12 | 13 | public CustomSupabaseSessionHandler( 14 | ILocalStorageService localStorage, 15 | ILogger logger 16 | ) 17 | { 18 | logger.LogInformation("------------------- CONSTRUCTOR -------------------"); 19 | _localStorage = localStorage; 20 | _logger = logger; 21 | } 22 | 23 | public async void DestroySession() 24 | { 25 | _logger.LogInformation("------------------- SessionDestroyer -------------------"); 26 | await _localStorage.RemoveItemAsync(SessionKey); 27 | } 28 | 29 | public async void SaveSession(Session session) 30 | { 31 | _logger.LogInformation("------------------- SessionPersistor -------------------"); 32 | await _localStorage.SetItemAsync(SessionKey, session); 33 | } 34 | 35 | public Session? LoadSession() 36 | { 37 | _logger.LogInformation("------------------- SessionRetriever -------------------"); 38 | 39 | var session = _localStorage.GetItemAsync(SessionKey).Result; 40 | return session?.ExpiresAt() <= DateTime.Now ? null : session; 41 | } 42 | } -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/README.MD: -------------------------------------------------------------------------------- 1 | #TODO 2 | - CustomSupabaseSessionHandler not working. 3 | - how to test: 4 | - Logout 5 | - Login 6 | - Access Crud table private RLS page 7 | - Insert some row 8 | - you will see the row displayed below 9 | - press F5 on the browser 10 | - the rows will disappear 11 | - Maybe i created CustomSupabaseSessionHandler wrong. But it seem like that the token is not being sent with the headers... 12 | 13 | - create policy to insert that check if the user_id is the same of the user logged in trying to insert. 14 | 15 | # Credits 16 | https://github.com/supabase-community/supabase-csharp 17 | https://github.com/patrickgod/BlazorAuthenticationTutorial 18 | https://github.com/d11-jwaring/SupabaseRealtimeBlazorWASM/tree/master 19 | 20 | 21 | # How to deploy 22 | dotnet publish -c Release -o release 23 | firebase deploy 24 | 25 | # Error message 26 | Failed to find a valid digest in the 'integrity' attribute for resource 'https://blazorwasmsupabasetemplate.web.app/_framework/blazor.boot.json' with computed SHA-256 integrity 'XdcujrjLMAFyEwhjckKrX5naw+S/ieI/g8U7BkEVUc8='. The resource has been blocked. 27 | Unknown error occurred while trying to verify integrity. 28 | service-worker.js:22 Uncaught (in promise) TypeError: Failed to fetch 29 | at service-worker.js:22:54 30 | at async onInstall (service-worker.js:22:5) 31 | 32 | -----> This is because of old files in cache in the browser. Clear cache by clicking in the clear button (just ctrl + f5 doesn't work) and after press ctrl + f5. This will solve. 33 | 34 | 35 | 36 | # JWT ERROR 37 | - WHEN CLICK IN LOGOUT BUTTON OR TRY TO GET DATA FROM DATABASE, BUT THE JWT IS ALREADY EXPIRED: 38 | - this problem only happens when login, close the app, later, after token expired, the app is open again. 39 | - I set the JWT expiry limit to 180 just to test this. 40 | 41 | blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] 42 | Unhandled exception rendering component: {"code":401,"msg":"invalid JWT: unable to parse or verify signature, token is expired by 22m22s"} 43 | Supabase.Gotrue.RequestException: {"code":401,"msg":"invalid JWT: unable to parse or verify signature, token is expired by 22m22s"} 44 | at Supabase.Gotrue.Helpers.MakeRequest(HttpMethod method, String url, Object data, Dictionary`2 headers) 45 | at Supabase.Gotrue.Client.SignOut() 46 | at BlazorWebAssemblySupabaseTemplate.Services.AuthService.Logout() 47 | at BlazorWebAssemblySupabaseTemplate.Shared.MainLayout.OnClickLogout() 48 | at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) 49 | at MudBlazor.MudChip.OnClickHandler(MouseEventArgs ev) 50 | at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) 51 | at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task , ComponentState ) 52 | 53 | 54 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Services/AuthService.cs: -------------------------------------------------------------------------------- 1 | using Blazored.LocalStorage; 2 | using Microsoft.AspNetCore.Components.Authorization; 3 | using Supabase.Gotrue; 4 | 5 | namespace BlazorWebAssemblySupabaseTemplate.Services; 6 | 7 | public class AuthService 8 | { 9 | private readonly Supabase.Client _client; 10 | private readonly AuthenticationStateProvider _customAuthStateProvider; 11 | private readonly ILocalStorageService _localStorage; 12 | private readonly ILogger _logger; 13 | 14 | public AuthService( 15 | Supabase.Client client, 16 | AuthenticationStateProvider customAuthStateProvider, 17 | ILocalStorageService localStorage, 18 | ILogger logger 19 | ) 20 | { 21 | logger.LogInformation("------------------- CONSTRUCTOR -------------------"); 22 | 23 | _client = client; 24 | _customAuthStateProvider = customAuthStateProvider; 25 | _localStorage = localStorage; 26 | _logger = logger; 27 | } 28 | 29 | public async Task Login(string email, string password) 30 | { 31 | _logger.LogInformation("METHOD: Login"); 32 | 33 | var session = await _client.Auth.SignIn(email, password); 34 | 35 | _logger.LogInformation("------------------- User logged in -------------------"); 36 | // logger.LogInformation($"instance.Auth.CurrentUser.Id {client?.Auth?.CurrentUser?.Id}"); 37 | _logger.LogInformation($"client.Auth.CurrentUser.Email {_client?.Auth?.CurrentUser?.Email}"); 38 | 39 | await _customAuthStateProvider.GetAuthenticationStateAsync(); 40 | } 41 | 42 | public async Task Logout() 43 | { 44 | await _client.Auth.SignOut(); 45 | await _localStorage.RemoveItemAsync("token"); 46 | await _customAuthStateProvider.GetAuthenticationStateAsync(); 47 | } 48 | 49 | public async Task GetUser() 50 | { 51 | var session = await _client.Auth.RetrieveSessionAsync(); 52 | return session?.User; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Services/DatabaseService.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Blazored.LocalStorage; 3 | using Microsoft.AspNetCore.Components.Authorization; 4 | using MudBlazor; 5 | using Supabase.Functions; 6 | 7 | namespace BlazorWebAssemblySupabaseTemplate.Services; 8 | 9 | public class DatabaseService 10 | { 11 | private readonly Supabase.Client _client; 12 | private readonly AuthenticationStateProvider _customAuthStateProvider; 13 | private readonly ILocalStorageService _localStorage; 14 | private readonly ILogger _logger; 15 | private readonly IDialogService _dialogService; 16 | 17 | public DatabaseService( 18 | Supabase.Client client, 19 | AuthenticationStateProvider customAuthStateProvider, 20 | ILocalStorageService localStorage, 21 | ILogger logger, 22 | IDialogService dialogService) 23 | { 24 | logger.LogInformation("------------------- CONSTRUCTOR -------------------"); 25 | 26 | _client = client; 27 | _customAuthStateProvider = customAuthStateProvider; 28 | _localStorage = localStorage; 29 | _logger = logger; 30 | _dialogService = dialogService; 31 | } 32 | 33 | public async Task> From() where TModel : BaseModelApp, new() 34 | { 35 | var modeledResponse = await _client 36 | .From() 37 | .Where(x => x.SoftDeleted == false) 38 | .Get(); 39 | return modeledResponse.Models; 40 | } 41 | 42 | public async Task> Delete(TModel item) where TModel : BaseModelApp, new() 43 | { 44 | var modeledResponse = await _client 45 | .From() 46 | .Delete(item); 47 | return modeledResponse.Models; 48 | } 49 | 50 | public async Task?> Insert(TModel item) where TModel : BaseModelApp, new() 51 | { 52 | Postgrest.Responses.ModeledResponse modeledResponse; 53 | try 54 | { 55 | modeledResponse = await _client 56 | .From() 57 | .Insert(item); 58 | 59 | return modeledResponse.Models; 60 | } 61 | catch (Client.RequestException ex) 62 | { 63 | if(ex.Response?.StatusCode == HttpStatusCode.Forbidden) 64 | await _dialogService.ShowMessageBox( 65 | "Warning", 66 | "This database request was forbidden." 67 | ); 68 | else 69 | await _dialogService.ShowMessageBox( 70 | "Warning", 71 | "This request was not completed because of some problem with the http request. \n " 72 | +ex.Response?.RequestMessage 73 | ); 74 | } 75 | 76 | return null; 77 | } 78 | 79 | public async Task> SoftDelete(TModel item) where TModel : BaseModelApp, new() 80 | { 81 | var modeledResponse = await _client.Postgrest 82 | .Table() 83 | .Set(x => x.SoftDeleted, true) 84 | .Set(x => x.SoftDeletedAt, DateTime.Now) 85 | .Where(x => x.Id == item.Id) 86 | // .Filter(x => x.Id, Operator.Equals, item.Id) 87 | .Update(); 88 | return modeledResponse.Models; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Services/StorageService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using MudBlazor; 3 | using Supabase.Storage.Interfaces; 4 | 5 | namespace BlazorWebAssemblySupabaseTemplate.Services; 6 | 7 | public class StorageService 8 | { 9 | private readonly Supabase.Client _client; 10 | private readonly ILogger _logger; 11 | private readonly IDialogService _dialogService; 12 | private readonly IStorageClient _storage; 13 | 14 | public StorageService( 15 | Supabase.Client client, 16 | ILogger logger, 17 | IDialogService dialogService 18 | ) 19 | { 20 | logger.LogInformation("------------------- CONSTRUCTOR -------------------"); 21 | _client = client; 22 | _logger = logger; 23 | _dialogService = dialogService; 24 | 25 | _storage = client.Storage; 26 | } 27 | 28 | public async Task UploadFile(String bucketName, Stream streamData, String fileName) 29 | { 30 | var bucket = _storage.From(bucketName); 31 | 32 | // TODO: verify if there is a better way to do it 33 | // Maybe this isn't a good way to do it 34 | var bytesData = await StreamToBytesAsync(streamData); 35 | 36 | var fileExtension = fileName.Split(".").Last(); 37 | 38 | var saveName = "File_" + DateTime.Now; 39 | 40 | saveName = saveName.Replace("/", "_").Replace(" ", "_").Replace(":", "_"); 41 | saveName = saveName + "." + fileExtension; 42 | 43 | // Console.WriteLine("saveName"); 44 | // Console.WriteLine(saveName); 45 | 46 | return await bucket.Upload(bytesData, saveName); 47 | } 48 | 49 | public async Task StreamToBytesAsync(Stream streamData) 50 | { 51 | byte[] bytes; 52 | 53 | using var memoryStream = new MemoryStream(); 54 | await streamData.CopyToAsync(memoryStream); 55 | bytes = memoryStream.ToArray(); 56 | 57 | return bytes; 58 | } 59 | 60 | public async Task?> GetFilesFromBucket(string bucketName) 61 | { 62 | return await _storage.From(bucketName).List(); 63 | } 64 | 65 | public Task DownloadFile(string bucketName, string fileName) 66 | { 67 | var bucket = _storage.From(bucketName); 68 | return bucket.Download(fileName, (_, f) => Debug.WriteLine($"Download Progress: {f}%")); 69 | } 70 | } -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | @inject AuthService AuthService 4 | @inject NavigationManager NavigationManager 5 | @inject ISnackbar Snackbar 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | Logout 25 | 26 | 27 | Login 28 | 29 | 30 | 31 | 32 | 33 | Blazor WASM Supabase Demo 34 | 35 | 36 | @* ********************************** MENU ********************************** *@ 37 | 38 | 39 | 40 | Crud table 41 | 42 | 43 | Crud table private RLS 44 | 45 | 46 | Upload File 47 | 48 | 49 | 50 | 51 | @* ********************************** MENU ********************************** *@ 52 | 53 | 54 | 55 | @Body 56 | 57 | 58 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorWebAssemblySupabaseTemplate.Shared; 2 | 3 | public partial class MainLayout 4 | { 5 | bool _drawerOpen = true; 6 | 7 | void DrawerToggle() 8 | { 9 | _drawerOpen = !_drawerOpen; 10 | } 11 | 12 | private async Task OnClickLogout() 13 | { 14 | await AuthService.Logout(); 15 | Snackbar.Add("Logout successful"); 16 | NavigationManager.NavigateTo($"/"); 17 | } 18 | } -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row:not(.auth) { 41 | display: none; 42 | } 43 | 44 | .top-row.auth { 45 | justify-content: space-between; 46 | } 47 | 48 | .top-row ::deep a, .top-row ::deep .btn-link { 49 | margin-left: 0; 50 | } 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .page { 55 | flex-direction: row; 56 | } 57 | 58 | .sidebar { 59 | width: 250px; 60 | height: 100vh; 61 | position: sticky; 62 | top: 0; 63 | } 64 | 65 | .top-row { 66 | position: sticky; 67 | top: 0; 68 | z-index: 1; 69 | } 70 | 71 | .top-row.auth ::deep a:first-child { 72 | flex: 1; 73 | text-align: right; 74 | width: 0; 75 | } 76 | 77 | .top-row, article { 78 | padding-left: 2rem !important; 79 | padding-right: 1.5rem !important; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/Supabase/DatabasePolicies.sql: -------------------------------------------------------------------------------- 1 | -- CREATE POLICY "policy_name" 2 | -- ON public.Lista FOR 3 | -- DELETE USING ( auth.uid() = user_id ); 4 | 5 | -- CREATE POLICY "policy_name" 6 | -- ON public.Lista FOR 7 | -- SELECT USING ( auth.uid() = user_id ); 8 | 9 | -- #################### TABELA LISTA 10 | 11 | -- CREATE POLICY "Enable read access for all users" ON "public"."Lista" 12 | -- AS PERMISSIVE FOR SELECT 13 | -- TO authenticated 14 | -- USING (true) 15 | 16 | 17 | CREATE POLICY "Users can SELECT if own row" 18 | ON "public"."TodoPrivate" AS PERMISSIVE FOR SELECT 19 | TO authenticated 20 | USING ( auth.uid() = user_id ); 21 | 22 | CREATE POLICY "Users can UPDATE if own row" 23 | ON "public"."TodoPrivate" AS PERMISSIVE FOR UPDATE 24 | TO authenticated 25 | USING ( auth.uid() = user_id ); 26 | 27 | CREATE POLICY "Users can DELETE if own row" 28 | ON "public"."TodoPrivate" AS PERMISSIVE FOR DELETE 29 | TO authenticated 30 | USING ( auth.uid() = user_id ); 31 | 32 | CREATE POLICY "Users can INSERT only if own uuid" 33 | ON "public"."TodoPrivate" 34 | AS PERMISSIVE 35 | FOR INSERT 36 | TO authenticated 37 | WITH CHECK (auth.uid() = user_id); -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using BlazorWebAssemblySupabaseTemplate 10 | @using BlazorWebAssemblySupabaseTemplate.Shared 11 | @using Microsoft.AspNetCore.Components.Authorization 12 | @using Microsoft.AspNetCore.Authorization 13 | @using MudBlazor 14 | 15 | @using BlazorWebAssemblySupabaseTemplate.Services -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "release/wwwroot", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | /* @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); */ 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | h1:focus { 8 | outline: none; 9 | } 10 | 11 | a, .btn-link { 12 | color: #0071c1; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 22 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 23 | } 24 | 25 | .content { 26 | padding-top: 1.1rem; 27 | } 28 | 29 | .valid.modified:not([type=checkbox]) { 30 | outline: 1px solid #26b050; 31 | } 32 | 33 | .invalid { 34 | outline: 1px solid red; 35 | } 36 | 37 | .validation-message { 38 | color: red; 39 | } 40 | 41 | #blazor-error-ui { 42 | background: lightyellow; 43 | bottom: 0; 44 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 45 | display: none; 46 | left: 0; 47 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 48 | position: fixed; 49 | width: 100%; 50 | z-index: 1000; 51 | } 52 | 53 | #blazor-error-ui .dismiss { 54 | cursor: pointer; 55 | position: absolute; 56 | right: 0.75rem; 57 | top: 0.5rem; 58 | } 59 | 60 | .blazor-error-boundary { 61 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 62 | padding: 1rem 1rem 1rem 3.7rem; 63 | color: white; 64 | } 65 | 66 | .blazor-error-boundary::after { 67 | content: "An error has occurred." 68 | } 69 | 70 | .loading-progress { 71 | position: relative; 72 | display: block; 73 | width: 8rem; 74 | height: 8rem; 75 | margin: 20vh auto 1rem auto; 76 | } 77 | 78 | .loading-progress circle { 79 | fill: none; 80 | stroke: #e0e0e0; 81 | stroke-width: 0.6rem; 82 | transform-origin: 50% 50%; 83 | transform: rotate(-90deg); 84 | } 85 | 86 | .loading-progress circle:last-child { 87 | stroke: #1b6ec2; 88 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 89 | transition: stroke-dasharray 0.05s ease-in-out; 90 | } 91 | 92 | .loading-progress-text { 93 | position: absolute; 94 | text-align: center; 95 | font-weight: bold; 96 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 97 | } 98 | 99 | .loading-progress-text:after { 100 | content: var(--blazor-load-percentage-text, "Loading"); 101 | } 102 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/favicon.png -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/icon-192.png -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/icon-512.png -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BlazorWebAssemblySupabaseTemplate 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 |
31 | An unhandled error has occurred. 32 | Reload 33 | 🗙 34 |
35 | 36 | 37 | 38 | 39 | 40 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BlazorWebAssemblySupabaseTemplate", 3 | "short_name": "BlazorWebAssemblySupabaseTemplate", 4 | "start_url": "./", 5 | "display": "standalone", 6 | "background_color": "#ffffff", 7 | "theme_color": "#03173d", 8 | "prefer_related_applications": false, 9 | "icons": [ 10 | { 11 | "src": "icon-512.png", 12 | "type": "image/png", 13 | "sizes": "512x512" 14 | }, 15 | { 16 | "src": "icon-192.png", 17 | "type": "image/png", 18 | "sizes": "192x192" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/sample-data/weather.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2022-01-06", 4 | "temperatureC": 1, 5 | "summary": "Freezing" 6 | }, 7 | { 8 | "date": "2022-01-07", 9 | "temperatureC": 14, 10 | "summary": "Bracing" 11 | }, 12 | { 13 | "date": "2022-01-08", 14 | "temperatureC": -13, 15 | "summary": "Freezing" 16 | }, 17 | { 18 | "date": "2022-01-09", 19 | "temperatureC": -16, 20 | "summary": "Balmy" 21 | }, 22 | { 23 | "date": "2022-01-10", 24 | "temperatureC": -2, 25 | "summary": "Chilly" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/service-worker.js: -------------------------------------------------------------------------------- 1 | // In development, always fetch from the network and do not enable offline support. 2 | // This is because caching would make development more difficult (changes would not 3 | // be reflected on the first load after each change). 4 | self.addEventListener('fetch', () => { }); 5 | -------------------------------------------------------------------------------- /Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/service-worker.published.js: -------------------------------------------------------------------------------- 1 | // Caution! Be sure you understand the caveats before publishing an application with 2 | // offline support. See https://aka.ms/blazor-offline-considerations 3 | 4 | self.importScripts('./service-worker-assets.js'); 5 | self.addEventListener('install', event => event.waitUntil(onInstall(event))); 6 | self.addEventListener('activate', event => event.waitUntil(onActivate(event))); 7 | self.addEventListener('fetch', event => event.respondWith(onFetch(event))); 8 | 9 | const cacheNamePrefix = 'offline-cache-'; 10 | const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; 11 | const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ]; 12 | const offlineAssetsExclude = [ /^service-worker\.js$/ ]; 13 | 14 | async function onInstall(event) { 15 | console.info('Service worker: Install'); 16 | 17 | // Fetch and cache all matching items from the assets manifest 18 | const assetsRequests = self.assetsManifest.assets 19 | .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) 20 | .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) 21 | .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); 22 | await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); 23 | } 24 | 25 | async function onActivate(event) { 26 | console.info('Service worker: Activate'); 27 | 28 | // Delete unused caches 29 | const cacheKeys = await caches.keys(); 30 | await Promise.all(cacheKeys 31 | .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) 32 | .map(key => caches.delete(key))); 33 | } 34 | 35 | async function onFetch(event) { 36 | let cachedResponse = null; 37 | if (event.request.method === 'GET') { 38 | // For all navigation requests, try to serve index.html from cache 39 | // If you need some URLs to be server-rendered, edit the following check to exclude those URLs 40 | const shouldServeIndexHtml = event.request.mode === 'navigate'; 41 | 42 | const request = shouldServeIndexHtml ? 'index.html' : event.request; 43 | const cache = await caches.open(cacheName); 44 | cachedResponse = await cache.match(request); 45 | } 46 | 47 | return cachedResponse || fetch(event.request); 48 | } 49 | -------------------------------------------------------------------------------- /Examples/SupabaseExample/Assets/supabase-csharp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/SupabaseExample/Assets/supabase-csharp.png -------------------------------------------------------------------------------- /Examples/SupabaseExample/Models/Channel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Supabase.Postgrest.Attributes; 3 | using Supabase.Postgrest.Models; 4 | 5 | namespace SupabaseExample.Models 6 | { 7 | [Table("channels")] 8 | public class Channel : BaseModel 9 | { 10 | [PrimaryKey("id", false)] // Key is Autogenerated 11 | public int Id { get; set; } 12 | 13 | [Column("inserted_at")] 14 | public DateTime InsertedAt { get; set; } 15 | 16 | [Column("slug")] 17 | public string Slug { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/SupabaseExample/Models/Movie.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Supabase.Postgrest.Attributes; 4 | using Supabase.Postgrest.Models; 5 | 6 | namespace SupabaseExample.Models 7 | { 8 | [Table("movie")] 9 | public class Movie : BaseModel 10 | { 11 | [PrimaryKey("id", false)] 12 | public int Id { get; set; } 13 | 14 | [Column("name")] 15 | public string Name { get; set; } 16 | 17 | [Reference(typeof(Person))] 18 | public List Persons { get; set; } 19 | 20 | 21 | [Column("created_at")] 22 | public DateTime CreatedAt { get; set; } 23 | } 24 | 25 | [Table("person")] 26 | public class Person : BaseModel 27 | { 28 | [PrimaryKey("id", false)] 29 | public int Id { get; set; } 30 | 31 | [Column("first_name")] 32 | public string FirstName { get; set; } 33 | 34 | [Column("last_name")] 35 | public string LastName { get; set; } 36 | 37 | [Reference(typeof(Profile))] 38 | public Profile Profile { get; set; } 39 | 40 | [Column("created_at")] 41 | public DateTime CreatedAt { get; set; } 42 | } 43 | 44 | [Table("profile")] 45 | public class Profile : BaseModel 46 | { 47 | [Column("email")] 48 | public string Email { get; set; } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Examples/SupabaseExample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | using Supabase.Gotrue; 7 | using Supabase.Gotrue.Interfaces; 8 | using static Supabase.Realtime.PostgresChanges.PostgresChangesOptions; 9 | using Constants = Supabase.Realtime.Constants; 10 | 11 | namespace SupabaseExample 12 | { 13 | 14 | class Program 15 | { 16 | static async Task Main(string[] args) 17 | { 18 | // Be sure to set this in your Debug Options. 19 | var url = Environment.GetEnvironmentVariable("SUPABASE_URL"); 20 | var key = Environment.GetEnvironmentVariable("SUPABASE_KEY"); 21 | 22 | var supabase = new Supabase.Client(url, key, new Supabase.SupabaseOptions { AutoConnectRealtime = true }); 23 | await supabase.InitializeAsync(); 24 | 25 | var reference = supabase.From(); 26 | 27 | await reference.On(ListenType.All, 28 | (sender, response) => 29 | { 30 | switch (response.Event) 31 | { 32 | case Constants.EventType.Insert: 33 | break; 34 | case Constants.EventType.Update: 35 | break; 36 | case Constants.EventType.Delete: 37 | break; 38 | } 39 | Debug.WriteLine($"[{response.Event}]:{response.Topic}:{response.Payload.Data}"); 40 | }); 41 | 42 | var channels = await reference.Get(); 43 | 44 | //await reference.Insert(new Models.Channel { Slug = GenerateName(10), InsertedAt = DateTime.Now }); 45 | 46 | #region Storage 47 | 48 | var storage = supabase.Storage; 49 | 50 | var exists = await storage.GetBucket("testing") != null; 51 | if (!exists) 52 | await storage.CreateBucket("testing", new Supabase.Storage.BucketUpsertOptions { Public = true }); 53 | 54 | var buckets = await storage.ListBuckets(); 55 | 56 | foreach (var b in buckets) 57 | Debug.WriteLine($"[{b.Id}] {b.Name}"); 58 | 59 | var bucket = storage.From("testing"); 60 | var basePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase).Replace("file:", "") 61 | .Replace("C:\\", ""); 62 | var imagePath = Path.Combine(basePath, "Assets", "supabase-csharp.png"); 63 | 64 | Debug.WriteLine(await bucket.Upload(imagePath, "supabase-csharp.png", 65 | new Supabase.Storage.FileOptions { Upsert = true }, 66 | (sender, args) => Debug.WriteLine($"Upload Progress: {args}%"))); 67 | Debug.WriteLine(bucket.GetPublicUrl("supabase-csharp.png")); 68 | Debug.WriteLine(await bucket.CreateSignedUrl("supabase-csharp.png", 3600)); 69 | 70 | var bucketItems = await bucket.List(); 71 | 72 | foreach (var item in bucketItems) 73 | Debug.WriteLine($"[{item.Id}] {item.Name} - {item.CreatedAt}"); 74 | 75 | Debug.WriteLine(await bucket.Download("supabase-csharp.png", Path.Combine(basePath, "testing-download.png"), 76 | (sender, args) => Debug.WriteLine($"Download Progress: {args}%"))); 77 | 78 | await storage.EmptyBucket("testing"); 79 | await storage.DeleteBucket("testing"); 80 | 81 | 82 | #endregion 83 | } 84 | 85 | // From: https://stackoverflow.com/a/49922533/3629438 86 | static string GenerateName(int len) 87 | { 88 | Random r = new Random(); 89 | string[] consonants = 90 | { 91 | "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "l", "n", "p", "q", "r", "s", "sh", "zh", "t", "v", 92 | "w", "x" 93 | }; 94 | string[] vowels = { "a", "e", "i", "o", "u", "ae", "y" }; 95 | string Name = ""; 96 | Name += consonants[r.Next(consonants.Length)].ToUpper(); 97 | Name += vowels[r.Next(vowels.Length)]; 98 | int 99 | b = 2; //b tells how many times a new letter has been added. It's 2 right now because the first two letters are already in the name. 100 | while (b < len) 101 | { 102 | Name += consonants[r.Next(consonants.Length)]; 103 | b++; 104 | Name += vowels[r.Next(vowels.Length)]; 105 | b++; 106 | } 107 | 108 | return Name; 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /Examples/SupabaseExample/SupabaseExample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | 0.7.2 7 | 9 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Examples/SupabaseExample/db/00-schema.sql: -------------------------------------------------------------------------------- 1 | ALTER SYSTEM SET wal_level='logical'; 2 | ALTER SYSTEM SET max_wal_senders='10'; 3 | ALTER SYSTEM SET max_replication_slots='10'; 4 | 5 | -- Create the Replication publication 6 | CREATE PUBLICATION supabase_realtime FOR ALL TABLES; 7 | 8 | -- Create a second schema 9 | CREATE SCHEMA personal; 10 | 11 | -- USERS 12 | CREATE TYPE public.user_status AS ENUM ('ONLINE', 'OFFLINE'); 13 | CREATE TABLE public.users ( 14 | username text primary key, 15 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 16 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 17 | data jsonb DEFAULT null, 18 | age_range int4range DEFAULT null, 19 | status user_status DEFAULT 'ONLINE'::public.user_status, 20 | catchphrase tsvector DEFAULT null 21 | ); 22 | ALTER TABLE public.users REPLICA IDENTITY FULL; -- Send "previous data" to supabase 23 | COMMENT ON COLUMN public.users.data IS 'For unstructured data and prototyping.'; 24 | 25 | -- CHANNELS 26 | CREATE TABLE public.channels ( 27 | id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 28 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 29 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 30 | data jsonb DEFAULT null, 31 | slug text 32 | ); 33 | ALTER TABLE public.users REPLICA IDENTITY FULL; -- Send "previous data" to supabase 34 | COMMENT ON COLUMN public.channels.data IS 'For unstructured data and prototyping.'; 35 | 36 | -- MESSAGES 37 | CREATE TABLE public.messages ( 38 | id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 39 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 40 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 41 | data jsonb DEFAULT null, 42 | message text, 43 | username text REFERENCES users NOT NULL, 44 | channel_id bigint REFERENCES channels NOT NULL 45 | ); 46 | ALTER TABLE public.messages REPLICA IDENTITY FULL; -- Send "previous data" to supabase 47 | COMMENT ON COLUMN public.messages.data IS 'For unstructured data and prototyping.'; 48 | 49 | -- STORED FUNCTION 50 | CREATE FUNCTION public.get_status(name_param text) 51 | RETURNS user_status AS $$ 52 | SELECT status from users WHERE username=name_param; 53 | $$ LANGUAGE SQL IMMUTABLE; 54 | 55 | -- SECOND SCHEMA USERS 56 | CREATE TYPE personal.user_status AS ENUM ('ONLINE', 'OFFLINE'); 57 | CREATE TABLE personal.users( 58 | username text primary key, 59 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 60 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 61 | data jsonb DEFAULT null, 62 | age_range int4range DEFAULT null, 63 | status user_status DEFAULT 'ONLINE'::public.user_status 64 | ); 65 | 66 | -- SECOND SCHEMA STORED FUNCTION 67 | CREATE FUNCTION personal.get_status(name_param text) 68 | RETURNS user_status AS $$ 69 | SELECT status from users WHERE username=name_param; 70 | $$ LANGUAGE SQL IMMUTABLE; 71 | 72 | 73 | CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION postgres; 74 | -- auth.users definition 75 | CREATE TABLE auth.users ( 76 | instance_id uuid NULL, 77 | id uuid NOT NULL, 78 | aud varchar(255) NULL, 79 | "role" varchar(255) NULL, 80 | email varchar(255) NULL, 81 | encrypted_password varchar(255) NULL, 82 | confirmed_at timestamptz NULL, 83 | invited_at timestamptz NULL, 84 | confirmation_token varchar(255) NULL, 85 | confirmation_sent_at timestamptz NULL, 86 | recovery_token varchar(255) NULL, 87 | recovery_sent_at timestamptz NULL, 88 | email_change_token varchar(255) NULL, 89 | email_change varchar(255) NULL, 90 | email_change_sent_at timestamptz NULL, 91 | last_sign_in_at timestamptz NULL, 92 | raw_app_meta_data jsonb NULL, 93 | raw_user_meta_data jsonb NULL, 94 | is_super_admin bool NULL, 95 | created_at timestamptz NULL, 96 | updated_at timestamptz NULL, 97 | CONSTRAINT users_pkey PRIMARY KEY (id) 98 | ); 99 | CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, email); 100 | CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id); 101 | -- auth.refresh_tokens definition 102 | CREATE TABLE auth.refresh_tokens ( 103 | instance_id uuid NULL, 104 | id bigserial NOT NULL, 105 | "token" varchar(255) NULL, 106 | user_id varchar(255) NULL, 107 | revoked bool NULL, 108 | created_at timestamptz NULL, 109 | updated_at timestamptz NULL, 110 | CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id) 111 | ); 112 | CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id); 113 | CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id); 114 | CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree (token); 115 | -- auth.instances definition 116 | CREATE TABLE auth.instances ( 117 | id uuid NOT NULL, 118 | uuid uuid NULL, 119 | raw_base_config text NULL, 120 | created_at timestamptz NULL, 121 | updated_at timestamptz NULL, 122 | CONSTRAINT instances_pkey PRIMARY KEY (id) 123 | ); 124 | -- auth.audit_log_entries definition 125 | CREATE TABLE auth.audit_log_entries ( 126 | instance_id uuid NULL, 127 | id uuid NOT NULL, 128 | payload json NULL, 129 | created_at timestamptz NULL, 130 | CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id) 131 | ); 132 | CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id); 133 | -- auth.schema_migrations definition 134 | CREATE TABLE auth.schema_migrations ( 135 | "version" varchar(255) NOT NULL, 136 | CONSTRAINT schema_migrations_pkey PRIMARY KEY ("version") 137 | ); 138 | INSERT INTO auth.schema_migrations (version) 139 | VALUES ('20171026211738'), 140 | ('20171026211808'), 141 | ('20171026211834'), 142 | ('20180103212743'), 143 | ('20180108183307'), 144 | ('20180119214651'), 145 | ('20180125194653'); 146 | -- Gets the User ID from the request cookie 147 | create or replace function auth.uid() returns uuid as $$ 148 | select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid; 149 | $$ language sql stable; 150 | -- Gets the User ID from the request cookie 151 | create or replace function auth.role() returns text as $$ 152 | select nullif(current_setting('request.jwt.claim.role', true), '')::text; 153 | $$ language sql stable; 154 | GRANT ALL PRIVILEGES ON SCHEMA auth TO postgres; 155 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO postgres; 156 | GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO postgres; 157 | ALTER USER postgres SET search_path = "auth"; 158 | -------------------------------------------------------------------------------- /Examples/SupabaseExample/db/01-dummy-data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO 2 | public.users (username, status, age_range, catchphrase) 3 | VALUES 4 | ('supabot', 'ONLINE', '[1,2)'::int4range, 'fat cat'::tsvector), 5 | ('kiwicopple', 'OFFLINE', '[25,35)'::int4range, 'cat bat'::tsvector), 6 | ('awailas', 'ONLINE', '[25,35)'::int4range, 'bat rat'::tsvector), 7 | ('dragarcia', 'ONLINE', '[20,30)'::int4range, 'rat fat'::tsvector); 8 | 9 | INSERT INTO 10 | public.channels (slug) 11 | VALUES 12 | ('public'), 13 | ('random'); 14 | 15 | INSERT INTO 16 | public.messages (message, channel_id, username) 17 | VALUES 18 | ('Hello World 👋', 1, 'supabot'), 19 | ('Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.', 2, 'supabot'); 20 | 21 | INSERT INTO 22 | personal.users (username, status, age_range) 23 | VALUES 24 | ('supabot', 'ONLINE', '[1,2)'::int4range), 25 | ('kiwicopple', 'OFFLINE', '[25,35)'::int4range), 26 | ('awailas', 'ONLINE', '[25,35)'::int4range), 27 | ('dragarcia', 'ONLINE', '[20,30)'::int4range), 28 | ('leroyjenkins', 'ONLINE', '[20,40)'::int4range); -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExample.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F94175FC-DE2B-4DBC-9C79-2A97B8489412}" 5 | ProjectSection(SolutionItems) = preProject 6 | README.md = README.md 7 | EndProjectSection 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SupabaseExampleXA", "SupabaseExampleXA\SupabaseExampleXA.csproj", "{0F986F22-CF68-40E8-B193-45880B7C4D49}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SupabaseExampleXA.Android", "SupabaseExampleXA.Android\SupabaseExampleXA.Android.csproj", "{4C26C775-92CC-4EE4-A57B-51093C5DF5BF}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SupabaseExampleXA.iOS", "SupabaseExampleXA.iOS\SupabaseExampleXA.iOS.csproj", "{A99C9838-F680-4388-838F-60DC72CDD257}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Supabase", "..\..\Supabase\Supabase.csproj", "{313E0FFC-E478-4365-99FA-70B8BBC92A5D}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 22 | Release|iPhoneSimulator = Release|iPhoneSimulator 23 | Debug|iPhone = Debug|iPhone 24 | Release|iPhone = Release|iPhone 25 | EndGlobalSection 26 | GlobalSection(MonoDevelopProperties) = preSolution 27 | version = 0.1.0-prerelease 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 35 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 36 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 37 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 38 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|iPhone.ActiveCfg = Debug|Any CPU 39 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|iPhone.Build.0 = Debug|Any CPU 40 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|iPhone.ActiveCfg = Release|Any CPU 41 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|iPhone.Build.0 = Release|Any CPU 42 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 47 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 48 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 49 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 50 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|iPhone.ActiveCfg = Debug|Any CPU 51 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|iPhone.Build.0 = Debug|Any CPU 52 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|iPhone.ActiveCfg = Release|Any CPU 53 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|iPhone.Build.0 = Release|Any CPU 54 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 55 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 56 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator 57 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|Any CPU.Build.0 = Release|iPhoneSimulator 58 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 59 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 60 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 61 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 62 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|iPhone.ActiveCfg = Debug|iPhone 63 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|iPhone.Build.0 = Debug|iPhone 64 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|iPhone.ActiveCfg = Release|iPhone 65 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|iPhone.Build.0 = Release|iPhone 66 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 71 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 72 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 73 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 74 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|iPhone.ActiveCfg = Debug|Any CPU 75 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|iPhone.Build.0 = Debug|Any CPU 76 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|iPhone.ActiveCfg = Release|Any CPU 77 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|iPhone.Build.0 = Release|Any CPU 78 | EndGlobalSection 79 | EndGlobal 80 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Android.App; 4 | using Android.Content.PM; 5 | using Android.Runtime; 6 | using Android.Views; 7 | using Android.Widget; 8 | using Android.OS; 9 | 10 | namespace SupabaseExampleXA.Droid 11 | { 12 | [Activity(Label = "SupabaseExampleXA", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)] 13 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity 14 | { 15 | protected override void OnCreate(Bundle savedInstanceState) 16 | { 17 | TabLayoutResource = Resource.Layout.Tabbar; 18 | ToolbarResource = Resource.Layout.Toolbar; 19 | 20 | base.OnCreate(savedInstanceState); 21 | 22 | Xamarin.Essentials.Platform.Init(this, savedInstanceState); 23 | global::Xamarin.Forms.Forms.Init(this, savedInstanceState); 24 | LoadApplication(new App()); 25 | } 26 | public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults) 27 | { 28 | Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); 29 | 30 | base.OnRequestPermissionsResult(requestCode, permissions, grantResults); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using Android.App; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("SupabaseExampleXA.Android")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("SupabaseExampleXA.Android")] 14 | [assembly: AssemblyCopyright("Copyright © 2014")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: ComVisible(false)] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | [assembly: AssemblyVersion("1.0.0.0")] 26 | [assembly: AssemblyFileVersion("1.0.0.0")] 27 | 28 | // Add some common permissions, these can be removed if not needed 29 | [assembly: UsesPermission(Android.Manifest.Permission.Internet)] 30 | [assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)] 31 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.xml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable-hdpi/ 12 | icon.png 13 | 14 | drawable-ldpi/ 15 | icon.png 16 | 17 | drawable-mdpi/ 18 | icon.png 19 | 20 | layout/ 21 | main.xml 22 | 23 | values/ 24 | strings.xml 25 | 26 | In order to get the build system to recognize Android resources, set the build action to 27 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 28 | instead operate on resource IDs. When you compile an Android application that uses resources, 29 | the build system will package the resources for distribution and generate a class called 30 | "Resource" that contains the tokens for each one of the resources included. For example, 31 | for the above Resources layout, this is what the Resource class would expose: 32 | 33 | public class Resource { 34 | public class drawable { 35 | public const int icon = 0x123; 36 | } 37 | 38 | public class layout { 39 | public const int main = 0x456; 40 | } 41 | 42 | public class strings { 43 | public const int first_string = 0xabc; 44 | public const int second_string = 0xbcd; 45 | } 46 | } 47 | 48 | You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main 49 | to reference the layout/main.xml file, or Resource.strings.first_string to reference the first 50 | string in the dictionary file values/strings.xml. 51 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/layout/Tabbar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/layout/Toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-anydpi-v26/icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-anydpi-v26/icon_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-hdpi/icon.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-hdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-hdpi/launcher_foreground.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-mdpi/icon.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-mdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-mdpi/launcher_foreground.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xhdpi/icon.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxhdpi/icon.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxxhdpi/icon.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxxhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxxhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #3F51B5 5 | #303F9F 6 | #FF4081 7 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.Android/SupabaseExampleXA.Android.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF} 7 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | {c9e5eea5-ca05-42a1-839b-61506e0a37df} 9 | Library 10 | SupabaseExampleXA.Droid 11 | SupabaseExampleXA.Android 12 | True 13 | True 14 | Resources\Resource.designer.cs 15 | Resource 16 | Properties\AndroidManifest.xml 17 | Resources 18 | Assets 19 | v10.0 20 | true 21 | true 22 | Xamarin.Android.Net.AndroidClientHandler 23 | 24 | 25 | 0.1.0-prerelease 26 | 27 | 28 | true 29 | portable 30 | false 31 | bin\Debug 32 | DEBUG; 33 | prompt 34 | 4 35 | None 36 | 37 | 38 | true 39 | portable 40 | true 41 | bin\Release 42 | prompt 43 | 4 44 | true 45 | false 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | {0F986F22-CF68-40E8-B193-45880B7C4D49} 94 | SupabaseExampleXA 95 | 96 | 97 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D} 98 | Supabase 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace SupabaseExampleXA.iOS 9 | { 10 | // The UIApplicationDelegate for the application. This class is responsible for launching the 11 | // User Interface of the application, as well as listening (and optionally responding) to 12 | // application events from iOS. 13 | [Register("AppDelegate")] 14 | public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate 15 | { 16 | // 17 | // This method is invoked when the application has loaded and is ready to run. In this 18 | // method you should instantiate the window, load the UI into it and then make the window 19 | // visible. 20 | // 21 | // You have 17 seconds to return from this method, or iOS will terminate your application. 22 | // 23 | public override bool FinishedLaunching(UIApplication app, NSDictionary options) 24 | { 25 | global::Xamarin.Forms.Forms.Init(); 26 | LoadApplication(new App()); 27 | 28 | return base.FinishedLaunching(app, options); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "scale": "2x", 5 | "size": "20x20", 6 | "idiom": "iphone", 7 | "filename": "Icon40.png" 8 | }, 9 | { 10 | "scale": "3x", 11 | "size": "20x20", 12 | "idiom": "iphone", 13 | "filename": "Icon60.png" 14 | }, 15 | { 16 | "scale": "2x", 17 | "size": "29x29", 18 | "idiom": "iphone", 19 | "filename": "Icon58.png" 20 | }, 21 | { 22 | "scale": "3x", 23 | "size": "29x29", 24 | "idiom": "iphone", 25 | "filename": "Icon87.png" 26 | }, 27 | { 28 | "scale": "2x", 29 | "size": "40x40", 30 | "idiom": "iphone", 31 | "filename": "Icon80.png" 32 | }, 33 | { 34 | "scale": "3x", 35 | "size": "40x40", 36 | "idiom": "iphone", 37 | "filename": "Icon120.png" 38 | }, 39 | { 40 | "scale": "2x", 41 | "size": "60x60", 42 | "idiom": "iphone", 43 | "filename": "Icon120.png" 44 | }, 45 | { 46 | "scale": "3x", 47 | "size": "60x60", 48 | "idiom": "iphone", 49 | "filename": "Icon180.png" 50 | }, 51 | { 52 | "scale": "1x", 53 | "size": "20x20", 54 | "idiom": "ipad", 55 | "filename": "Icon20.png" 56 | }, 57 | { 58 | "scale": "2x", 59 | "size": "20x20", 60 | "idiom": "ipad", 61 | "filename": "Icon40.png" 62 | }, 63 | { 64 | "scale": "1x", 65 | "size": "29x29", 66 | "idiom": "ipad", 67 | "filename": "Icon29.png" 68 | }, 69 | { 70 | "scale": "2x", 71 | "size": "29x29", 72 | "idiom": "ipad", 73 | "filename": "Icon58.png" 74 | }, 75 | { 76 | "scale": "1x", 77 | "size": "40x40", 78 | "idiom": "ipad", 79 | "filename": "Icon40.png" 80 | }, 81 | { 82 | "scale": "2x", 83 | "size": "40x40", 84 | "idiom": "ipad", 85 | "filename": "Icon80.png" 86 | }, 87 | { 88 | "scale": "1x", 89 | "size": "76x76", 90 | "idiom": "ipad", 91 | "filename": "Icon76.png" 92 | }, 93 | { 94 | "scale": "2x", 95 | "size": "76x76", 96 | "idiom": "ipad", 97 | "filename": "Icon152.png" 98 | }, 99 | { 100 | "scale": "2x", 101 | "size": "83.5x83.5", 102 | "idiom": "ipad", 103 | "filename": "Icon167.png" 104 | }, 105 | { 106 | "scale": "1x", 107 | "size": "1024x1024", 108 | "idiom": "ios-marketing", 109 | "filename": "Icon1024.png" 110 | } 111 | ], 112 | "properties": {}, 113 | "info": { 114 | "version": 1, 115 | "author": "xcode" 116 | } 117 | } -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon20.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon29.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon76.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UISupportedInterfaceOrientations 11 | 12 | UIInterfaceOrientationPortrait 13 | UIInterfaceOrientationLandscapeLeft 14 | UIInterfaceOrientationLandscapeRight 15 | 16 | UISupportedInterfaceOrientations~ipad 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationPortraitUpsideDown 20 | UIInterfaceOrientationLandscapeLeft 21 | UIInterfaceOrientationLandscapeRight 22 | 23 | MinimumOSVersion 24 | 8.0 25 | CFBundleDisplayName 26 | SupabaseExampleXA 27 | CFBundleIdentifier 28 | io.supabase.SupabaseExample 29 | CFBundleVersion 30 | 1.0 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | CFBundleName 34 | SupabaseExampleXA 35 | XSAppIconAssets 36 | Assets.xcassets/AppIcon.appiconset 37 | 38 | 39 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace SupabaseExampleXA.iOS 9 | { 10 | public class Application 11 | { 12 | // This is the main entry point of the application. 13 | static void Main(string[] args) 14 | { 15 | // if you want to use a different Application Delegate class from "AppDelegate" 16 | // you can specify it here. 17 | UIApplication.Main(args, null, "AppDelegate"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SupabaseExampleXA.iOS")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SupabaseExampleXA.iOS")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("72bdc44f-c588-44f3-b6df-9aace7daafdd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-568h@2x.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-Portrait.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-Portrait@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-Portrait@2x.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default@2x.png -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA.iOS/SupabaseExampleXA.iOS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | 8.0.30703 7 | 2.0 8 | {A99C9838-F680-4388-838F-60DC72CDD257} 9 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | {6143fdea-f3c2-4a09-aafa-6e230626515e} 11 | Exe 12 | SupabaseExampleXA.iOS 13 | Resources 14 | SupabaseExampleXA.iOS 15 | true 16 | NSUrlSessionHandler 17 | automatic 18 | 0.1.0-prerelease 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\iPhoneSimulator\Debug 25 | DEBUG 26 | prompt 27 | 4 28 | x86_64 29 | None 30 | true 31 | HttpClientHandler 32 | iPhone Developer 33 | 34 | 35 | none 36 | true 37 | bin\iPhoneSimulator\Release 38 | prompt 39 | 4 40 | None 41 | x86_64 42 | 43 | 44 | true 45 | full 46 | false 47 | bin\iPhone\Debug 48 | DEBUG 49 | prompt 50 | 4 51 | ARM64 52 | iPhone Developer 53 | true 54 | Entitlements.plist 55 | None 56 | -all 57 | 58 | 59 | none 60 | true 61 | bin\iPhone\Release 62 | prompt 63 | 4 64 | ARM64 65 | iPhone Developer 66 | Entitlements.plist 67 | SdkOnly 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | false 86 | 87 | 88 | false 89 | 90 | 91 | false 92 | 93 | 94 | false 95 | 96 | 97 | false 98 | 99 | 100 | false 101 | 102 | 103 | false 104 | 105 | 106 | false 107 | 108 | 109 | false 110 | 111 | 112 | false 113 | 114 | 115 | false 116 | 117 | 118 | false 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | {0F986F22-CF68-40E8-B193-45880B7C4D49} 136 | SupabaseExampleXA 137 | 138 | 139 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D} 140 | Supabase 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/App.xaml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json; 6 | using Supabase; 7 | using Supabase.Gotrue; 8 | using Xamarin.Essentials; 9 | using Xamarin.Forms; 10 | using Xamarin.Forms.Xaml; 11 | using Client = Supabase.Client; 12 | 13 | namespace SupabaseExampleXA 14 | { 15 | public partial class App : Application 16 | { 17 | private string supabaseCacheFilename = ".supabase.cache"; 18 | 19 | public App() 20 | { 21 | InitializeComponent(); 22 | 23 | MainPage = new LoadingPage(); 24 | } 25 | 26 | protected override void OnStart() 27 | { 28 | InitSupabase(); 29 | } 30 | 31 | protected override void OnSleep() 32 | { 33 | } 34 | 35 | protected override void OnResume() 36 | { 37 | } 38 | 39 | async void InitSupabase() 40 | { 41 | var options = new SupabaseOptions 42 | { 43 | SessionPersistor = SessionPersistor, 44 | SessionRetriever = SessionRetriever, 45 | SessionDestroyer = SessionDestroyer 46 | }; 47 | 48 | await Client.Initialize(Environment.GetEnvironmentVariable("supabaseUrl"), Environment.GetEnvironmentVariable("supabaseKey"), options); 49 | 50 | if (Client.Instance.Auth.CurrentSession == null) 51 | { 52 | await Client.Instance.Auth.SignIn(Environment.GetEnvironmentVariable("email"), Environment.GetEnvironmentVariable("password")); 53 | } 54 | 55 | await Client.Instance.Realtime.Connect(); 56 | 57 | MainPage = new NavigationPage(new ChannelListPage()); 58 | } 59 | 60 | internal Task SessionPersistor(Session session) 61 | { 62 | try 63 | { 64 | var cacheDir = FileSystem.CacheDirectory; 65 | var path = Path.Join(cacheDir, supabaseCacheFilename); 66 | var str = JsonConvert.SerializeObject(session); 67 | 68 | using (StreamWriter file = new StreamWriter(path)) 69 | { 70 | file.Write(str); 71 | file.Dispose(); 72 | return Task.FromResult(true); 73 | 74 | }; 75 | } 76 | catch (Exception err) 77 | { 78 | Debug.WriteLine("Unable to write cache file."); 79 | throw err; 80 | } 81 | } 82 | 83 | internal Task SessionRetriever() 84 | { 85 | var tsc = new TaskCompletionSource(); 86 | try 87 | { 88 | var cacheDir = FileSystem.CacheDirectory; 89 | var path = Path.Join(cacheDir, supabaseCacheFilename); 90 | 91 | if (File.Exists(path)) 92 | { 93 | using (StreamReader file = new StreamReader(path)) 94 | { 95 | var str = file.ReadToEnd(); 96 | if (!String.IsNullOrEmpty(str)) 97 | tsc.SetResult(JsonConvert.DeserializeObject(str)); 98 | else 99 | tsc.SetResult(null); 100 | file.Dispose(); 101 | }; 102 | } 103 | else 104 | { 105 | tsc.SetResult(null); 106 | } 107 | } 108 | catch 109 | { 110 | Debug.WriteLine("Unable to read cache file."); 111 | tsc.SetResult(null); 112 | } 113 | return tsc.Task; 114 | 115 | } 116 | 117 | internal Task SessionDestroyer() 118 | { 119 | try 120 | { 121 | var cacheDir = FileSystem.CacheDirectory; 122 | var path = Path.Join(cacheDir, supabaseCacheFilename); 123 | if (File.Exists(path)) 124 | File.Delete(path); 125 | return Task.FromResult(true); 126 | } 127 | catch (Exception err) 128 | { 129 | Debug.WriteLine("Unable to delete cache file."); 130 | return Task.FromResult(false); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms.Xaml; 2 | 3 | [assembly: XamlCompilation(XamlCompilationOptions.Compile)] -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/ChannelListPage.xaml: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/ChannelListPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using SupabaseExampleXA.Models; 8 | using Xamarin.Forms; 9 | 10 | namespace SupabaseExampleXA 11 | { 12 | public partial class ChannelListPage : ContentPage 13 | { 14 | public ChannelListPage() 15 | { 16 | InitializeComponent(); 17 | 18 | ChannelList.ItemSelected += ChannelList_ItemSelected; 19 | } 20 | 21 | private void ChannelList_ItemSelected(object sender, SelectedItemChangedEventArgs e) 22 | { 23 | if (ChannelList.SelectedItem == null) return; 24 | 25 | Navigation.PushAsync(new MessageListPage(ChannelList.SelectedItem as Channel)); 26 | ChannelList.SelectedItem = null; 27 | } 28 | 29 | protected override void OnAppearing() 30 | { 31 | base.OnAppearing(); 32 | Device.BeginInvokeOnMainThread(async () => await Refresh()); 33 | 34 | Supabase.Client.Instance.Auth.StateChanged += async (object sender, Supabase.Gotrue.ClientStateChanged e) => 35 | { 36 | if (e.State == Supabase.Gotrue.Client.AuthState.SignedIn) 37 | { 38 | await Refresh(); 39 | } 40 | }; 41 | } 42 | 43 | private async Task Refresh() 44 | { 45 | var channels = await Supabase.Client.Instance.From().Get(); 46 | ChannelList.ItemsSource = channels?.Models; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/LoadingPage.xaml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/LoadingPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using Xamarin.Forms; 5 | 6 | namespace SupabaseExampleXA 7 | { 8 | public partial class LoadingPage : ContentPage 9 | { 10 | public LoadingPage() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/MessageListPage.xaml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/MessageListPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using Supabase.Realtime; 8 | using SupabaseExampleXA.Models; 9 | using Xamarin.Forms; 10 | using static Supabase.Client; 11 | 12 | namespace SupabaseExampleXA 13 | { 14 | public partial class MessageListPage : ContentPage 15 | { 16 | Models.Channel channel; 17 | Supabase.Realtime.Channel subscription; 18 | ObservableCollection messages { get; set; } = new ObservableCollection(); 19 | 20 | public MessageListPage(Models.Channel channel) 21 | { 22 | InitializeComponent(); 23 | 24 | this.channel = channel; 25 | Title = channel.Slug; 26 | 27 | MessageList.ItemsSource = messages; 28 | MessageEditor.Completed += MessageEditor_Completed; 29 | 30 | Init(); 31 | } 32 | 33 | public async void Init() 34 | { 35 | var query = await Instance.From().Filter("channel_id", Postgrest.Constants.Operator.Equals, channel.Id).Get(); 36 | 37 | foreach (var model in query.Models) 38 | messages.Add(model); 39 | 40 | subscription = await Instance.From().On(ChannelEventType.All, OnSubscriptionEvent); 41 | } 42 | 43 | private async void MessageEditor_Completed(object sender, EventArgs e) 44 | { 45 | await Instance.From().Insert(new Message 46 | { 47 | Text = MessageEditor.Text, 48 | UserId = Instance.Auth.CurrentUser.Id, 49 | ChannelId = channel.Id 50 | }); 51 | MessageEditor.Text = null; 52 | } 53 | 54 | protected override void OnDisappearing() 55 | { 56 | base.OnDisappearing(); 57 | 58 | if (subscription != null) 59 | subscription.Unsubscribe(); 60 | } 61 | 62 | 63 | 64 | private void OnSubscriptionEvent(object sender, SocketResponseEventArgs args) 65 | { 66 | switch (args.Response.Event) 67 | { 68 | case Constants.EventType.Insert: 69 | var str = JsonConvert.SerializeObject(args.Response.Payload.Record); 70 | var message = args.Response.Model(); 71 | messages.Add(message); 72 | break; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/Models/Channel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Postgrest.Attributes; 3 | using Postgrest.Models; 4 | 5 | namespace SupabaseExampleXA.Models 6 | { 7 | [Table("channels")] 8 | public class Channel : BaseModel 9 | { 10 | [PrimaryKey("id", false)] // Key is Autogenerated 11 | public int Id { get; set; } 12 | 13 | [Column("inserted_at")] 14 | public DateTime InsertedAt { get; set; } 15 | 16 | [Column("slug")] 17 | public string Slug { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/Models/Message.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Postgrest.Attributes; 3 | using Postgrest.Models; 4 | 5 | namespace SupabaseExampleXA.Models 6 | { 7 | [Table("messages")] 8 | public class Message : BaseModel 9 | { 10 | [PrimaryKey("id", false)] 11 | public int Id { get; set; } 12 | 13 | [Column("inserted_at")] 14 | public DateTime InsertedAt { get; set; } = new DateTime(); 15 | 16 | [Column("message")] 17 | public string Text { get; set; } 18 | 19 | [Column("user_id")] 20 | public string UserId { get; set; } 21 | 22 | [Column("channel_id")] 23 | public int ChannelId { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Postgrest.Attributes; 3 | using Postgrest.Models; 4 | 5 | namespace SupabaseExampleXA.Models 6 | { 7 | [Table("users")] 8 | public class User : BaseModel 9 | { 10 | [PrimaryKey("id")] 11 | public string Id { get; set; } 12 | 13 | [Column("username")] 14 | public string Username { get; set; } 15 | 16 | [Column("status")] 17 | public string Status { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/Xamarin.Forms/SupabaseExampleXA/SupabaseExampleXA.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | true 6 | 0.1.0-prerelease 7 | 8 | 9 | 10 | portable 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joseph Schultz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | 7 | 8 | 9 |

10 | 11 | Documentation can be found [below](#getting-started), on 12 | the [Supabase Developer Documentation](https://supabase.com/docs/reference/csharp/introduction) and additionally in 13 | the [Generated API Docs](https://supabase-community.github.io/supabase-csharp/api/Supabase.Client.html). 14 | 15 | [**CHANGELOG is available in the repository root. 16 | **](https://github.com/supabase-community/supabase-csharp/blob/master/CHANGELOG.md) 17 | 18 | ## [NOTICE FOR v1.0.0] 19 | 20 | - The `supabase-csharp` Nuget package has been renamed to `Supabase` and a depreciation notice set to encourage 21 | adoption. 22 | - Almost all APIs stay the same when migrating from v0.16.x _except_ the change in namespace from `Postgrest` 23 | to `Supabase.Postgrest`. Some minor refactoring will be required in the codebase. 24 | - The assembly name has been changed from `supabase` to `Supabase`. 25 | 26 | ## Features 27 | 28 | - [x] Integration with [Supabase.Realtime](https://github.com/supabase-community/realtime-csharp) 29 | - Realtime listeners for database changes 30 | - [x] Integration with [Postgrest](https://github.com/supabase-community/postgrest-csharp) 31 | - Access your database using a REST API generated from your schema & database functions 32 | - [x] Integration with [Gotrue](https://github.com/supabase-community/gotrue-csharp) 33 | - User authentication, including OAuth, email/password, and native sign-in 34 | - [x] Integration with [Supabase Storage](https://github.com/supabase-community/storage-csharp) 35 | - Store files in S3 with additional managed metadata 36 | - [x] Integration with [Supabase Edge Functions](https://github.com/supabase-community/functions-csharp) 37 | - Run serverless functions on the edge 38 | - [x] [Nuget Release](https://www.nuget.org/packages/supabase-csharp) 39 | 40 | ## Quickstart 41 | 42 | 1. To get started, create a new project in the [Supabase Admin Panel](https://app.supabase.io). 43 | 2. Grab your Supabase URL and Supabase Public Key from the Admin Panel (Settings -> API Keys). 44 | 3. Initialize the client! 45 | 46 | _Reminder: `supabase-csharp` has some APIs that require the `service_key` rather than the `public_key` (for instance: 47 | the administration of users, bypassing database roles, etc.). If you are using 48 | the `service_key` **be sure it is not exposed client side.** Additionally, if you need to use both a service account and 49 | a public/user account, please do so using a separate client instance for each._ 50 | 51 | ## Documentation 52 | 53 | - [Getting Started](https://github.com/supabase-community/supabase-csharp/wiki#getting-started) 54 | - [Unity](https://github.com/supabase-community/supabase-csharp/wiki/Unity) 55 | - [Desktop/Mobile Clients (e.g. Xamarin, MAUI, etc.)](https://github.com/supabase-community/supabase-csharp/wiki/Desktop-Clients) 56 | - [Server-Side Applications](https://github.com/supabase-community/supabase-csharp/wiki/Server-Side-Applications) 57 | - [Release Notes/Breaking Changes](https://github.com/supabase-community/supabase-csharp/wiki/Release-Notes) 58 | - [Using the Client](https://github.com/supabase-community/supabase-csharp/wiki#using-the-client) 59 | - [Examples](https://github.com/supabase-community/supabase-csharp/wiki/Examples) 60 | 61 | ### Specific Features 62 | 63 | - [Offline Support](https://github.com/supabase-community/supabase-csharp/wiki/Authorization-with-Gotrue#offline-support) 64 | - [Refresh Token Thread](https://github.com/supabase-community/supabase-csharp/wiki/Authorization-with-Gotrue#updated-refresh-token-handling) 65 | - [Native Sign in with Apple]([Documentation/NativeSignInWithApple.md](https://github.com/supabase-community/supabase-csharp/wiki/Authorization-with-Gotrue#native-sign-in-with-apple)) 66 | 67 | ### Troubleshooting 68 | 69 | - [Troubleshooting](https://github.com/supabase-community/supabase-csharp/wiki/Troubleshooting) 70 | - [Discussion Forum](https://github.com/supabase-community/supabase-csharp/discussions) 71 | 72 | ## Package made possible through the efforts of: 73 | 74 | 75 | 76 | 77 | 78 | Join the ranks! See a problem? Help fix it! 79 | 80 | ## Contributing 81 | 82 | We are more than happy to have contributions! Please submit a PR. 83 | -------------------------------------------------------------------------------- /Supabase.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.2.32519.379 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F94175FC-DE2B-4DBC-9C79-2A97B8489412}" 6 | ProjectSection(SolutionItems) = preProject 7 | .env = .env 8 | .env.sample = .env.sample 9 | .gitignore = .gitignore 10 | CHANGELOG.md = CHANGELOG.md 11 | docker-compose.yml = docker-compose.yml 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Supabase", "Supabase\Supabase.csproj", "{FAE80407-C121-47A3-9304-D39FA828E9F1}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SupabaseTests", "SupabaseTests\SupabaseTests.csproj", "{28EE4F80-74AA-46F6-B15E-27C30310401A}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{43FFFE0C-91D2-43AB-913F-B3824702AE49}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{5B805377-7615-441C-86B1-4BE203B0289B}" 22 | ProjectSection(SolutionItems) = preProject 23 | .github\workflows\build-documentation.yaml = .github\workflows\build-documentation.yaml 24 | .github\workflows\release.yml = .github\workflows\release.yml 25 | .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml 26 | EndProjectSection 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{E407C761-AA9C-423C-AD1C-7EE687D3CAB9}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SupabaseExample", "Examples\SupabaseExample\SupabaseExample.csproj", "{F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}" 31 | EndProject 32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{F7E6F0F9-19B4-403B-A5C8-36D43CDC6B5F}" 33 | EndProject 34 | Global 35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 36 | Debug|Any CPU = Debug|Any CPU 37 | Release|Any CPU = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 40 | {FAE80407-C121-47A3-9304-D39FA828E9F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {FAE80407-C121-47A3-9304-D39FA828E9F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {FAE80407-C121-47A3-9304-D39FA828E9F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {FAE80407-C121-47A3-9304-D39FA828E9F1}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {28EE4F80-74AA-46F6-B15E-27C30310401A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {28EE4F80-74AA-46F6-B15E-27C30310401A}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {28EE4F80-74AA-46F6-B15E-27C30310401A}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {28EE4F80-74AA-46F6-B15E-27C30310401A}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}.Release|Any CPU.Build.0 = Release|Any CPU 52 | EndGlobalSection 53 | GlobalSection(SolutionProperties) = preSolution 54 | HideSolutionNode = FALSE 55 | EndGlobalSection 56 | GlobalSection(NestedProjects) = preSolution 57 | {43FFFE0C-91D2-43AB-913F-B3824702AE49} = {F94175FC-DE2B-4DBC-9C79-2A97B8489412} 58 | {5B805377-7615-441C-86B1-4BE203B0289B} = {43FFFE0C-91D2-43AB-913F-B3824702AE49} 59 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D} = {F7E6F0F9-19B4-403B-A5C8-36D43CDC6B5F} 60 | EndGlobalSection 61 | GlobalSection(ExtensibilityGlobals) = postSolution 62 | SolutionGuid = {832DE89D-7252-4B03-9301-BB8D36B40992} 63 | EndGlobalSection 64 | GlobalSection(MonoDevelopProperties) = preSolution 65 | version = 0.3.4 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /Supabase/DefaultSupabaseSessionHandler.cs: -------------------------------------------------------------------------------- 1 | using Supabase.Gotrue; 2 | using Supabase.Gotrue.Interfaces; 3 | 4 | namespace Supabase 5 | { 6 | /// 7 | /// Represents the default session handler for Gotrue - it does nothing by default. 8 | /// 9 | public class DefaultSupabaseSessionHandler : IGotrueSessionPersistence 10 | { 11 | /// 12 | /// Default Session Save (does nothing by default) 13 | /// 14 | /// 15 | public void SaveSession(Session session) 16 | { 17 | } 18 | 19 | /// 20 | /// Default Session Destroyer (does nothing by default) 21 | /// 22 | public void DestroySession() 23 | { 24 | } 25 | 26 | /// 27 | /// Default Session Loader (does nothing by default) 28 | /// 29 | /// 30 | public Session? LoadSession() 31 | { 32 | return null; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Supabase/Extensions/DictionaryExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Supabase.Extensions 5 | { 6 | internal static class DictionaryExtensions 7 | { 8 | // Works in C#3/VS2008: 9 | // Returns a new dictionary of this ... others merged leftward. 10 | // Keeps the type of 'this', which must be default-instantiable. 11 | // Example: 12 | // result = map.MergeLeft(other1, other2, ...) 13 | // From: https://stackoverflow.com/a/2679857/3629438 14 | public static T MergeLeft(this T me, params IDictionary[] others) 15 | where T : IDictionary, new() 16 | { 17 | T newMap = new T(); 18 | foreach (IDictionary src in (new List> { me }).Concat(others)) 19 | { 20 | foreach (KeyValuePair p in src) 21 | { 22 | newMap[p.Key] = p.Value; 23 | } 24 | } 25 | return newMap; 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Supabase/Interfaces/ISupabaseClient.cs: -------------------------------------------------------------------------------- 1 | using Supabase.Postgrest.Interfaces; 2 | using Supabase.Postgrest.Models; 3 | using Supabase.Postgrest.Responses; 4 | using Supabase.Functions.Interfaces; 5 | using Supabase.Gotrue; 6 | using Supabase.Gotrue.Interfaces; 7 | using Supabase.Realtime.Interfaces; 8 | using Supabase.Storage; 9 | using Supabase.Storage.Interfaces; 10 | using System.Threading.Tasks; 11 | 12 | namespace Supabase.Interfaces 13 | { 14 | /// 15 | /// Contract for what a SupabaseClient should implement 16 | /// 17 | /// Model representing User 18 | /// Model representing Session 19 | /// Class that conforms to 20 | /// Class that conforms to 21 | /// Model representing 22 | /// Model representing 23 | public interface ISupabaseClient 24 | where TUser : User 25 | where TSession : Session 26 | where TSocket : IRealtimeSocket 27 | where TChannel : IRealtimeChannel 28 | where TBucket : Bucket 29 | where TFileObject : FileObject 30 | { 31 | /// 32 | /// The Gotrue Auth Instance 33 | /// 34 | IGotrueClient Auth { get; set; } 35 | 36 | /// 37 | /// Creates a Gotrue Admin Auth Client 38 | /// 39 | /// 40 | /// 41 | IGotrueAdminClient AdminAuth(string serviceKey); 42 | 43 | /// 44 | /// The Supabase Functions Client 45 | /// 46 | IFunctionsClient Functions { get; set; } 47 | 48 | /// 49 | /// The Postgrest Client 50 | /// 51 | IPostgrestClient Postgrest { get; set; } 52 | 53 | /// 54 | /// The Realtime Client 55 | /// 56 | IRealtimeClient Realtime { get; set; } 57 | 58 | /// 59 | /// The Storage Client 60 | /// 61 | IStorageClient Storage { get; set; } 62 | 63 | /// 64 | /// Used for interacting with a Postgrest Table + Model. Provides helpers 65 | /// to be able to add realtime listeners and queries. 66 | /// 67 | /// 68 | /// 69 | ISupabaseTable From() where TModel : BaseModel, new(); 70 | 71 | 72 | /// 73 | /// Initializes a supabase client according to the provided . 74 | /// If option is enabled: 75 | /// - Will connect to realtime instance 76 | /// - Will restore session using a specified in 77 | /// 78 | /// 79 | Task> InitializeAsync(); 80 | 81 | /// 82 | /// Perform a stored procedure call. 83 | /// 84 | /// The function name to call 85 | /// The parameters to pass to the function call 86 | /// 87 | Task Rpc(string procedureName, object? parameters); 88 | 89 | /// 90 | /// Perform a stored procedure call. 91 | /// 92 | /// The function name to call 93 | /// The parameters to pass to the function call 94 | /// A type used for hydrating the HTTP response content (hydration through JSON.NET) 95 | /// A hydrated model 96 | Task Rpc(string procedureName, object? parameters); 97 | } 98 | } -------------------------------------------------------------------------------- /Supabase/Interfaces/ISupabaseFunctions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Newtonsoft.Json; 5 | 6 | namespace Supabase.Interfaces 7 | { 8 | /// 9 | /// Contract representing a wrapper client. 10 | /// 11 | public interface ISupabaseFunctions 12 | { 13 | /// 14 | /// Invoke a supabase function 15 | /// 16 | /// 17 | /// 18 | /// String content from invoke 19 | Task Invoke(string functionName, Dictionary? body = null); 20 | 21 | /// 22 | /// Invoke a supabase function and deserialize data to a provided model. 23 | /// 24 | /// 25 | /// 26 | /// Model representing data that is compatible with 27 | /// The deserialized Model 28 | Task Invoke(string functionName, Dictionary? body = null) where T : class; 29 | 30 | /// 31 | /// Invoke a supabase function and return the for the developer to parse. 32 | /// 33 | /// 34 | /// 35 | /// The HTTP Content 36 | Task RawInvoke(string functionName, Dictionary? body = null); 37 | } 38 | } -------------------------------------------------------------------------------- /Supabase/Interfaces/ISupabaseTable.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Supabase.Realtime.Interfaces; 3 | using Supabase.Postgrest.Interfaces; 4 | using Supabase.Postgrest.Models; 5 | using static Supabase.Realtime.PostgresChanges.PostgresChangesOptions; 6 | 7 | namespace Supabase.Interfaces 8 | { 9 | /// 10 | /// Contract representing a supabase wrapped postgrest 11 | /// 12 | /// Model that inherits from that represents this Table 13 | /// Class that implements 14 | public interface ISupabaseTable : IPostgrestTable 15 | where TModel : BaseModel, new() 16 | where TChannel : IRealtimeChannel 17 | { 18 | /// 19 | /// Add a realtime listener to this table. 20 | /// 21 | /// 22 | /// 23 | /// 24 | Task On(ListenType listenType, IRealtimeChannel.PostgresChangesHandler handler); 25 | } 26 | } -------------------------------------------------------------------------------- /Supabase/Supabase.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9.0 5 | enable 6 | CS8600;CS8602;CS8603 7 | true 8 | Supabase 9 | Supabase 10 | Supabase 11 | Supabase 12 | Joseph Schultz <joseph@acupofjose.com> 13 | A C# implementation of the Supabase client 14 | MIT 15 | en 16 | true 17 | MIT 18 | Joseph Schultz <joseph@acupofjose.com> 19 | https://github.com/supabase-community/supabase-csharp 20 | A C# implementation of the Supabase client 21 | supabase 22 | 1.1.1 23 | 1.1.1 24 | true 25 | icon.png 26 | README.md 27 | https://github.com/supabase-community/supabase-csharp 28 | true 29 | snupkg 30 | true 31 | netstandard2.1 32 | 33 | 34 | 35 | 1.1.1 36 | $(VersionPrefix) 37 | 38 | 39 | true 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Supabase/SupabaseModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Supabase.Postgrest.Models; 3 | 4 | namespace Supabase 5 | { 6 | /// 7 | /// Depreciated 8 | /// 9 | [Obsolete] 10 | public abstract class SupabaseModel : BaseModel 11 | {} 12 | } 13 | -------------------------------------------------------------------------------- /Supabase/SupabaseOptions.cs: -------------------------------------------------------------------------------- 1 | using Supabase.Gotrue; 2 | using System.Collections.Generic; 3 | using Supabase.Gotrue.Interfaces; 4 | 5 | namespace Supabase 6 | { 7 | /// 8 | /// Options available for Supabase Client Configuration 9 | /// 10 | public class SupabaseOptions 11 | { 12 | /// 13 | /// Schema to be used in Postgres / Realtime 14 | /// 15 | public string Schema = "public"; 16 | 17 | /// 18 | /// Should the Client automatically handle refreshing the User's Token? 19 | /// 20 | public bool AutoRefreshToken { get; set; } = true; 21 | 22 | /// 23 | /// Should the Client automatically connect to Realtime? 24 | /// 25 | public bool AutoConnectRealtime { get; set; } 26 | 27 | /// 28 | /// Functions passed to Gotrue that handle sessions. 29 | /// 30 | /// **By default these do nothing for persistence.** 31 | /// 32 | public IGotrueSessionPersistence SessionHandler { get; set; } = new DefaultSupabaseSessionHandler(); 33 | 34 | /// 35 | /// Allows developer to specify options that will be passed to all child Supabase clients. 36 | /// 37 | public Dictionary Headers = new(); 38 | 39 | /// 40 | /// Specifies Options passed to the StorageClient. 41 | /// 42 | public Storage.ClientOptions StorageClientOptions { get; set; } = new(); 43 | 44 | /// 45 | /// The Supabase Auth Url Format 46 | /// 47 | public string AuthUrlFormat { get; set; } = "{0}/auth/v1"; 48 | 49 | /// 50 | /// The Supabase Postgrest Url Format 51 | /// 52 | public string RestUrlFormat { get; set; } = "{0}/rest/v1"; 53 | 54 | /// 55 | /// The Supabase Realtime Url Format 56 | /// 57 | public string RealtimeUrlFormat { get; set; } = "{0}/realtime/v1"; 58 | 59 | /// 60 | /// The Supabase Storage Url Format 61 | /// 62 | public string StorageUrlFormat { get; set; } = "{0}/storage/v1"; 63 | 64 | /// 65 | /// The Supabase Functions Url Format 66 | /// 67 | public string FunctionsUrlFormat { get; set; } = "{0}/functions/v1"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Supabase/SupabaseTable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Supabase.Postgrest; 4 | using Supabase.Postgrest.Interfaces; 5 | using Supabase.Postgrest.Models; 6 | using Supabase.Interfaces; 7 | using Supabase.Realtime; 8 | using Supabase.Realtime.Interfaces; 9 | using static Supabase.Realtime.PostgresChanges.PostgresChangesOptions; 10 | 11 | namespace Supabase 12 | { 13 | /// 14 | /// A Supabase wrapper for a Postgrest Table. 15 | /// 16 | /// Model that implements 17 | public class SupabaseTable : Table, ISupabaseTable 18 | where TModel : BaseModel, new() 19 | { 20 | private RealtimeChannel? _channel; 21 | private readonly IPostgrestClient _postgrestClient; 22 | private readonly IRealtimeClient _realtimeClient; 23 | private readonly string _schema; 24 | 25 | /// 26 | /// A Supabase wrapper for a Postgrest table. 27 | /// 28 | /// 29 | /// 30 | /// 31 | public SupabaseTable(IPostgrestClient postgrestClient, 32 | IRealtimeClient realtimeClient, string schema = "public") : base( 33 | postgrestClient.BaseUrl, Postgrest.Client.SerializerSettings(postgrestClient.Options), 34 | postgrestClient.Options) 35 | { 36 | _postgrestClient = postgrestClient; 37 | _realtimeClient = realtimeClient; 38 | _schema = schema; 39 | GetHeaders = postgrestClient.GetHeaders; 40 | } 41 | 42 | /// 43 | public async Task On(ListenType listenType, IRealtimeChannel.PostgresChangesHandler handler) 44 | { 45 | if (_channel == null) 46 | { 47 | var parameters = new Dictionary(); 48 | 49 | // In regard to: https://github.com/supabase/supabase-js/pull/270 50 | var headers = _postgrestClient?.GetHeaders?.Invoke(); 51 | if (headers != null && headers.TryGetValue("Authorization", out var header)) 52 | { 53 | parameters.Add("user_token", header.Split(' ')[1]); 54 | } 55 | 56 | _channel = _realtimeClient.Channel("realtime", _schema, TableName, parameters: parameters); 57 | } 58 | 59 | if (_realtimeClient.Socket == null || !_realtimeClient.Socket.IsConnected) 60 | await _realtimeClient.ConnectAsync(); 61 | 62 | _channel.AddPostgresChangeHandler(listenType, handler); 63 | 64 | await _channel.Subscribe(); 65 | return _channel; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /SupabaseTests/Assets/supabase-csharp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/SupabaseTests/Assets/supabase-csharp.png -------------------------------------------------------------------------------- /SupabaseTests/Client.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Supabase.Postgrest; 6 | using SupabaseTests.Stubs; 7 | 8 | namespace SupabaseTests 9 | { 10 | [TestClass] 11 | public class Client 12 | { 13 | private static readonly Random Random = new(); 14 | 15 | private Supabase.Client _instance; 16 | 17 | private static string RandomString(int length) 18 | { 19 | const string chars = "abcdefghijklmnopqrstuvwxyz0123456789"; 20 | return new string(Enumerable.Repeat(chars, length) 21 | .Select(s => s[Random.Next(s.Length)]).ToArray()); 22 | } 23 | 24 | [TestInitialize] 25 | public async Task InitializeTest() 26 | { 27 | _instance = new Supabase.Client("http://localhost", null, new Supabase.SupabaseOptions 28 | { 29 | AuthUrlFormat = "{0}:9999", 30 | RealtimeUrlFormat = "ws://realtime-dev.localhost:4000/socket", 31 | RestUrlFormat = "{0}:3000", 32 | AutoConnectRealtime = false, 33 | }); 34 | await _instance.InitializeAsync(); 35 | } 36 | 37 | [TestMethod("Client: Initializes.")] 38 | public void ClientInitializes() 39 | { 40 | Assert.IsNotNull(_instance.Realtime); 41 | Assert.IsNotNull(_instance.Auth); 42 | } 43 | 44 | [TestMethod("SupabaseModel: Successfully Updates")] 45 | public async Task SupabaseModelUpdates() 46 | { 47 | var model = new Models.Channel { Slug = Guid.NewGuid().ToString() }; 48 | var insertResult = await _instance.From().Insert(model); 49 | var newChannel = insertResult.Models.FirstOrDefault(); 50 | 51 | var newSlug = $"Updated Slug @ {DateTime.Now.ToLocalTime()}"; 52 | newChannel.Slug = newSlug; 53 | 54 | var updatedResult = await newChannel.Update(); 55 | 56 | Assert.AreEqual(newSlug, updatedResult.Models.First().Slug); 57 | } 58 | 59 | [TestMethod("SupabaseModel: Successfully Deletes")] 60 | public async Task SupabaseModelDeletes() 61 | { 62 | var slug = Guid.NewGuid().ToString(); 63 | var model = new Models.Channel { Slug = slug }; 64 | 65 | var insertResult = await _instance.From().Insert(model); 66 | var newChannel = insertResult.Models.FirstOrDefault(); 67 | 68 | await newChannel.Delete(); 69 | 70 | var result = await _instance.From() 71 | .Filter("slug", Constants.Operator.Equals, slug).Get(); 72 | 73 | Assert.AreEqual(0, result.Models.Count); 74 | } 75 | 76 | [TestMethod("Supports Dependency Injection for clients via property")] 77 | public void SupportsDIForClientsViaProperty() 78 | { 79 | _instance.Auth = new FakeAuthClient(); 80 | _instance.Functions = new FakeFunctionsClient(); 81 | _instance.Realtime = new FakeRealtimeClient(); 82 | _instance.Postgrest = new FakeRestClient(); 83 | _instance.Storage = new FakeStorageClient(); 84 | 85 | Assert.ThrowsExceptionAsync(() => _instance.Auth.GetUser("")); 86 | Assert.ThrowsExceptionAsync(() => _instance.Functions.Invoke("")); 87 | Assert.ThrowsExceptionAsync(() => _instance.Realtime.ConnectAsync()); 88 | Assert.ThrowsExceptionAsync(() => _instance.Postgrest.Rpc("", null)); 89 | Assert.ThrowsExceptionAsync(() => _instance.Storage.ListBuckets()); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /SupabaseTests/Models/Channel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Supabase.Postgrest.Attributes; 3 | using Supabase.Postgrest.Models; 4 | 5 | namespace SupabaseTests.Models 6 | { 7 | [Table("channels")] 8 | public class Channel : BaseModel 9 | { 10 | [PrimaryKey("id", false)] // Key is Autogenerated 11 | public int Id { get; set; } 12 | 13 | [Column("inserted_at")] 14 | public DateTime InsertedAt { get; set; } 15 | 16 | [Column("slug")] 17 | public string Slug { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SupabaseTests/Models/Stub.cs: -------------------------------------------------------------------------------- 1 | using Supabase.Postgrest.Models; 2 | 3 | namespace SupabaseTests.Models 4 | { 5 | public class Stub : BaseModel 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SupabaseTests/Models/User.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Supabase.Postgrest.Attributes; 3 | using Supabase.Postgrest.Models; 4 | 5 | namespace SupabaseTests.Models 6 | { 7 | [Table("users")] 8 | public class User : BaseModel 9 | { 10 | [JsonProperty("username")] 11 | public string Username { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SupabaseTests/StatelessClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Supabase.Gotrue; 6 | using SupabaseTests.Models; 7 | using static Supabase.Gotrue.Constants; 8 | using static Supabase.StatelessClient; 9 | 10 | namespace SupabaseTests 11 | { 12 | [TestClass] 13 | public class StatelessClient 14 | { 15 | 16 | private string supabaseUrl = "http://localhost"; 17 | private Supabase.SupabaseOptions options = new() 18 | { 19 | AuthUrlFormat = "{0}:9999", 20 | RealtimeUrlFormat = "{0}:4000/socket", 21 | RestUrlFormat = "{0}:3000" 22 | }; 23 | 24 | [TestMethod("Can access Stateless REST")] 25 | public async Task CanAccessStatelessRest() 26 | { 27 | var restOptions = GetRestOptions(null, options); 28 | var result1 = await new Supabase.Postgrest.Client(String.Format(options.RestUrlFormat, supabaseUrl), restOptions).Table().Get(); 29 | 30 | var result2 = await From(supabaseUrl, null, options).Get(); 31 | 32 | Assert.AreEqual(result1.Models.Count, result2.Models.Count); 33 | } 34 | 35 | [TestMethod("Can access Stateless GoTrue")] 36 | public void CanAccessStatelessGotrue() 37 | { 38 | var gotrueOptions = GetAuthOptions(supabaseUrl, null, options); 39 | 40 | var client = new Supabase.Gotrue.Client(gotrueOptions); 41 | 42 | var url = client.SignIn(Provider.Spotify); 43 | 44 | Assert.IsNotNull(url); 45 | } 46 | 47 | [TestMethod("User defined Headers will override internal headers")] 48 | public void CanOverrideInternalHeaders() 49 | { 50 | Supabase.SupabaseOptions options = new Supabase.SupabaseOptions 51 | { 52 | AuthUrlFormat = "{0}:9999", 53 | RealtimeUrlFormat = "{0}:4000/socket", 54 | RestUrlFormat = "{0}:3000", 55 | Headers = new Dictionary { 56 | { "Authorization", "Bearer 123" } 57 | } 58 | }; 59 | 60 | var gotrueOptions = GetAuthOptions(supabaseUrl, "456", options); 61 | 62 | Assert.AreEqual("Bearer 123", gotrueOptions.Headers["Authorization"]); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SupabaseTests/Stubs/FakeFunctionsClient.cs: -------------------------------------------------------------------------------- 1 | using Supabase.Functions.Interfaces; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SupabaseTests.Stubs 9 | { 10 | internal class FakeFunctionsClient : IFunctionsClient 11 | { 12 | public Func> GetHeaders { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 13 | 14 | public Task Invoke(string url, string token = null, Supabase.Functions.Client.InvokeFunctionOptions options = null) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | 19 | public Task Invoke(string url, string token = null, Supabase.Functions.Client.InvokeFunctionOptions options = null) where T : class 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | 24 | public Task RawInvoke(string url, string token = null, Supabase.Functions.Client.InvokeFunctionOptions options = null) 25 | { 26 | throw new NotImplementedException(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SupabaseTests/Stubs/FakeRealtimeClient.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Supabase.Realtime; 3 | using Supabase.Realtime.Interfaces; 4 | using Supabase.Realtime.Socket; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.ObjectModel; 8 | using System.Net.WebSockets; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using Supabase.Realtime.Exceptions; 12 | 13 | namespace SupabaseTests.Stubs 14 | { 15 | internal class FakeRealtimeClient : IRealtimeClient 16 | { 17 | public void AddStateChangedHandler(IRealtimeClient.SocketStateEventHandler handler) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public void RemoveStateChangedHandler(IRealtimeClient.SocketStateEventHandler handler) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | 27 | public void ClearStateChangedHandlers() 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public void AddDebugHandler(IRealtimeDebugger.DebugEventHandler handler) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | 37 | public void RemoveDebugHandler(IRealtimeDebugger.DebugEventHandler handler) 38 | { 39 | throw new NotImplementedException(); 40 | } 41 | 42 | public void ClearDebugHandlers() 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | 47 | public RealtimeChannel Channel(string channelName) 48 | { 49 | throw new NotImplementedException(); 50 | } 51 | 52 | public RealtimeChannel Channel(string database = "realtime", string schema = "public", string table = "*", 53 | string column = null, string value = null, Dictionary parameters = null) 54 | { 55 | throw new NotImplementedException(); 56 | } 57 | 58 | public IRealtimeClient Connect(Action, RealtimeException> callback = null) 59 | { 60 | throw new NotImplementedException(); 61 | } 62 | 63 | public Task> ConnectAsync() 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | 68 | public IRealtimeClient Disconnect(WebSocketCloseStatus code = WebSocketCloseStatus.NormalClosure, string reason = "Programmatic Disconnect") 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | public void Remove(RealtimeChannel channel) 74 | { 75 | throw new NotImplementedException(); 76 | } 77 | 78 | public void SetAuth(string jwt) 79 | { 80 | throw new NotImplementedException(); 81 | } 82 | 83 | public ClientOptions Options { get; } 84 | public JsonSerializerSettings SerializerSettings { get; } 85 | public IRealtimeSocket Socket { get; } 86 | public ReadOnlyDictionary Subscriptions { get; } 87 | public Func> GetHeaders { get; set; } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SupabaseTests/Stubs/FakeRestClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Supabase.Postgrest; 5 | using Supabase.Postgrest.Interfaces; 6 | using Supabase.Postgrest.Models; 7 | using Supabase.Postgrest.Responses; 8 | 9 | namespace SupabaseTests.Stubs 10 | { 11 | internal class FakeRestClient : IPostgrestClient 12 | { 13 | public IPostgrestTableWithCache Table(IPostgrestCacheProvider cacheProvider) where T : BaseModel, new() 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public string BaseUrl => throw new NotImplementedException(); 19 | 20 | public ClientOptions Options => throw new NotImplementedException(); 21 | 22 | public Func> GetHeaders { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 23 | 24 | public void AddRequestPreparedHandler(OnRequestPreparedEventHandler handler) 25 | { 26 | throw new NotImplementedException(); 27 | } 28 | 29 | public void RemoveRequestPreparedHandler(OnRequestPreparedEventHandler handler) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | 34 | public void ClearRequestPreparedHandlers() 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | 39 | public void AddDebugHandler(IPostgrestDebugger.DebugEventHandler handler) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | 44 | public void RemoveDebugHandler(IPostgrestDebugger.DebugEventHandler handler) 45 | { 46 | throw new NotImplementedException(); 47 | } 48 | 49 | public void ClearDebugHandlers() 50 | { 51 | throw new NotImplementedException(); 52 | } 53 | 54 | public Task Rpc(string procedureName, object parameters) 55 | { 56 | throw new NotImplementedException(); 57 | } 58 | 59 | public Task Rpc(string procedureName, object parameters = null) 60 | { 61 | throw new NotImplementedException(); 62 | } 63 | 64 | public Task Rpc(string procedureName, Dictionary parameters) 65 | { 66 | throw new NotImplementedException(); 67 | } 68 | 69 | public IPostgrestTable Table() where T : BaseModel, new() 70 | { 71 | throw new NotImplementedException(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SupabaseTests/Stubs/FakeStorageClient.cs: -------------------------------------------------------------------------------- 1 | using Supabase.Storage; 2 | using Supabase.Storage.Interfaces; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SupabaseTests.Stubs 9 | { 10 | internal class FakeStorageClient : IStorageClient 11 | { 12 | public Dictionary Headers { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 13 | public Func> GetHeaders { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 14 | 15 | public ClientOptions Options => throw new NotImplementedException(); 16 | 17 | public Task CreateBucket(string id, BucketUpsertOptions options = null) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public Task DeleteBucket(string id) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | 27 | public Task EmptyBucket(string id) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public IStorageFileApi From(string id) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | 37 | public Task GetBucket(string id) 38 | { 39 | throw new NotImplementedException(); 40 | } 41 | 42 | public Task> ListBuckets() 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | 47 | public Task UpdateBucket(string id, BucketUpsertOptions options = null) 48 | { 49 | throw new NotImplementedException(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SupabaseTests/SupabaseTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 0.8.5 7 | Library 8 | net7.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /SupabaseTests/db/00-schema.sql: -------------------------------------------------------------------------------- 1 | -- Set up reatime 2 | create role anon nologin noinherit; 3 | create role authenticated nologin noinherit; 4 | create role service_role nologin noinherit bypassrls; 5 | 6 | grant usage on schema public to anon, authenticated, service_role; 7 | 8 | alter default privileges in schema public grant all on tables to anon, authenticated, service_role; 9 | alter default privileges in schema public grant all on functions to anon, authenticated, service_role; 10 | alter default privileges in schema public grant all on sequences to anon, authenticated, service_role; 11 | 12 | create schema if not exists _realtime; 13 | create schema if not exists realtime; 14 | 15 | create publication supabase_realtime with (publish = 'insert, update, delete'); 16 | 17 | -- Supabase super admin 18 | create user supabase_admin; 19 | alter user supabase_admin with superuser createdb createrole replication bypassrls; 20 | 21 | 22 | -- Extension namespacing 23 | create schema extensions; 24 | create extension if not exists "uuid-ossp" with schema extensions; 25 | create extension if not exists pgcrypto with schema extensions; 26 | -- create extension if not exists pgjwt with schema extensions; 27 | 28 | create user authenticator noinherit; 29 | grant anon to authenticator; 30 | grant authenticated to authenticator; 31 | grant service_role to authenticator; 32 | grant supabase_admin to authenticator; 33 | 34 | grant usage on schema public to postgres, anon, authenticated, service_role; 35 | alter default privileges in schema public grant all on tables to postgres, anon, authenticated, service_role; 36 | alter default privileges in schema public grant all on functions to postgres, anon, authenticated, service_role; 37 | alter default privileges in schema public grant all on sequences to postgres, anon, authenticated, service_role; 38 | 39 | -- Set up namespacing 40 | alter user supabase_admin SET search_path TO public, extensions; -- don't include the "auth" schema 41 | 42 | -- These are required so that the users receive grants whenever "supabase_admin" creates tables/function 43 | alter default privileges for user supabase_admin in schema public grant all 44 | on sequences to postgres, anon, authenticated, service_role; 45 | alter default privileges for user supabase_admin in schema public grant all 46 | on tables to postgres, anon, authenticated, service_role; 47 | alter default privileges for user supabase_admin in schema public grant all 48 | on functions to postgres, anon, authenticated, service_role; -------------------------------------------------------------------------------- /SupabaseTests/db/01-auth-schema.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION postgres; 2 | -- auth.users definition 3 | CREATE TABLE auth.users ( 4 | instance_id uuid NULL, 5 | id uuid NOT NULL, 6 | aud varchar(255) NULL, 7 | "role" varchar(255) NULL, 8 | email varchar(255) NULL, 9 | encrypted_password varchar(255) NULL, 10 | confirmed_at timestamptz NULL, 11 | invited_at timestamptz NULL, 12 | confirmation_token varchar(255) NULL, 13 | confirmation_sent_at timestamptz NULL, 14 | recovery_token varchar(255) NULL, 15 | recovery_sent_at timestamptz NULL, 16 | email_change_token varchar(255) NULL, 17 | email_change varchar(255) NULL, 18 | email_change_sent_at timestamptz NULL, 19 | last_sign_in_at timestamptz NULL, 20 | raw_app_meta_data jsonb NULL, 21 | raw_user_meta_data jsonb NULL, 22 | is_super_admin bool NULL, 23 | created_at timestamptz NULL, 24 | updated_at timestamptz NULL, 25 | CONSTRAINT users_pkey PRIMARY KEY (id) 26 | ); 27 | CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, email); 28 | CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id); 29 | -- auth.refresh_tokens definition 30 | CREATE TABLE auth.refresh_tokens ( 31 | instance_id uuid NULL, 32 | id bigserial NOT NULL, 33 | "token" varchar(255) NULL, 34 | user_id varchar(255) NULL, 35 | revoked bool NULL, 36 | created_at timestamptz NULL, 37 | updated_at timestamptz NULL, 38 | CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id) 39 | ); 40 | CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id); 41 | CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id); 42 | CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree (token); 43 | -- auth.instances definition 44 | CREATE TABLE auth.instances ( 45 | id uuid NOT NULL, 46 | uuid uuid NULL, 47 | raw_base_config text NULL, 48 | created_at timestamptz NULL, 49 | updated_at timestamptz NULL, 50 | CONSTRAINT instances_pkey PRIMARY KEY (id) 51 | ); 52 | -- auth.audit_log_entries definition 53 | CREATE TABLE auth.audit_log_entries ( 54 | instance_id uuid NULL, 55 | id uuid NOT NULL, 56 | payload json NULL, 57 | created_at timestamptz NULL, 58 | CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id) 59 | ); 60 | CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id); 61 | -- auth.schema_migrations definition 62 | CREATE TABLE auth.schema_migrations ( 63 | "version" varchar(255) NOT NULL, 64 | CONSTRAINT schema_migrations_pkey PRIMARY KEY ("version") 65 | ); 66 | INSERT INTO auth.schema_migrations (version) 67 | VALUES ('20171026211738'), 68 | ('20171026211808'), 69 | ('20171026211834'), 70 | ('20180103212743'), 71 | ('20180108183307'), 72 | ('20180119214651'), 73 | ('20180125194653'); 74 | -- Gets the User ID from the request cookie 75 | create or replace function auth.uid() returns uuid as $$ 76 | select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid; 77 | $$ language sql stable; 78 | -- Gets the User ID from the request cookie 79 | create or replace function auth.role() returns text as $$ 80 | select nullif(current_setting('request.jwt.claim.role', true), '')::text; 81 | $$ language sql stable; 82 | GRANT ALL PRIVILEGES ON SCHEMA auth TO postgres; 83 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO postgres; 84 | GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO postgres; 85 | ALTER USER postgres SET search_path = "auth"; -------------------------------------------------------------------------------- /SupabaseTests/db/02-rest-schema.sql: -------------------------------------------------------------------------------- 1 | -- CHANNELS 2 | CREATE TABLE public.channels ( 3 | id int generated by default as identity, 4 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 5 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL, 6 | data jsonb DEFAULT null, 7 | slug text 8 | ); 9 | ALTER TABLE public.channels REPLICA IDENTITY FULL; 10 | ALTER publication supabase_realtime add table public.channels; -------------------------------------------------------------------------------- /SupabaseTests/db/03-dummy-data.sql: -------------------------------------------------------------------------------- 1 | -- insert users 2 | INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "confirmed_at", "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", "recovery_sent_at", "email_change_token", "email_change", "email_change_sent_at", "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", "created_at", "updated_at") VALUES 3 | ('00000000-0000-0000-0000-000000000000', '317eadce-631a-4429-a0bb-f19a7a517b4a', 'authenticated', 'authenticated', 'inian+user2@supabase.io', '', NULL, '2021-02-17 04:41:13.408828+00', '541rn7rTZPGeGCYsp0a38g', '2021-02-17 04:41:13.408828+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:41:13.406912+00', '2021-02-17 04:41:13.406919+00'), 4 | ('00000000-0000-0000-0000-000000000000', '4d56e902-f0a0-4662-8448-a4d9e643c142', 'authenticated', 'authenticated', 'inian+user1@supabase.io', '', NULL, '2021-02-17 04:40:58.570482+00', 'U1HvzExEO3l7JzP-4tTxJA', '2021-02-17 04:40:58.570482+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:40:58.568637+00', '2021-02-17 04:40:58.568642+00'), 5 | ('00000000-0000-0000-0000-000000000000', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', 'authenticated', 'authenticated', 'inian+admin@supabase.io', '', NULL, '2021-02-17 04:40:42.901743+00', '3EG99GjT_e3NC4eGEBXOjw', '2021-02-17 04:40:42.901743+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:40:42.890632+00', '2021-02-17 04:40:42.890637+00'); -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.3" 2 | 3 | services: 4 | db: 5 | image: supabase/postgres:14.1.0.105 6 | container_name: realtime-db 7 | ports: 8 | - "5432:5432" 9 | volumes: 10 | - ./SupabaseTests/db:/docker-entrypoint-initdb.d/ 11 | command: postgres -c config_file=/etc/postgresql/postgresql.conf 12 | environment: 13 | POSTGRES_HOST: /var/run/postgresql 14 | POSTGRES_PASSWORD: postgres 15 | 16 | rest: 17 | image: postgrest/postgrest:latest 18 | restart: unless-stopped 19 | ports: 20 | - "3000:3000" 21 | environment: 22 | PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres 23 | PGRST_DB_SCHEMA: public,storage 24 | PGRST_DB_EXTRA_SEARCH_PATH: public,storage,extensions 25 | PGRST_DB_ANON_ROLE: postgres 26 | PGRST_JWT_SECRET: 'f023d3db-39dc-4ac9-87b2-b2be72e9162b' 27 | depends_on: 28 | - db 29 | 30 | gotrue: 31 | image: supabase/gotrue:v2.129.1 32 | restart: unless-stopped 33 | ports: 34 | - "9999:9999" 35 | environment: 36 | GOTRUE_MAILER_URLPATHS_CONFIRMATION: '/verify' 37 | GOTRUE_JWT_SECRET: 'f023d3db-39dc-4ac9-87b2-b2be72e9162b' 38 | GOTRUE_JWT_EXP: 3600 39 | GOTRUE_DB_DRIVER: postgres 40 | DB_NAMESPACE: auth 41 | GOTRUE_API_HOST: 0.0.0.0 42 | PORT: 9999 43 | GOTRUE_DISABLE_SIGNUP: 'false' 44 | API_EXTERNAL_URL: http://localhost:9999 45 | GOTRUE_SITE_URL: http://localhost:9999 46 | GOTRUE_URI_ALLOW_LIST: https://supabase.io/docs 47 | GOTRUE_MAILER_AUTOCONFIRM: 'true' 48 | GOTRUE_LOG_LEVEL: DEBUG 49 | GOTRUE_OPERATOR_TOKEN: super-secret-operator-token 50 | DATABASE_URL: 'postgres://postgres:postgres@db:5432/postgres?sslmode=disable' 51 | depends_on: 52 | - db 53 | 54 | realtime: 55 | depends_on: 56 | - db 57 | image: supabase/realtime:v2.13.0 58 | container_name: realtime-server 59 | ports: 60 | - "4000:4000" 61 | environment: 62 | PORT: 4000 63 | DB_HOST: host.docker.internal 64 | DB_PORT: 5432 65 | DB_USER: postgres 66 | DB_PASSWORD: postgres 67 | DB_NAME: postgres 68 | DB_ENC_KEY: supabaserealtime 69 | DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime' 70 | API_JWT_SECRET: dc447559-996d-4761-a306-f47a5eab1623 71 | FLY_ALLOC_ID: fly123 72 | FLY_APP_NAME: realtime 73 | SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq 74 | ERL_AFLAGS: -proto_dist inet_tcp 75 | ENABLE_TAILSCALE: "false" 76 | DNS_NODES: "''" 77 | command: sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server" 78 | 79 | --------------------------------------------------------------------------------