Wallet connection + supabase

This commit is contained in:
TG9six 2025-10-15 20:28:33 +04:00
parent 5437f21c2d
commit 1fa07220c1
270 changed files with 288559 additions and 170 deletions

6
.vsconfig Normal file
View File

@ -0,0 +1,6 @@
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Workload.ManagedGame"
]
}

8
Assets/Editor.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d4be753bffbac8d4c99c0d4cdad9d5d2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,54 @@
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
[Serializable] class SupabaseClientSecrets { public string url; public string anonKey; }
public class GenerateSupabaseSecretsPrebuild : IPreprocessBuildWithReport
{
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report)
{
// 1) read from Cloud Build env (populated from project secrets)
var url = Environment.GetEnvironmentVariable("SUPABASE_URL");
var anonKey = Environment.GetEnvironmentVariable("SUPABASE_ANON");
// 2) local dev override (git-ignored)
var devPath = Path.Combine(Application.dataPath, "Editor/supabase.secrets.dev.json");
if (File.Exists(devPath))
{
try
{
var dev = JsonUtility.FromJson<SupabaseClientSecrets>(File.ReadAllText(devPath));
if (!string.IsNullOrWhiteSpace(dev.url)) url = dev.url;
if (!string.IsNullOrWhiteSpace(dev.anonKey)) anonKey = dev.anonKey;
}
catch { Debug.LogWarning("Invalid supabase.secrets.dev.json; ignoring."); }
}
if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(anonKey))
throw new BuildFailedException("Missing SUPABASE_URL or SUPABASE_ANON_KEY.");
var json = JsonUtility.ToJson(new SupabaseClientSecrets { url = url, anonKey = anonKey }, false);
var resDir = Path.Combine(Application.dataPath, "Resources");
Directory.CreateDirectory(resDir);
var outPath = Path.Combine(resDir, "SupabaseClientSecrets.json");
File.WriteAllText(outPath, json);
AssetDatabase.ImportAsset(RelativeToProject(outPath));
Debug.Log("Embedded SupabaseClientSecrets.json");
}
static string RelativeToProject(string abs)
{
var root = Application.dataPath[..^"Assets".Length];
return abs.Replace(root, "").Replace("\\", "/");
}
}
#endif

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: eaaa4e4797f4ddc46ba77c5cb7cfa543

View File

@ -0,0 +1,205 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text.Json;
using UnityEditor;
using UnityEngine;
using Supabase;
using Postgrest;
using Postgrest.Attributes;
using Postgrest.Models;
#region DB Model
[Table("players")]
public class PlayerRow : BaseModel
{
[PrimaryKey("wallet_address", false)]
[Column("wallet_address")] public string WalletAddress { get; set; }
[Column("total_kills")] public int TotalKills { get; set; }
[Column("average_placement")] public decimal? AveragePlacement { get; set; }
[Column("win_percentage")] public decimal? WinPercentage { get; set; }
[Column("in_game_currency")] public long InGameCurrency { get; set; }
// jsonb we'll send/receive as a plain object map
[Column("purchased_items")] public Dictionary<string, object> PurchasedItems { get; set; } = new();
[Column("updated_at")] public DateTimeOffset? UpdatedAt { get; set; }
}
#endregion
public class PlayersDevTools : EditorWindow
{
// Inputs
string wallet = "dev_wallet_test_1";
int totalKills = 3;
double avgPlacement = 2.5;
double winPct = 55.25;
long currency = 1000;
string purchasedJson = "{\"starter_pack\":true,\"sword\":\"iron\"}";
Supabase.Client client;
[MenuItem("Tools/Supabase/Players Tester")]
public static void Open() => GetWindow<PlayersDevTools>("Players Tester");
async void OnEnable() => await EnsureClient();
async Task EnsureClient()
{
if (client != null) return;
var s = await SupabaseSecretsLoader.LoadAsync(); // editor dev json or Resources
var opts = new SupabaseOptions { AutoRefreshToken = true, AutoConnectRealtime = false, Schema = "public" };
client = new Supabase.Client(s.url, s.anonKey, opts);
await client.InitializeAsync();
}
void OnGUI()
{
EditorGUILayout.LabelField("Upsert / Read / Delete: public.players", EditorStyles.boldLabel);
EditorGUILayout.Space();
wallet = EditorGUILayout.TextField("wallet_address", wallet);
totalKills = EditorGUILayout.IntField("total_kills", totalKills);
avgPlacement = EditorGUILayout.DoubleField("average_placement", avgPlacement);
winPct = EditorGUILayout.DoubleField("win_percentage", winPct);
currency = EditorGUILayout.LongField("in_game_currency", currency);
purchasedJson = EditorGUILayout.TextField("purchased_items (json)", purchasedJson);
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("UPSERT")) _ = UpsertAsync();
if (GUILayout.Button("READ")) _ = ReadAsync();
if (GUILayout.Button("DELETE")) _ = DeleteAsync();
}
}
async Task UpsertAsync()
{
try
{
await EnsureClient();
var row = new PlayerRow
{
WalletAddress = wallet,
TotalKills = totalKills,
AveragePlacement = (decimal)avgPlacement,
WinPercentage = (decimal)winPct,
InGameCurrency = currency,
PurchasedItems = ParseDict(purchasedJson),
UpdatedAt = DateTimeOffset.UtcNow
};
// postgrest-csharp 3.5.x Upsert by PK, no onConflict param
var resp = await client.From<PlayerRow>().Upsert(row);
var saved = resp.Models.Count > 0 ? resp.Models[0] : row;
Debug.Log($"✅ UPSERT OK -> wallet={saved.WalletAddress} kills={saved.TotalKills} currency={saved.InGameCurrency}");
ShowNotification(new GUIContent("UPSERT OK"));
}
catch (Exception e)
{
Debug.LogError($"UPSERT FAILED: {e.Message}");
ShowNotification(new GUIContent("UPSERT FAILED"));
}
}
async Task ReadAsync()
{
try
{
await EnsureClient();
var resp = await client
.From<PlayerRow>()
.Select("*") // 3.5.x needs a string
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, wallet)
.Get();
if (resp.Models.Count == 0)
{
Debug.LogWarning("No row found.");
ShowNotification(new GUIContent("NOT FOUND"));
return;
}
var p = resp.Models[0];
Debug.Log($"✅ READ -> wallet={p.WalletAddress}, kills={p.TotalKills}, avg={p.AveragePlacement}, win%={p.WinPercentage}, cur={p.InGameCurrency}, items={JsonSerializer.Serialize(p.PurchasedItems)}");
ShowNotification(new GUIContent("READ OK"));
}
catch (Exception e)
{
Debug.LogError($"READ FAILED: {e.Message}");
ShowNotification(new GUIContent("READ FAILED"));
}
}
async Task DeleteAsync()
{
try
{
await EnsureClient();
await client
.From<PlayerRow>()
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, wallet)
.Delete();
Debug.Log("✅ DELETE OK");
ShowNotification(new GUIContent("DELETE OK"));
}
catch (Exception e)
{
Debug.LogError($"DELETE FAILED: {e.Message}");
ShowNotification(new GUIContent("DELETE FAILED"));
}
}
// -------- JSON helpers (System.Text.Json) --------
static Dictionary<string, object> ParseDict(string json)
{
var result = new Dictionary<string, object>();
if (string.IsNullOrWhiteSpace(json)) return result;
try
{
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.ValueKind != JsonValueKind.Object) return result;
foreach (var p in doc.RootElement.EnumerateObject())
result[p.Name] = ToPlain(p.Value);
}
catch { /* ignore -> empty dict */ }
return result;
}
static object ToPlain(JsonElement e)
{
switch (e.ValueKind)
{
case JsonValueKind.String: return e.GetString();
case JsonValueKind.Number:
if (e.TryGetInt64(out var l)) return l;
if (e.TryGetDouble(out var d)) return d;
return e.GetRawText();
case JsonValueKind.True: return true;
case JsonValueKind.False: return false;
case JsonValueKind.Null: return null;
case JsonValueKind.Array:
var list = new List<object>();
foreach (var it in e.EnumerateArray()) list.Add(ToPlain(it));
return list;
case JsonValueKind.Object:
var dict = new Dictionary<string, object>();
foreach (var p in e.EnumerateObject()) dict[p.Name] = ToPlain(p.Value);
return dict;
default: return e.GetRawText();
}
}
}
#endif

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e00fc484bb422604caed4b0cf9609e65

View File

@ -0,0 +1,53 @@
#if UNITY_EDITOR
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using Supabase;
public static class SupabaseSecretsVerifier
{
[MenuItem("Tools/Supabase/Verify Secrets (Editor)")]
public static void VerifyMenu() { _ = VerifyAsync(); }
static string Mask(string s) =>
string.IsNullOrEmpty(s) ? "<empty>" :
(s.Length <= 8 ? $"***{s[^4..]}" : $"{s[..4]}***{s[^6..]}");
static async Task VerifyAsync()
{
try
{
// 1) Load secrets (dev file in Editor OR Resources if present)
var sec = await SupabaseSecretsLoader.LoadAsync();
// 2) Log masked values
Debug.Log($"[Supabase] URL: {sec.url}\nAnon: {Mask(sec.anonKey)}");
// 3) Try Client init (no network calls needed here)
var opts = new SupabaseOptions { AutoConnectRealtime = false, AutoRefreshToken = true };
var client = new Client(sec.url, sec.anonKey, opts);
await client.InitializeAsync();
// 4) Optional lightweight API ping (Auth health -> 200 OK)
var healthUrl = $"{sec.url.TrimEnd('/')}/auth/v1/health";
using var req = UnityWebRequest.Get(healthUrl);
req.SetRequestHeader("apikey", sec.anonKey);
await req.SendWebRequest();
var ok = req.result == UnityWebRequest.Result.Success && req.responseCode == 200;
var msg = ok ? "✅ Secrets OK: Editor can init Supabase and reach /auth/v1/health."
: $"⚠️ Init OK but health probe returned {req.responseCode} ({req.error}). " +
"This can be blocked by firewall; init still proves secrets are loaded.";
Debug.Log(msg);
EditorUtility.DisplayDialog("Supabase Secrets", msg + $"\n\nHost: {new System.Uri(sec.url).Host}\nAnon: {Mask(sec.anonKey)}", "OK");
}
catch (System.Exception ex)
{
Debug.LogError($"❌ Supabase secrets verification failed: {ex.Message}");
EditorUtility.DisplayDialog("Supabase Secrets", "❌ Verification failed:\n" + ex.Message, "OK");
}
}
}
#endif

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c3a3724e5f6cb2e45a163af4ede0050f

View File

@ -0,0 +1 @@
{"url":"https://ouhzztijvgrvjpdvgido.supabase.co","anonKey":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im91aHp6dGlqdmdydmpwZHZnaWRvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjAxNjQzNzUsImV4cCI6MjA3NTc0MDM3NX0.60JLI7OIAZRiqNg0SOHPPORMkCcspHvFD7LwETRwUDE"}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2b0912cbe91eaaa4c866ee4da239a044
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,47 @@
using UnityEngine;
using TMPro;
namespace TPSBR
{
public class PlayerWalletAddress : MonoBehaviour
{
string firstFourChars = "";
string lastFourChars = "";
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
if (PlayerPrefs.HasKey("WALLET_ADDRESS"))
{
GetTruncatedString();
this.GetComponent<TextMeshProUGUI>().text = firstFourChars + "......" + lastFourChars;
}
}
// Update is called once per frame
void Update()
{
}
public void GetTruncatedString()
{
string originalString = PlayerPrefs.GetString("WALLET_ADDRESS");
if (originalString.Length >= 4)
{
firstFourChars = originalString.Substring(0, 4);
}
else
{
firstFourChars = originalString; // Or handle as needed if shorter
}
if (originalString.Length >= 4)
{
lastFourChars = originalString.Substring(originalString.Length - 4);
}
else
{
lastFourChars = originalString; // Or handle as needed if shorter
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1f3d1b1bb90e0a1468f1bba7758e40ad

View File

@ -0,0 +1 @@
{"url":"https://ouhzztijvgrvjpdvgido.supabase.co","anonKey":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im91aHp6dGlqdmdydmpwZHZnaWRvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjAxNjQzNzUsImV4cCI6MjA3NTc0MDM3NX0.60JLI7OIAZRiqNg0SOHPPORMkCcspHvFD7LwETRwUDE"}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0f7224fac46612a4b9d9a07e54761e97
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,35 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 582efc0fde77aab43a77ee4237ba305a, type: 3}
m_Name: WalletConnectConnectionProvider
m_EditorClassIdentifier:
<ProjectId>k__BackingField: 0b840bb98f4fea8ce4647bc0b0de86a0
<ProjectName>k__BackingField: BRSol
<AutoRenewSession>k__BackingField: 1
<BaseContext>k__BackingField: unity-game
<Metadata>k__BackingField:
Name:
Description:
Url:
Icons: []
Redirect:
Native:
Universal:
VerifyUrl:
<StoragePath>k__BackingField: wallet-connect/
<OverrideRegistryUri>k__BackingField:
handlerPrefab: {fileID: 5250797134093199901, guid: 0c7c312ad6c521f4b8346ed8e9d7c664, type: 3}
enabledWallets: []
disabledWallets: []
<LogLevel>k__BackingField: 1
<WalletLocationOption>k__BackingField: 0
<ConnectButtonRow>k__BackingField: {fileID: 8599114313589093787, guid: 4591cfadb2bf9824da90ebf4005728a8, type: 3}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4cff9744a34ebe64b8bb75767a8b96cd
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -0,0 +1,156 @@
fileFormatVersion: 2
guid: 2aa4e0d266612134984241a04bbb34db
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 2
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,19 @@
using UnityEngine;
namespace TPSBR
{
public class AppKitConfigInstaller : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1a9c92c8d28c4e44a99a8f4d42c4b64a

View File

@ -0,0 +1,439 @@
using System.Collections;
using System.Reflection;
using System.Threading.Tasks;
using UnityEngine;
using Reown.AppKit.Unity;
using Reown.AppKit.Unity.Model;
using System;
using UnityEngine.SceneManagement;
[DefaultExecutionOrder(1000)]
public class AppKitMetadataInjector : MonoBehaviour
{
[Header("WalletConnect Cloud Project ID")]
[SerializeField] private string projectId = "0b840bb98f4fea8ce4647bc0b0de86a0";
[Header("Branding")]
[SerializeField] private string appName = "BR Game";
[SerializeField] private string appDescription = "Public PC build (QR connect via phone)";
[SerializeField] private string appUrl = "https://example.com";
[SerializeField] private string appIconUrl = "https://walletconnect.com/meta/favicon.ico";
[Header("Chains (first one becomes default)")]
[SerializeField] private bool useEthereum = true; // eip155:1
[SerializeField] private bool usePolygon = true; // eip155:137
[SerializeField] private bool useBase = true; // eip155:8453
private bool _initialized;
private string _defaultChainId = "eip155:1";
private IEnumerator Start()
{
// Wait until the prefab set AppKit.Instance
while (AppKit.Instance == null) yield return null;
// Build metadata + config
var md = new Metadata(appName, appDescription, appUrl, appIconUrl);
var cfg = new AppKitConfig(projectId, md)
{
enableEmail = false,
enableOnramp = false,
enableCoinbaseWallet = false,
connectViewWalletsCountDesktop = 2,
connectViewWalletsCountMobile = 3,
};
// Build supported chains list (order matters: first = default)
EnableDesktopWallets(cfg);
var chains = new System.Collections.Generic.List<Chain>();
if (useEthereum) chains.Add(ChainConstants.Chains.Ethereum);
if (usePolygon) chains.Add(ChainConstants.Chains.Polygon);
if (useBase) chains.Add(ChainConstants.Chains.Base);
if (chains.Count == 0) chains.Add(ChainConstants.Chains.Ethereum);
cfg.supportedChains = chains.ToArray();
_defaultChainId = cfg.supportedChains[0].ChainId; // remember default
// Set AppKit.Config via private setter (reflection) BEFORE init
var cfgProp = typeof(AppKit).GetProperty("Config",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
cfgProp.SetValue(null, cfg);
// Initialize (supports InitializeAsync(cfg) / Initialize(cfg) / no-arg)
yield return InitializeAppKit(cfg);
// Select default chain if API exists (some builds require this)
var selectChain = typeof(AppKit).GetMethod("SelectChain",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (selectChain != null)
selectChain.Invoke(null, new object[] { _defaultChainId });
_initialized = true;
Debug.Log($"✅ AppKit initialized. Default chain: {_defaultChainId}");
AppKit.ConnectorController.AccountConnected += OnWalletConnected;
}
// Button hook
public void OnWalletConnectButton()
{
if (!_initialized)
{
StartCoroutine(OpenAfterReady());
return;
}
//AppKit.OpenModal(ViewType.Connect); // shows QR
//AppKit.ConnectorController.AccountConnected += OnWalletConnected;
OpenQrModal();
}
public void OnBrowserExtensionButton()
{
if (!_initialized)
{
StartCoroutine(ConnectDesktopAfterReady());
return;
}
TriggerDesktopConnect();
}
// Waits until initialized; doesn't re-init
private IEnumerator OpenAfterReady()
{
yield return WaitForInitialization();
OpenQrModal();
}
private IEnumerator ConnectDesktopAfterReady()
{
yield return WaitForInitialization();
TriggerDesktopConnect();
}
private IEnumerator WaitForInitialization()
{
var isInitProp = typeof(AppKit).GetProperty("IsInitialized",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
while (isInitProp != null && !(bool)isInitProp.GetValue(null))
yield return null;
yield break;
}
private void OpenQrModal()
{
AppKit.OpenModal(ViewType.Connect);
AppKit.ConnectorController.AccountConnected += OnWalletConnected;
}
private void TriggerDesktopConnect()
{
var controller = AppKit.ConnectorController;
if (controller == null)
{
Debug.LogWarning("AppKit ConnectorController unavailable for desktop connection.");
return;
}
var controllerType = controller.GetType();
var methods = controllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var method in methods)
{
if (method.Name != "ConnectAsync") continue;
var parameters = method.GetParameters();
if (parameters.Length == 0) continue;
var args = new object[parameters.Length];
var success = true;
for (int i = 0; i < parameters.Length; i++)
{
var paramType = parameters[i].ParameterType;
if (paramType.IsEnum && paramType.Name.Contains("ConnectMethod"))
{
try
{
args[i] = Enum.Parse(paramType, "Desktop", true);
}
catch
{
success = false;
break;
}
}
else if (paramType.Name.Contains("ConnectParams"))
{
args[i] = Activator.CreateInstance(paramType);
}
else if (paramType.FullName == typeof(System.Threading.CancellationToken).FullName)
{
args[i] = default(System.Threading.CancellationToken);
}
else if (paramType.IsValueType)
{
args[i] = Activator.CreateInstance(paramType);
}
else
{
args[i] = null;
}
}
if (!success) continue;
var result = method.Invoke(controller, args);
if (result is Task task)
{
StartCoroutine(WaitForTask(task));
}
AppKit.ConnectorController.AccountConnected += OnWalletConnected;
return;
}
Debug.LogWarning("Unable to locate desktop ConnectAsync overload; falling back to QR modal.");
OpenQrModal();
}
private IEnumerator WaitForTask(Task task)
{
while (task != null && !task.IsCompleted)
yield return null;
yield break;
}
private IEnumerator InitializeAppKit(AppKitConfig cfg)
{
var isInitProp = typeof(AppKit).GetProperty("IsInitialized",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
if (isInitProp != null && (bool)isInitProp.GetValue(null)) yield break;
var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
var initAsync = typeof(AppKit).GetMethod("InitializeAsync", flags);
if (initAsync != null)
{
var ps = initAsync.GetParameters();
var result = ps.Length == 1
? initAsync.Invoke(null, new object[] { cfg })
: initAsync.Invoke(null, null);
if (result is Task t)
while (!t.IsCompleted) yield return null;
yield break;
}
var init = typeof(AppKit).GetMethod("Initialize", flags);
if (init != null)
{
var ps = init.GetParameters();
var result = ps.Length == 1
? init.Invoke(null, new object[] { cfg })
: init.Invoke(null, null);
if (result is Task t)
while (!t.IsCompleted) yield return null;
yield break;
}
// If neither exists, rely on prefab auto-init
yield return null;
}
private void OnWalletConnected(object sender, Reown.AppKit.Unity.Connector.AccountConnectedEventArgs e)
{
Debug.Log("✅ Wallet connected!");
Debug.Log($"Address: {e.Account.Address}");
PlayerPrefs.SetString("WALLET_ADDRESS", e.Account.Address);
PlayerLoginOrSignup();
SceneManager.LoadScene(1);
}
private void EnableDesktopWallets(AppKitConfig cfg)
{
TrySetConfigBoolean(cfg, "enableEIP6963", true);
TrySetConfigBoolean(cfg, "enableInjected", true);
TrySetConfigBoolean(cfg, "enableDesktop", true);
TrySetConfigBoolean(cfg, "enableDesktopWallets", true);
TrySetConfigBoolean(cfg, "enableInjectedWallets", true);
}
private void TrySetConfigBoolean(AppKitConfig cfg, string memberName, bool value)
{
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var type = cfg.GetType();
var prop = type.GetProperty(memberName, flags);
if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool))
{
prop.SetValue(cfg, value);
return;
}
var field = type.GetField(memberName, flags);
if (field != null && field.FieldType == typeof(bool))
{
field.SetValue(cfg, value);
}
}
public async void PlayerLoginOrSignup()
{
try
{
var rec = await GameDb.WalletConnectedAsync(PlayerPrefs.GetString("WALLET_ADDRESS"));
Debug.Log($"[Runtime] ✅ Ensured player: {rec.WalletAddress} kills={rec.TotalKills} currency={rec.InGameCurrency}");
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("AddKill")]
public async void AddKill()
{
try
{
var rec = await GameDb.AddKillAsync(PlayerPrefs.GetString("WALLET_ADDRESS"));
Debug.Log($"[Runtime] ✅ Ensured player: {rec.WalletAddress} kills={rec.TotalKills} currency={rec.InGameCurrency}");
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("AddCurrency")]
public async void AddCurrency()
{
try
{
var rec = await GameDb.AddCurrencyAsync(PlayerPrefs.GetString("WALLET_ADDRESS"),100);
Debug.Log($"[Runtime] ✅ Ensured player: {rec.WalletAddress} kills={rec.TotalKills} currency={rec.InGameCurrency}");
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("GetCurrency")]
public async void GetCurrency()
{
try
{
var rec = await GameDb.GetCurrencyAsync(PlayerPrefs.GetString("WALLET_ADDRESS"));
Debug.Log(rec.ToString());
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("GetInventory")]
public async void GetPurchasedItem()
{
try
{
var rec = await GameDb.GetPurchasedItemsJsonAsync(PlayerPrefs.GetString("WALLET_ADDRESS"));
Debug.Log(rec.ToString());
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("AddGamesPlayed")]
public async void AddGamesPlayed()
{
try
{
var rec = await GameDb.IncGamesPlayedAsync(PlayerPrefs.GetString("WALLET_ADDRESS"));
Debug.Log(rec.ToString());
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("GetplayerData")]
public async void PlayerDataAsync()
{
try
{
var rec = await GameDb.GetPlayerAsync(PlayerPrefs.GetString("WALLET_ADDRESS"));
Debug.Log(rec.ToString());
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("Placement")]
public async void PlayerPlacement()
{
try
{
var rec = await GameDb.SendGameWinAsync(PlayerPrefs.GetString("WALLET_ADDRESS"),1);
Debug.Log(rec.ToString());
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("ItemBought")]
public async void AddPurchaseItem()
{
try
{
var rec = await GameDb.AddPurchasedItemAsync(PlayerPrefs.GetString("WALLET_ADDRESS"), "ChadGuy");
Debug.Log(rec.ToString());
}
catch (System.Exception e)
{
Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
}
}
[ContextMenu("CheckName")]
public async void OnWalletConnected(string wallet) // call this from your wallet SDK
{
try
{
// makes sure the row exists (display_name = "" on first create)
await GameDb.GetPlayerNameAsync(wallet);
Debug.Log($"Player ensured for {wallet}");
}
catch (System.Exception e)
{
Debug.LogError(e);
}
}
public void LoginAsGuest()
{
SceneManager.LoadScene(1);
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ea6984f3d743ff64da14b7927db196c6

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3c7fc92f5bf6c4c4593d42bbb81c5642
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,16 @@
using System.Threading.Tasks;
using UnityEngine;
[System.Serializable] public class SupabaseClientSecrets { public string url; public string anonKey; }
public static class SupabaseSecretsLoader
{
public static async Task<SupabaseClientSecrets> LoadAsync()
{
var ta = Resources.Load<TextAsset>("SupabaseClientSecrets");
if (ta == null || string.IsNullOrWhiteSpace(ta.text))
throw new System.Exception("SupabaseClientSecrets.json missing. Did prebuild run?");
await Task.Yield();
return JsonUtility.FromJson<SupabaseClientSecrets>(ta.text);
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6bbc9308b37c47c43b5ceea25619377e

View File

@ -0,0 +1,8 @@
using UnityEngine;
using Reown.AppKit.Unity;
using Reown.AppKit.Unity.Model;
public class AppKitConfigInstaller : MonoBehaviour
{
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f094ac8197426c44c9248e777c8c3fcd

287
Assets/Scripts/GameDb.cs Normal file
View File

@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Supabase;
using Postgrest;
using Game.Data;
using UnityEngine; // for PlayerPrefs
using System.Text.RegularExpressions;
public static class GameDb
{
private static Supabase.Client _client;
private const string PrefsWalletKey = "WALLET_ADDRESS";
private const string PrefsName = "DISPLAY_NAME";
public static async Task InitAsync()
{
if (_client != null) return;
var s = await SupabaseSecretsLoader.LoadAsync();
var opts = new SupabaseOptions { AutoConnectRealtime = false, AutoRefreshToken = true, Schema = "public" };
_client = new Supabase.Client(s.url, s.anonKey, opts);
await _client.InitializeAsync();
}
// ------------------------------------------------------------
// WalletConnected: check if exists, create if missing, store in PlayerPrefs
// ------------------------------------------------------------
//public static async Task<PlayerRecord> WalletConnectedAsync(string walletAddress)
//{
// if (string.IsNullOrWhiteSpace(walletAddress))
// throw new ArgumentException("walletAddress cannot be empty.");
// await InitAsync();
// // exists?
// var existing = await _client.From<PlayerRecord>()
// .Select("*")
// .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
// .Get();
// PlayerRecord result;
// if (existing.Models.Count > 0)
// {
// result = existing.Models[0];
// }
// else
// {
// // insert minimal row (DB supplies defaults & computed columns)
// var insert = await _client.From<PlayerRecord>().Insert(new PlayerRecord { WalletAddress = walletAddress });
// result = insert.Models.Count > 0 ? insert.Models[0] : new PlayerRecord { WalletAddress = walletAddress };
// }
// // "login success": save wallet locally
// PlayerPrefs.SetString(PrefsWalletKey, walletAddress);
// PlayerPrefs.Save();
// return result;
//}
public static async Task<PlayerRecord> WalletConnectedAsync(string walletAddress)
{
if (string.IsNullOrWhiteSpace(walletAddress))
throw new ArgumentException("walletAddress cannot be empty.");
await InitAsync();
// Atomic, safe, and wont try to write generated columns.
var rows = await _client.Rpc<List<PlayerRecord>>("ensure_player", new { p_wallet = walletAddress });
var player = rows != null && rows.Count > 0 ? rows[0] : null;
if (player == null)
throw new Exception("ensure_player RPC returned no row.");
PlayerPrefs.SetString("WALLET_ADDRESS", walletAddress);
PlayerPrefs.Save();
return player;
}
// ------------------------------------------------------------
// AddKill (+1 by default) -- uses RPC for atomic increment
// ------------------------------------------------------------
public static async Task<PlayerRecord> AddKillAsync(string walletAddress, int delta = 1)
{
await InitAsync();
var payload = new { p_wallet = walletAddress, p_delta = delta };
// SQL returns SETOF players → deserialize to a list/array and take first
var rows = await _client.Rpc<List<PlayerRecord>>("add_kills", payload);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
// ------------------------------------------------------------
// Call Supabase-side method to add currency (RPC)
// ------------------------------------------------------------
public static async Task<PlayerRecord> AddCurrencyAsync(string walletAddress, long delta)
{
await InitAsync();
var payload = new { p_wallet = walletAddress, p_delta = delta };
var rows = await _client.Rpc<List<PlayerRecord>>("add_currency", payload);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
// ------------------------------------------------------------
// Get currency for player
// ------------------------------------------------------------
public static async Task<long> GetCurrencyAsync(string walletAddress)
{
await InitAsync();
var resp = await _client.From<PlayerRecord>()
.Select("in_game_currency")
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
.Get();
return resp.Models.Count > 0 ? resp.Models[0].InGameCurrency : 0L;
}
// ------------------------------------------------------------
// Get JSON for bought items
// ------------------------------------------------------------
public static async Task<string> GetPurchasedItemsJsonAsync(string walletAddress)
{
await InitAsync();
var resp = await _client.From<PlayerRecord>()
.Select("purchased_items")
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
.Get();
if (resp.Models.Count == 0) return "{}";
var dict = resp.Models[0].PurchasedItems ?? new Dictionary<string, object>();
return JsonSerializer.Serialize(dict);
}
// ------------------------------------------------------------
// Edit JSON for bought items (replace with provided JSON)
// ------------------------------------------------------------
public static async Task<PlayerRecord> SetPurchasedItemsJsonAsync(string walletAddress, string json)
{
await InitAsync();
var rows = await _client.Rpc<List<PlayerRecord>>(
"set_purchased_items",
new { p_wallet = walletAddress, p_items = System.Text.Json.JsonDocument.Parse(json).RootElement }
);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
// ------------------------------------------------------------
// +1 games played / +1 games won (RPCs)
// ------------------------------------------------------------
public static async Task<PlayerRecord> IncGamesPlayedAsync(string walletAddress)
{
await InitAsync();
var rows = await _client.Rpc<List<PlayerRecord>>("inc_games_played", new { p_wallet = walletAddress });
return rows != null && rows.Count > 0 ? rows[0] : null;
}
public static async Task<PlayerRecord> IncGamesWonAsync(string walletAddress)
{
await InitAsync();
var rows = await _client.Rpc<List<PlayerRecord>>("inc_games_won", new { p_wallet = walletAddress });
return rows != null && rows.Count > 0 ? rows[0] : null;
}
// ------------------------------------------------------------
// Get Player data (full record)
// ------------------------------------------------------------
public static async Task<PlayerRecord> GetPlayerAsync(string walletAddress)
{
await InitAsync();
var resp = await _client.From<PlayerRecord>()
.Select("*")
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
.Get();
return resp.Models.Count > 0 ? resp.Models[0] : null;
}
public static async Task<PlayerRecord> SendGameWinAsync(
string walletAddress, int placement)
{
await InitAsync();
var payload = new
{
p_wallet = walletAddress,
p_placement = placement
};
var rows = await _client.Rpc<List<PlayerRecord>>("record_game_win", payload);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
/// <summary>
/// Add/overwrite one entry in purchased_items: key -> value (value default true).
/// </summary>
public static async Task<PlayerRecord> AddPurchasedItemAsync(string walletAddress, string key, object value = null)
{
await InitAsync();
// Fallback: serialize -> parse -> take RootElement
JsonElement jsonb = value == null
? JsonDocument.Parse("true").RootElement
: JsonDocument.Parse(JsonSerializer.Serialize(value)).RootElement;
var payload = new { p_wallet = walletAddress, p_key = key, p_value = jsonb };
var rows = await _client.Rpc<List<PlayerRecord>>("add_purchased_item", payload);
return rows != null && rows.Count > 0 ? rows[0] : null;
}
static string SanitizeName(string name)
{
if (string.IsNullOrWhiteSpace(name)) return null;
name = name.Trim();
// allow letters, numbers, space, underscore, dash; clamp length 3..24
name = Regex.Replace(name, @"[^A-Za-z0-9 _\-]", "");
if (name.Length < 3) return null;
if (name.Length > 24) name = name.Substring(0, 24);
return name;
}
// -------- GETTER --------
public static async Task<string> GetPlayerNameAsync(string walletAddress)
{
await InitAsync();
var resp = await _client.From<PlayerRecord>()
.Select("display_name")
.Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
.Get();
var name = resp.Models.Count > 0 ? resp.Models[0].DisplayName ?? "" : "";
// cache (optional)
PlayerPrefs.SetString(PrefsName, name);
PlayerPrefs.Save();
return name;
}
// -------- CHECK EMPTY (DB) --------
public static async Task<bool> IsNameEmptyAsync(string walletAddress)
{
var name = await GetPlayerNameAsync(walletAddress);
return string.IsNullOrEmpty(name);
}
// -------- SETTER: only if empty (safe path) --------
// Calls your SQL function: set_name_if_empty(p_wallet, p_name)
public static async Task<PlayerRecord> SetNameIfEmptyAsync(string walletAddress, string proposedName)
{
await InitAsync();
var clean = SanitizeName(proposedName);
if (string.IsNullOrEmpty(clean))
throw new ArgumentException("Invalid name. Use 324 chars: letters, numbers, space, _ or -.");
// RPC returns the player row (whether it updated or not)
var rows = await _client.Rpc<List<PlayerRecord>>(
"set_name_if_empty",
new { p_wallet = walletAddress, p_name = clean }
);
var player = rows != null && rows.Count > 0 ? rows[0] : null;
if (player == null) throw new Exception("set_name_if_empty returned no row.");
PlayerPrefs.SetString(PrefsName, player.DisplayName ?? "");
PlayerPrefs.Save();
return player;
}
// -------- SETTER: force update (optional, for rename UI) --------
public static async Task<PlayerRecord> SetNameAsync(string walletAddress, string newName)
{
await InitAsync();
var clean = SanitizeName(newName);
if (string.IsNullOrEmpty(clean))
throw new ArgumentException("Invalid name. Use 324 chars: letters, numbers, space, _ or -.");
// RPC returns the row; no SDK .Set() nonsense, no risk of overwriting other fields
var rows = await _client.Rpc<List<PlayerRecord>>(
"set_name",
new { p_wallet = walletAddress, p_name = clean }
);
var rec = rows != null && rows.Count > 0 ? rows[0] : null;
if (rec == null) throw new Exception("Name update failed.");
PlayerPrefs.SetString("DISPLAY_NAME", rec.DisplayName ?? "");
PlayerPrefs.Save();
return rec;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 92ed5ec56fc6e9c4398ff98996359355

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using Postgrest.Attributes;
using Postgrest.Models;
namespace Game.Data
{
[Table("players")]
public class PlayerRecord : BaseModel
{
[PrimaryKey("wallet_address", false)]
[Column("wallet_address")] public string WalletAddress { get; set; }
[Column("display_name")] public string DisplayName { get; set; }
[Column("total_kills")] public int TotalKills { get; set; }
[Column("in_game_currency")] public long InGameCurrency { get; set; }
[Column("purchased_items")] public Dictionary<string, object> PurchasedItems { get; set; } = new();
// server-computed or trigger-updated fields (read-only from client)
[Column("average_placement")] public decimal AveragePlacement { get; set; }
[Column("win_percentage")] public decimal WinPercentage { get; set; }
[Column("games_played")] public int GamesPlayed { get; set; }
[Column("games_won")] public int GamesWon { get; set; }
[Column("updated_at")] public DateTimeOffset? UpdatedAt { get; set; }
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f09b0f00fb687a44dbb2753ea4086cb9

View File

@ -0,0 +1,29 @@
using UnityEngine;
using Game.Data;
public class PlayerRuntimeTest : MonoBehaviour
{
[Header("Fake wallet for test (replace with your wallet connect result)")]
public string walletAddress = "runtime_wallet_test_1";
[ContextMenu("Ensure Player Now")]
//public async void EnsureNow()
//{
// try
// {
// var rec = await GameDb.EnsurePlayerAsync(walletAddress);
// Debug.Log($"[Runtime] ✅ Ensured player: {rec.WalletAddress} kills={rec.TotalKills} currency={rec.InGameCurrency}");
// }
// catch (System.Exception e)
// {
// Debug.LogError($"[Runtime] ❌ Ensure failed: {e.Message}");
// }
//}
// Optional: auto-run at Start
private void Start()
{
// Comment this if you only want the context menu / UI button
// EnsureNow();
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 58bc03d613f30f14cb36ef5bf5768bb1

View File

@ -11,15 +11,20 @@ public class SupabaseEmailAuth : MonoBehaviour
private async void Start()
{
var options = new SupabaseOptions
{
AutoRefreshToken = true,
AutoConnectRealtime = false
};
//var options = new SupabaseOptions
//{
// AutoRefreshToken = true,
// AutoConnectRealtime = false
//};
client = new Client(supabaseUrl, supabaseAnonKey, options);
await client.InitializeAsync();
Debug.Log("Supabase initialized");
//client = new Client(supabaseUrl, supabaseAnonKey, options);
//await client.InitializeAsync();
//Debug.Log("Supabase initialized");
var s = await SupabaseSecretsLoader.LoadAsync();
if (string.IsNullOrWhiteSpace(s.url) || string.IsNullOrWhiteSpace(s.anonKey))
Debug.LogError("Supabase secrets NOT embedded.");
else
Debug.Log($"Supabase URL OK: {new System.Uri(s.url).Host} (anon present: {s.anonKey.Length > 20})");
}
public async Task SignUp(string email, string password)

View File

@ -0,0 +1,16 @@
using Supabase;
using UnityEngine;
public class SupabaseBootstrap : MonoBehaviour
{
public static Client Client { get; private set; }
async void Awake()
{
var s = await SupabaseSecretsLoader.LoadAsync();
var opts = new SupabaseOptions { AutoConnectRealtime = false, AutoRefreshToken = true };
Client = new Client(s.url, s.anonKey, opts);
await Client.InitializeAsync();
Debug.Log("Supabase initialized from secrets.");
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5a4dea48775d5ed44863c4e2a666486c

View File

@ -0,0 +1,83 @@
using UnityEngine;
using UnityEngine.UI;
using Reown.AppKit.Unity;
using TMPro;
public class WalletAddressDisplay : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI addressText; // or TMP_Text
[SerializeField] private Button copyButton; // optional
private string _fullAddress = "";
private void Awake()
{
if (copyButton != null) copyButton.onClick.AddListener(CopyToClipboard);
}
private void OnEnable()
{
UpdateLabel(); // show current state immediately
// Subscribe to connection events from the static ConnectorController
if (AppKit.ConnectorController != null)
{
AppKit.ConnectorController.AccountConnected += OnAccountConnected;
AppKit.ConnectorController.AccountDisconnected += OnAccountDisconnected;
}
}
private void OnDisable()
{
if (AppKit.ConnectorController != null)
{
AppKit.ConnectorController.AccountConnected -= OnAccountConnected;
AppKit.ConnectorController.AccountDisconnected -= OnAccountDisconnected;
}
if (copyButton != null) copyButton.onClick.RemoveListener(CopyToClipboard);
}
private void OnAccountConnected(object _, Reown.AppKit.Unity.Connector.AccountConnectedEventArgs __)
{
UpdateLabel();
}
private void OnAccountDisconnected(object _, Reown.AppKit.Unity.Connector.AccountDisconnectedEventArgs __)
{
_fullAddress = "";
SetText("Not connected");
}
private void UpdateLabel()
{
var controller = AppKit.ConnectorController;
if (controller != null && controller.Account != null)
{
_fullAddress = controller.Account.Address;
SetText(Shorten(_fullAddress));
}
else
{
SetText("Not connected");
}
}
private void CopyToClipboard()
{
if (!string.IsNullOrEmpty(_fullAddress))
GUIUtility.systemCopyBuffer = _fullAddress;
}
private static string Shorten(string addr)
{
if (string.IsNullOrEmpty(addr) || addr.Length < 12) return addr;
return addr[..6] + "..." + addr[^4..];
}
private void SetText(string s)
{
if (addressText != null) addressText.text = s;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b815ce1848c896947bf57fb1a8bf9475

167
Assets/SupabasePlayers.cs Normal file
View File

@ -0,0 +1,167 @@
//using System;
//using System.Collections.Generic;
//using System.Threading.Tasks;
//using Supabase;
//using Postgrest;
//using Postgrest.Attributes;
//using Postgrest.Models;
//#region Model
//[Table("players")]
//public class PlayerRow : BaseModel
//{
// //[PrimaryKey("wallet_address", false)]
// //[Column("wallet_address")]
// //public string WalletAddress { get; set; }
// //[Column("total_kills")]
// //public int TotalKills { get; set; }
// //[Column("average_placement")]
// //public decimal? AveragePlacement { get; set; }
// //[Column("win_percentage")]
// //public decimal? WinPercentage { get; set; }
// //[Column("in_game_currency")]
// //public long InGameCurrency { get; set; }
// //[Column("purchased_items")]
// //public Dictionary<string, object> PurchasedItems { get; set; } = new();
// //[Column("updated_at")]
// //public DateTimeOffset? UpdatedAt { get; set; }
//}
//#endregion
////public static class SupabasePlayers
////{
//// // TODO: set these
//// public static string SupabaseUrl = "https://YOUR-PROJECT.supabase.co";
//// public static string SupabaseAnonKey = "YOUR-ANON-KEY";
//// private static Client _client;
//// public static async Task InitAsync()
//// {
//// if (_client != null) return;
//// var options = new SupabaseOptions
//// {
//// AutoConnectRealtime = false,
//// Schema = "public"
//// };
//// _client = new Client(SupabaseUrl, SupabaseAnonKey, options);
//// await _client.InitializeAsync();
//// }
//// // ---------- CREATE (or get existing via UPSERT on PK) ----------
//// public static async Task<PlayerRow> CreateIfMissingAsync(
//// string walletAddress,
//// int totalKills = 0,
//// decimal? averagePlacement = null,
//// decimal? winPercentage = null,
//// long inGameCurrency = 0,
//// Dictionary<string, object> purchasedItems = null)
//// {
//// await InitAsync();
//// var row = new PlayerRow
//// {
//// WalletAddress = walletAddress,
//// TotalKills = totalKills,
//// AveragePlacement = averagePlacement,
//// WinPercentage = winPercentage,
//// InGameCurrency = inGameCurrency,
//// PurchasedItems = purchasedItems ?? new Dictionary<string, object>(),
//// UpdatedAt = DateTimeOffset.UtcNow
//// };
//// var resp = await _client
//// .From<PlayerRow>()
//// .Upsert(row); // upsert by PK
//// return resp.Models.Count > 0 ? resp.Models[0] : row;
//// }
//// // ---------- READ ----------
//// public static async Task<PlayerRow> GetAsync(string walletAddress)
//// {
//// await InitAsync();
//// var resp = await _client
//// .From<PlayerRow>()
//// .Select("*") // 3.5.x requires a string
//// .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
//// .Get();
//// return resp.Models.Count > 0 ? resp.Models[0] : null;
//// }
//// // ---------- PATCH ----------
//// public static async Task<PlayerRow> PatchAsync(
//// string walletAddress,
//// int? totalKills = null,
//// decimal? averagePlacement = null,
//// decimal? winPercentage = null,
//// long? inGameCurrency = null,
//// Dictionary<string, object> purchasedItems = null)
//// {
//// await InitAsync();
//// var updateResp = await _client
//// .From<PlayerRow>()
//// .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
//// .Set(p =>
//// {
//// var dict = new Dictionary<object, object>();
//// if (totalKills.HasValue) dict["total_kills"] = totalKills.Value;
//// if (averagePlacement.HasValue) dict["average_placement"] = averagePlacement.Value;
//// if (winPercentage.HasValue) dict["win_percentage"] = winPercentage.Value;
//// if (inGameCurrency.HasValue) dict["in_game_currency"] = inGameCurrency.Value;
//// if (purchasedItems != null) dict["purchased_items"] = purchasedItems;
//// dict["updated_at"] = DateTimeOffset.UtcNow;
//// return dict;
//// })
//// .Update();
//// return updateResp.Models.Count > 0
//// ? updateResp.Models[0]
//// : await GetAsync(walletAddress);
//// }
//// // ---------- REPLACE (full row) ----------
//// public static async Task<PlayerRow> ReplaceAsync(PlayerRow fullRow)
//// {
//// await InitAsync();
//// if (string.IsNullOrWhiteSpace(fullRow.WalletAddress))
//// throw new ArgumentException("WalletAddress (PK) is required.");
//// fullRow.UpdatedAt = DateTimeOffset.UtcNow;
//// var resp = await _client
//// .From<PlayerRow>()
//// .Upsert(fullRow); // upsert by PK
//// return resp.Models.Count > 0 ? resp.Models[0] : fullRow;
//// }
//// // ---------- DELETE ----------
//// public static async Task<bool> DeleteAsync(string walletAddress)
//// {
//// await InitAsync();
//// await _client
//// .From<PlayerRow>()
//// .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress)
//// .Delete();
//// return true; // PostgREST usually returns empty body on delete
//// }
////}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 69ffe49890f7675448fbe768edb72f7d

View File

@ -33,26 +33,26 @@ MonoBehaviour:
m_Settings:
m_SettingsList:
m_List:
- rid: 1051813860904534127
- rid: 3263039602809372672
- rid: 3067256451524460819
- rid: 1051813860904534128
- rid: 3263039602809372673
- rid: 3067256451524460821
- rid: 1051813860904534129
- rid: 3263039602809372674
- rid: 3067256451524460823
- rid: 3067256451524460824
- rid: 1051813860904534130
- rid: 3263039602809372675
- rid: 3067256451524460826
- rid: 3067256451524460827
- rid: 3067256451524460828
- rid: 1051813860904534131
- rid: 3263039602809372676
- rid: 3067256451524460830
- rid: 1051813860904534132
- rid: 1051813860904534133
- rid: 1051813860904534134
- rid: 1051813860904534135
- rid: 3263039602809372677
- rid: 3263039602809372678
- rid: 3263039602809372679
- rid: 3263039602809372680
- rid: 3067256451524460835
- rid: 1051813860904534136
- rid: 1051813860904534137
- rid: 3263039602809372681
- rid: 3263039602809372682
- rid: 3067256451524460838
m_RuntimeSettings:
m_List:
@ -96,109 +96,6 @@ MonoBehaviour:
references:
version: 2
RefIds:
- rid: 1051813860904534127
type: {class: UniversalRenderPipelineEditorShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
m_AutodeskInteractive: {fileID: 4800000, guid: 0e9d5a909a1f7e84882a534d0d11e49f, type: 3}
m_AutodeskInteractiveTransparent: {fileID: 4800000, guid: 5c81372d981403744adbdda4433c9c11, type: 3}
m_AutodeskInteractiveMasked: {fileID: 4800000, guid: 80aa867ac363ac043847b06ad71604cd, type: 3}
m_TerrainDetailLit: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3}
m_TerrainDetailGrassBillboard: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3}
m_TerrainDetailGrass: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3}
m_DefaultSpeedTree7Shader: {fileID: 4800000, guid: 0f4122b9a743b744abe2fb6a0a88868b, type: 3}
m_DefaultSpeedTree8Shader: {fileID: -6465566751694194690, guid: 9920c1f1781549a46ba081a2a15a16ec, type: 3}
m_DefaultSpeedTree9Shader: {fileID: -6465566751694194690, guid: cbd3e1cc4ae141c42a30e33b4d666a61, type: 3}
- rid: 1051813860904534128
type: {class: Renderer2DResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
m_Version: 0
m_LightShader: {fileID: 4800000, guid: 3f6c848ca3d7bca4bbe846546ac701a1, type: 3}
m_ProjectedShadowShader: {fileID: 4800000, guid: ce09d4a80b88c5a4eb9768fab4f1ee00, type: 3}
m_SpriteShadowShader: {fileID: 4800000, guid: 44fc62292b65ab04eabcf310e799ccf6, type: 3}
m_SpriteUnshadowShader: {fileID: 4800000, guid: de02b375720b5c445afe83cd483bedf3, type: 3}
m_GeometryShadowShader: {fileID: 4800000, guid: 19349a0f9a7ed4c48a27445bcf92e5e1, type: 3}
m_GeometryUnshadowShader: {fileID: 4800000, guid: 77774d9009bb81447b048c907d4c6273, type: 3}
m_FallOffLookup: {fileID: 2800000, guid: 5688ab254e4c0634f8d6c8e0792331ca, type: 3}
m_CopyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3}
m_DefaultLitMaterial: {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
m_DefaultUnlitMaterial: {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2}
m_DefaultMaskMaterial: {fileID: 2100000, guid: 15d0c3709176029428a0da2f8cecf0b5, type: 2}
- rid: 1051813860904534129
type: {class: UniversalRenderPipelineEditorMaterials, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
m_DefaultMaterial: {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_DefaultParticleMaterial: {fileID: 2100000, guid: e823cd5b5d27c0f4b8256e7c12ee3e6d, type: 2}
m_DefaultLineMaterial: {fileID: 2100000, guid: e823cd5b5d27c0f4b8256e7c12ee3e6d, type: 2}
m_DefaultTerrainMaterial: {fileID: 2100000, guid: 594ea882c5a793440b60ff72d896021e, type: 2}
m_DefaultDecalMaterial: {fileID: 2100000, guid: 31d0dcc6f2dd4e4408d18036a2c93862, type: 2}
- rid: 1051813860904534130
type: {class: URPShaderStrippingSetting, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
m_Version: 0
m_StripUnusedPostProcessingVariants: 0
m_StripUnusedVariants: 1
m_StripScreenCoordOverrideVariants: 1
- rid: 1051813860904534131
type: {class: GPUResidentDrawerResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.GPUDriven.Runtime}
data:
m_Version: 0
m_InstanceDataBufferCopyKernels: {fileID: 7200000, guid: f984aeb540ded8b4fbb8a2047ab5b2e2, type: 3}
m_InstanceDataBufferUploadKernels: {fileID: 7200000, guid: 53864816eb00f2343b60e1a2c5a262ef, type: 3}
m_TransformUpdaterKernels: {fileID: 7200000, guid: 2a567b9b2733f8d47a700c3c85bed75b, type: 3}
m_WindDataUpdaterKernels: {fileID: 7200000, guid: fde76746e4fd0ed418c224f6b4084114, type: 3}
m_OccluderDepthPyramidKernels: {fileID: 7200000, guid: 08b2b5fb307b0d249860612774a987da, type: 3}
m_InstanceOcclusionCullingKernels: {fileID: 7200000, guid: f6d223acabc2f974795a5a7864b50e6c, type: 3}
m_OcclusionCullingDebugKernels: {fileID: 7200000, guid: b23e766bcf50ca4438ef186b174557df, type: 3}
m_DebugOcclusionTestPS: {fileID: 4800000, guid: d3f0849180c2d0944bc71060693df100, type: 3}
m_DebugOccluderPS: {fileID: 4800000, guid: b3c92426a88625841ab15ca6a7917248, type: 3}
- rid: 1051813860904534132
type: {class: ProbeVolumeRuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_Version: 1
probeVolumeBlendStatesCS: {fileID: 7200000, guid: a3f7b8c99de28a94684cb1daebeccf5d, type: 3}
probeVolumeUploadDataCS: {fileID: 7200000, guid: 0951de5992461754fa73650732c4954c, type: 3}
probeVolumeUploadDataL2CS: {fileID: 7200000, guid: 6196f34ed825db14b81fb3eb0ea8d931, type: 3}
- rid: 1051813860904534133
type: {class: IncludeAdditionalRPAssets, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_version: 0
m_IncludeReferencedInScenes: 0
m_IncludeAssetsByLabel: 0
m_LabelToInclude:
- rid: 1051813860904534134
type: {class: ProbeVolumeBakingResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_Version: 1
dilationShader: {fileID: 7200000, guid: 6bb382f7de370af41b775f54182e491d, type: 3}
subdivideSceneCS: {fileID: 7200000, guid: bb86f1f0af829fd45b2ebddda1245c22, type: 3}
voxelizeSceneShader: {fileID: 4800000, guid: c8b6a681c7b4e2e4785ffab093907f9e, type: 3}
traceVirtualOffsetCS: {fileID: -6772857160820960102, guid: ff2cbab5da58bf04d82c5f34037ed123, type: 3}
traceVirtualOffsetRT: {fileID: -5126288278712620388, guid: ff2cbab5da58bf04d82c5f34037ed123, type: 3}
skyOcclusionCS: {fileID: -6772857160820960102, guid: 5a2a534753fbdb44e96c3c78b5a6999d, type: 3}
skyOcclusionRT: {fileID: -5126288278712620388, guid: 5a2a534753fbdb44e96c3c78b5a6999d, type: 3}
renderingLayerCS: {fileID: -6772857160820960102, guid: 94a070d33e408384bafc1dea4a565df9, type: 3}
renderingLayerRT: {fileID: -5126288278712620388, guid: 94a070d33e408384bafc1dea4a565df9, type: 3}
- rid: 1051813860904534135
type: {class: STP/RuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_setupCS: {fileID: 7200000, guid: 33be2e9a5506b2843bdb2bdff9cad5e1, type: 3}
m_preTaaCS: {fileID: 7200000, guid: a679dba8ec4d9ce45884a270b0e22dda, type: 3}
m_taaCS: {fileID: 7200000, guid: 3923900e2b41b5e47bc25bfdcbcdc9e6, type: 3}
- rid: 1051813860904534136
type: {class: ProbeVolumeDebugResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_Version: 1
probeVolumeDebugShader: {fileID: 4800000, guid: 3b21275fd12d65f49babb5286f040f2d, type: 3}
probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 3a80877c579b9144ebdcc6d923bca303, type: 3}
probeVolumeSamplingDebugShader: {fileID: 4800000, guid: bf54e6528c79a224e96346799064c393, type: 3}
probeVolumeOffsetDebugShader: {fileID: 4800000, guid: db8bd7436dc2c5f4c92655307d198381, type: 3}
probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 20be25aac4e22ee49a7db76fb3df6de2, type: 3}
numbersDisplayTex: {fileID: 2800000, guid: 73fe53b428c5b3440b7e87ee830b608a, type: 3}
- rid: 1051813860904534137
type: {class: ProbeVolumeGlobalSettings, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_Version: 1
m_ProbeVolumeDisableStreamingAssets: 0
- rid: 3067256451524460819
type: {class: UniversalRenderPipelineRuntimeShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
@ -263,3 +160,106 @@ MonoBehaviour:
m_ExportShaderVariants: 1
m_ShaderVariantLogLevel: 0
m_StripRuntimeDebugShaders: 1
- rid: 3263039602809372672
type: {class: UniversalRenderPipelineEditorShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
m_AutodeskInteractive: {fileID: 4800000, guid: 0e9d5a909a1f7e84882a534d0d11e49f, type: 3}
m_AutodeskInteractiveTransparent: {fileID: 4800000, guid: 5c81372d981403744adbdda4433c9c11, type: 3}
m_AutodeskInteractiveMasked: {fileID: 4800000, guid: 80aa867ac363ac043847b06ad71604cd, type: 3}
m_TerrainDetailLit: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3}
m_TerrainDetailGrassBillboard: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3}
m_TerrainDetailGrass: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3}
m_DefaultSpeedTree7Shader: {fileID: 4800000, guid: 0f4122b9a743b744abe2fb6a0a88868b, type: 3}
m_DefaultSpeedTree8Shader: {fileID: -6465566751694194690, guid: 9920c1f1781549a46ba081a2a15a16ec, type: 3}
m_DefaultSpeedTree9Shader: {fileID: -6465566751694194690, guid: cbd3e1cc4ae141c42a30e33b4d666a61, type: 3}
- rid: 3263039602809372673
type: {class: Renderer2DResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
m_Version: 0
m_LightShader: {fileID: 4800000, guid: 3f6c848ca3d7bca4bbe846546ac701a1, type: 3}
m_ProjectedShadowShader: {fileID: 4800000, guid: ce09d4a80b88c5a4eb9768fab4f1ee00, type: 3}
m_SpriteShadowShader: {fileID: 4800000, guid: 44fc62292b65ab04eabcf310e799ccf6, type: 3}
m_SpriteUnshadowShader: {fileID: 4800000, guid: de02b375720b5c445afe83cd483bedf3, type: 3}
m_GeometryShadowShader: {fileID: 4800000, guid: 19349a0f9a7ed4c48a27445bcf92e5e1, type: 3}
m_GeometryUnshadowShader: {fileID: 4800000, guid: 77774d9009bb81447b048c907d4c6273, type: 3}
m_FallOffLookup: {fileID: 2800000, guid: 5688ab254e4c0634f8d6c8e0792331ca, type: 3}
m_CopyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3}
m_DefaultLitMaterial: {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
m_DefaultUnlitMaterial: {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2}
m_DefaultMaskMaterial: {fileID: 2100000, guid: 15d0c3709176029428a0da2f8cecf0b5, type: 2}
- rid: 3263039602809372674
type: {class: UniversalRenderPipelineEditorMaterials, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
m_DefaultMaterial: {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_DefaultParticleMaterial: {fileID: 2100000, guid: e823cd5b5d27c0f4b8256e7c12ee3e6d, type: 2}
m_DefaultLineMaterial: {fileID: 2100000, guid: e823cd5b5d27c0f4b8256e7c12ee3e6d, type: 2}
m_DefaultTerrainMaterial: {fileID: 2100000, guid: 594ea882c5a793440b60ff72d896021e, type: 2}
m_DefaultDecalMaterial: {fileID: 2100000, guid: 31d0dcc6f2dd4e4408d18036a2c93862, type: 2}
- rid: 3263039602809372675
type: {class: URPShaderStrippingSetting, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime}
data:
m_Version: 0
m_StripUnusedPostProcessingVariants: 0
m_StripUnusedVariants: 1
m_StripScreenCoordOverrideVariants: 1
- rid: 3263039602809372676
type: {class: GPUResidentDrawerResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.GPUDriven.Runtime}
data:
m_Version: 0
m_InstanceDataBufferCopyKernels: {fileID: 7200000, guid: f984aeb540ded8b4fbb8a2047ab5b2e2, type: 3}
m_InstanceDataBufferUploadKernels: {fileID: 7200000, guid: 53864816eb00f2343b60e1a2c5a262ef, type: 3}
m_TransformUpdaterKernels: {fileID: 7200000, guid: 2a567b9b2733f8d47a700c3c85bed75b, type: 3}
m_WindDataUpdaterKernels: {fileID: 7200000, guid: fde76746e4fd0ed418c224f6b4084114, type: 3}
m_OccluderDepthPyramidKernels: {fileID: 7200000, guid: 08b2b5fb307b0d249860612774a987da, type: 3}
m_InstanceOcclusionCullingKernels: {fileID: 7200000, guid: f6d223acabc2f974795a5a7864b50e6c, type: 3}
m_OcclusionCullingDebugKernels: {fileID: 7200000, guid: b23e766bcf50ca4438ef186b174557df, type: 3}
m_DebugOcclusionTestPS: {fileID: 4800000, guid: d3f0849180c2d0944bc71060693df100, type: 3}
m_DebugOccluderPS: {fileID: 4800000, guid: b3c92426a88625841ab15ca6a7917248, type: 3}
- rid: 3263039602809372677
type: {class: ProbeVolumeRuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_Version: 1
probeVolumeBlendStatesCS: {fileID: 7200000, guid: a3f7b8c99de28a94684cb1daebeccf5d, type: 3}
probeVolumeUploadDataCS: {fileID: 7200000, guid: 0951de5992461754fa73650732c4954c, type: 3}
probeVolumeUploadDataL2CS: {fileID: 7200000, guid: 6196f34ed825db14b81fb3eb0ea8d931, type: 3}
- rid: 3263039602809372678
type: {class: IncludeAdditionalRPAssets, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_version: 0
m_IncludeReferencedInScenes: 0
m_IncludeAssetsByLabel: 0
m_LabelToInclude:
- rid: 3263039602809372679
type: {class: ProbeVolumeBakingResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_Version: 1
dilationShader: {fileID: 7200000, guid: 6bb382f7de370af41b775f54182e491d, type: 3}
subdivideSceneCS: {fileID: 7200000, guid: bb86f1f0af829fd45b2ebddda1245c22, type: 3}
voxelizeSceneShader: {fileID: 4800000, guid: c8b6a681c7b4e2e4785ffab093907f9e, type: 3}
traceVirtualOffsetCS: {fileID: -6772857160820960102, guid: ff2cbab5da58bf04d82c5f34037ed123, type: 3}
traceVirtualOffsetRT: {fileID: -5126288278712620388, guid: ff2cbab5da58bf04d82c5f34037ed123, type: 3}
skyOcclusionCS: {fileID: -6772857160820960102, guid: 5a2a534753fbdb44e96c3c78b5a6999d, type: 3}
skyOcclusionRT: {fileID: -5126288278712620388, guid: 5a2a534753fbdb44e96c3c78b5a6999d, type: 3}
renderingLayerCS: {fileID: -6772857160820960102, guid: 94a070d33e408384bafc1dea4a565df9, type: 3}
renderingLayerRT: {fileID: -5126288278712620388, guid: 94a070d33e408384bafc1dea4a565df9, type: 3}
- rid: 3263039602809372680
type: {class: STP/RuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_setupCS: {fileID: 7200000, guid: 33be2e9a5506b2843bdb2bdff9cad5e1, type: 3}
m_preTaaCS: {fileID: 7200000, guid: a679dba8ec4d9ce45884a270b0e22dda, type: 3}
m_taaCS: {fileID: 7200000, guid: 3923900e2b41b5e47bc25bfdcbcdc9e6, type: 3}
- rid: 3263039602809372681
type: {class: ProbeVolumeDebugResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_Version: 1
probeVolumeDebugShader: {fileID: 4800000, guid: 3b21275fd12d65f49babb5286f040f2d, type: 3}
probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 3a80877c579b9144ebdcc6d923bca303, type: 3}
probeVolumeSamplingDebugShader: {fileID: 4800000, guid: bf54e6528c79a224e96346799064c393, type: 3}
probeVolumeOffsetDebugShader: {fileID: 4800000, guid: db8bd7436dc2c5f4c92655307d198381, type: 3}
probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 20be25aac4e22ee49a7db76fb3df6de2, type: 3}
numbersDisplayTex: {fileID: 2800000, guid: 73fe53b428c5b3440b7e87ee830b608a, type: 3}
- rid: 3263039602809372682
type: {class: ProbeVolumeGlobalSettings, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime}
data:
m_Version: 1
m_ProbeVolumeDisableStreamingAssets: 0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5622920d4cb64fb45abadeb06d20725d
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,3 +1,5 @@
using System;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@ -18,7 +20,7 @@ namespace TPSBR.UI
private AudioSetup _openSound;
// UIView INTERFACE
private bool _hasReportedStats;
protected override void OnInitialize()
{
base.OnInitialize();
@ -49,14 +51,52 @@ namespace TPSBR.UI
}
PlaySound(_openSound);
if (_hasReportedStats == false)
{
_ = ReportGameCompletedAsync();
}
Global.Networking.StopGameOnDisconnect();
}
private async Task ReportGameCompletedAsync()
{
_hasReportedStats = true;
try
{
if (!PlayerPrefs.HasKey("WALLET_ADDRESS"))
return;
var wallet = PlayerPrefs.GetString("WALLET_ADDRESS");
if (string.IsNullOrWhiteSpace(wallet))
return;
var localPlayer = Context.NetworkGame.GetPlayer(Context.LocalPlayerRef);
if (localPlayer == null)
return;
await GameDb.IncGamesPlayedAsync(wallet);
int placement = localPlayer.Statistics.Position;
if (placement > 0)
{
await GameDb.SendGameWinAsync(wallet, placement);
}
else
{
Debug.LogWarning("[Supabase] Skipped reporting placement because it was not set.");
}
}
catch (Exception ex)
{
Debug.LogError($"[Supabase] Failed to report game completion: {ex.Message}");
}
}
protected override void OnDeinitialize()
{
_restartButton.onClick.RemoveListener(OnRestartButton);
_hasReportedStats = false;
base.OnDeinitialize();
}

View File

@ -1,7 +1,8 @@
using UnityEngine;
using Fusion;
using TMPro;
using System.Threading.Tasks;
using System;
namespace TPSBR.UI
{
public class UIGameplayView : UIView
@ -195,6 +196,18 @@ namespace TPSBR.UI
Sound = _playerDeathSound,
});
}
//else if (killData.KillerRef == Context.ObservedPlayerRef)
//{
// bool eliminated = killerPlayer != null ? killerPlayer.Statistics.IsEliminated : false;
// _events.ShowEvent(new GameplayEventData
// {
// Name = eliminated == true ? "ENEMY ELIMINATED" : "ENEMY KILLED",
// Description = victimPlayer != null ? victimPlayer.Nickname : "",
// Color = _enemyKilledColor,
// Sound = _enemyKilledSound,
// });
//}
else if (killData.KillerRef == Context.ObservedPlayerRef)
{
bool eliminated = killerPlayer != null ? killerPlayer.Statistics.IsEliminated : false;
@ -207,6 +220,30 @@ namespace TPSBR.UI
Sound = _enemyKilledSound,
});
}
if (killData.KillerRef == Context.LocalPlayerRef)
{
_ = ReportKillAsync();
}
}
private async Task ReportKillAsync()
{
try
{
if (PlayerPrefs.HasKey("WALLET_ADDRESS") == false)
return;
var wallet = PlayerPrefs.GetString("WALLET_ADDRESS");
if (string.IsNullOrWhiteSpace(wallet) == true)
return;
await GameDb.AddKillAsync(wallet);
}
catch (Exception ex)
{
Debug.LogError($"[Supabase] Failed to report kill: {ex.Message}");
}
}
private void OnPlayerEliminated(PlayerRef playerRef)

View File

@ -1,3 +1,5 @@
using UnityEngine;
namespace TPSBR.UI
{
public class MenuUI : SceneUI
@ -10,11 +12,12 @@ namespace TPSBR.UI
Context.Input.RequestCursorVisibility(true, ECursorStateSource.Menu);
if (Context.PlayerData.Nickname.HasValue() == false)
{
var changeNicknameView = Open<UIChangeNicknameView>();
changeNicknameView.SetData("ENTER NICKNAME", true);
}
OnWalletConnected(PlayerPrefs.GetString("WALLET_ADDRESS"));
//if (Context.PlayerData.Nickname.HasValue() == false)
//{
// var changeNicknameView = Open<UIChangeNicknameView>();
// changeNicknameView.SetData("ENTER NICKNAME", true);
//}
}
protected override void OnDeinitializeInternal()
@ -47,5 +50,32 @@ namespace TPSBR.UI
Global.Networking.ClearErrorStatus();
}
}
public async void OnWalletConnected(string wallet) // call this from your wallet SDK
{
try
{
// makes sure the row exists (display_name = "" on first create)
string playername= await GameDb.GetPlayerNameAsync(wallet);
if (playername == "")
{
var changeNicknameView = Open<UIChangeNicknameView>();
changeNicknameView.SetData("ENTER NICKNAME", true);
}
else
{
Context.PlayerData.Nickname = playername.ToString();
Debug.Log($"Player ensured for {wallet}");
}
}
catch (System.Exception e)
{
Debug.LogError(e);
}
}
}
}

View File

@ -1,5 +1,6 @@
using UnityEngine;
using TMPro;
using UnityEngine.Windows;
namespace TPSBR.UI
{
@ -43,7 +44,7 @@ namespace TPSBR.UI
protected override void OnOpen()
{
base.OnOpen();
GetName();
string currentNickname = Context.PlayerData.Nickname;
if (currentNickname.HasValue() == false)
{
@ -51,6 +52,7 @@ namespace TPSBR.UI
}
else
{
_name.text = Context.PlayerData.Nickname;
}
}
@ -67,8 +69,23 @@ namespace TPSBR.UI
private void OnConfirmButton()
{
Context.PlayerData.Nickname = _name.text;
SetName(PlayerPrefs.GetString("WALLET_ADDRESS"),Context.PlayerData.Nickname.ToString());
Close();
}
public async void SetName(string walletaddress,string playername)
{
Debug.Log("Setting name for wallet address" + walletaddress.ToString());
var rec = await GameDb.SetNameIfEmptyAsync(walletaddress, playername);
}
public async void GetName()
{
var playername= await GameDb.GetPlayerNameAsync(PlayerPrefs.GetString("WALLET_ADDRESS"));
Context.PlayerData.Nickname = playername;
_name.text = playername;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -0,0 +1,156 @@
fileFormatVersion: 2
guid: 1f9eec5f79cc6034f81f7a1eea6afb3d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 2
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -0,0 +1,156 @@
fileFormatVersion: 2
guid: b7331575412f0084bb3b5f12872eb763
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 2
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,42 @@
using Reown.AppKit.Unity;
using System.Threading.Tasks;
using UnityEngine;
public class WalletConnectController : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
public async void Start()
{
var config = new AppKitConfig();
await AppKit.InitializeAsync(config);
}
// Update is called once per frame
void Update()
{
}
public async Task ResumeSession()
{
// Try to resume account connection from the last session
var resumed = await AppKit.ConnectorController.TryResumeSessionAsync();
if (resumed)
{
// Continue to the game
ShowWalletConnected();
}
else
{
// Connect account
AppKit.AccountConnected += (_, e) => ShowWalletConnected();
AppKit.OpenModal();
}
}
public void ShowWalletConnected()
{
Debug.Log("Connected");
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7caa00906f77d4e44a62da79d4795981

Binary file not shown.

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2007 James Newton-King
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Binary file not shown.

View File

@ -0,0 +1,71 @@
# ![Logo](https://raw.githubusercontent.com/JamesNK/Newtonsoft.Json/master/Doc/icons/logo.jpg) Json.NET
[![NuGet version (Newtonsoft.Json)](https://img.shields.io/nuget/v/Newtonsoft.Json.svg?style=flat-square)](https://www.nuget.org/packages/Newtonsoft.Json/)
[![Build status](https://dev.azure.com/jamesnk/Public/_apis/build/status/JamesNK.Newtonsoft.Json?branchName=master)](https://dev.azure.com/jamesnk/Public/_build/latest?definitionId=8)
Json.NET is a popular high-performance JSON framework for .NET
## Serialize JSON
```csharp
Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Sizes = new string[] { "Small" };
string json = JsonConvert.SerializeObject(product);
// {
// "Name": "Apple",
// "Expiry": "2008-12-28T00:00:00",
// "Sizes": [
// "Small"
// ]
// }
```
## Deserialize JSON
```csharp
string json = @"{
'Name': 'Bad Boys',
'ReleaseDate': '1995-4-7T00:00:00',
'Genres': [
'Action',
'Comedy'
]
}";
Movie m = JsonConvert.DeserializeObject<Movie>(json);
string name = m.Name;
// Bad Boys
```
## LINQ to JSON
```csharp
JArray array = new JArray();
array.Add("Manual text");
array.Add(new DateTime(2000, 5, 23));
JObject o = new JObject();
o["MyArray"] = array;
string json = o.ToString();
// {
// "MyArray": [
// "Manual text",
// "2000-05-23T00:00:00"
// ]
// }
```
## Links
- [Homepage](https://www.newtonsoft.com/json)
- [Documentation](https://www.newtonsoft.com/json/help)
- [NuGet Package](https://www.nuget.org/packages/Newtonsoft.Json)
- [Release Notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Contributing Guidelines](https://github.com/JamesNK/Newtonsoft.Json/blob/master/CONTRIBUTING.md)
- [License](https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/json.net)

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

26
Packages/Supabase.Core.1.0.0/README.md vendored Normal file
View File

@ -0,0 +1,26 @@
<p align="center">
<img width="300" src=".github/supabase-core.png"/>
</p>
<p align="center">
<img src="https://github.com/supabase-community/core-csharp/workflows/Build%20And%20Test/badge.svg"/>
<a href="https://www.nuget.org/packages/supabase-core/">
<img src="https://img.shields.io/nuget/vpre/supabase-core"/>
</a>
</p>
This repo contains shared resources for the [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
repo and its dependent libraries.
## Package made possible through the efforts of:
Join the ranks! See a problem? Help fix it!
<a href="https://github.com/supabase-community/core-csharp/graphs/contributors">
<img src="https://contrib.rocks/image?repo=supabase-community/core-csharp" />
</a>
Made with [contrib.rocks](https://contrib.rocks/preview?repo=supabase-community%core-csharp).
## Contributing
We are more than happy to have contributions! Please submit a PR.

Binary file not shown.

BIN
Packages/Supabase.Core.1.0.0/icon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

View File

@ -0,0 +1,114 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Supabase.Core</name>
</assembly>
<members>
<member name="T:Supabase.Core.Attributes.MapToAttribute">
<summary>
Used internally to add a string value to a C# field.
</summary>
</member>
<member name="P:Supabase.Core.Attributes.MapToAttribute.Mapping">
<summary>
The externally specified target value.
</summary>
</member>
<member name="P:Supabase.Core.Attributes.MapToAttribute.Formatter">
<summary>
A formatter to be passed into the <see cref="M:System.String.ToString" /> method.
</summary>
</member>
<member name="M:Supabase.Core.Attributes.MapToAttribute.#ctor(System.String,System.String)">
<summary>
Creates a Mapping to be used internally.
For example, specifying an Enum that has a different string value elsewhere.
</summary>
<param name="mapping"></param>
<param name="formatter"></param>
</member>
<member name="T:Supabase.Core.Extensions.DictionaryExtensions">
<summary>
Extensions for the `Dictionary` Classes
</summary>
</member>
<member name="M:Supabase.Core.Extensions.DictionaryExtensions.MergeLeft``3(``0,System.Collections.Generic.IDictionary{``1,``2}[])">
<summary>
Merges two dictionaries, allowing overwrite priorities leftward.
Works in C#3/VS2008:
Returns a new dictionary of this ... others merged leftward.
Keeps the type of 'this', which must be default-instantiable.
Example:
result = map.MergeLeft(other1, other2, ...)
From: https://stackoverflow.com/a/2679857/3629438
</summary>
<param name="me"></param>
<param name="others"></param>
<typeparam name="T"></typeparam>
<typeparam name="K"></typeparam>
<typeparam name="V"></typeparam>
<returns></returns>
</member>
<member name="T:Supabase.Core.Helpers">
<summary>
Shortcut Methods, mostly focused on getting attributes from class properties and enums.
</summary>
</member>
<member name="M:Supabase.Core.Helpers.GetPropertyValue``1(System.Object,System.String)">
<summary>
Returns the current value from a given class property
</summary>
<param name="obj"></param>
<param name="propName"></param>
<typeparam name="T"></typeparam>
<returns></returns>
</member>
<member name="M:Supabase.Core.Helpers.GetCustomAttribute``1(System.Object)">
<summary>
Returns a cast Custom Attribute from a given object.
</summary>
<param name="obj"></param>
<typeparam name="T"></typeparam>
<returns></returns>
</member>
<member name="M:Supabase.Core.Helpers.GetCustomAttribute``1(System.Type)">
<summary>
Returns a cast Custom Attribute from a given type.
</summary>
<param name="type"></param>
<typeparam name="T"></typeparam>
<returns></returns>
</member>
<member name="M:Supabase.Core.Helpers.GetMappedToAttr(System.Enum)">
<summary>
Shortcut method for accessing a `MapTo` attribute, combined with an Enum.
</summary>
<param name="obj"></param>
<returns></returns>
</member>
<member name="T:Supabase.Core.Interfaces.IGettableHeaders">
<summary>
Used for classes that need to retrieve `Headers` externally.
</summary>
</member>
<member name="P:Supabase.Core.Interfaces.IGettableHeaders.GetHeaders">
<summary>
An executable `Func` that returns a dictionary of headers to be appended onto a request.
</summary>
</member>
<member name="T:Supabase.Core.Util">
<summary>
A shared utilities class
</summary>
</member>
<member name="M:Supabase.Core.Util.GetAssemblyVersion(System.Type)">
<summary>
Returns the Current Assembly version - this is usually appended into the headers of each request.
</summary>
<param name="clientType"></param>
<returns></returns>
</member>
</members>
</doc>

Binary file not shown.

View File

@ -0,0 +1,362 @@
<p align="center">
<img width="300" src=".github/logo.png"/>
</p>
<p align="center">
<img src="https://github.com/supabase/postgrest-csharp/workflows/Build%20And%20Test/badge.svg"/>
<a href="https://www.nuget.org/packages/Supabase.Postgrest/">
<img src="https://img.shields.io/nuget/vpre/Supabase.Postgrest"/>
</a>
</p>
---
## [Notice]: v4.0.0 renames this package from `postgrest-csharp` to `Supabase.Postgrest`. Which includes changing the namespace from `Postgrest` to `Supabase.Postgrest`.
## Now supporting (many) LINQ expressions!
```c#
await client.Table<Movie>()
.Select(x => new object[] { x.Id, x.Name, x.Tags, x.ReleaseDate })
.Where(x => x.Tags.Contains("Action") || x.Tags.Contains("Adventure"))
.Order(x => x.ReleaseDate, Ordering.Descending)
.Get();
await client.Table<Movie>()
.Set(x => x.WatchedAt, DateTime.Now)
.Where(x => x.Id == "11111-22222-33333-44444")
// Or .Filter(x => x.Id, Operator.Equals, "11111-22222-33333-44444")
.Update();
```
---
Documentation can be found [here](https://supabase-community.github.io/postgrest-csharp/api/Postgrest.html).
Postgrest-csharp is written primarily as a helper library
for [supabase/supabase-csharp](https://github.com/supabase/supabase-csharp), however, it should be easy enough to use
outside of the supabase ecosystem.
The bulk of this library is a translation and c-sharp-ification of
the [supabase/postgrest-js](https://github.com/supabase/postgrest-js) library.
## Getting Started
Postgrest-csharp is _heavily_ dependent on Models deriving from `BaseModel`. To interact with the API, one must have the
associated
model specified.
To use this library on the Supabase Hosted service but separately from the `supabase-csharp`, you'll need to specify
your url and public key like so:
```c#
var auth = new Supabase.Gotrue.Client(new ClientOptions<Session>
{
Url = "https://PROJECT_ID.supabase.co/auth/v1",
Headers = new Dictionary<string, string>
{
{ "apikey", SUPABASE_PUBLIC_KEY },
{ "Authorization", $"Bearer {SUPABASE_USER_TOKEN}" }
}
})
```
Leverage `Table`,`PrimaryKey`, and `Column` attributes to specify names of classes/properties that are different from
their C# Versions.
```c#
[Table("messages")]
public class Message : BaseModel
{
[PrimaryKey("id")]
public int Id { get; set; }
[Column("username")]
public string UserName { get; set; }
[Column("channel_id")]
public int ChannelId { get; set; }
public override bool Equals(object obj)
{
return obj is Message message &&
Id == message.Id;
}
public override int GetHashCode()
{
return HashCode.Combine(Id);
}
}
```
Utilizing the client is then just a matter of instantiating it and specifying the Model one is working with.
```c#
void Initialize()
{
var client = new Client("http://localhost:3000");
// Get All Messages
var response = await client.Table<Message>().Get();
List<Message> models = response.Models;
// Insert
var newMessage = new Message { UserName = "acupofjose", ChannelId = 1 };
await client.Table<Message>().Insert();
// Update
var model = response.Models.First();
model.UserName = "elrhomariyounes";
await model.Update();
// Delete
await response.Models.Last().Delete();
}
```
## Foreign Keys, Join Tables, and Relationships
The Postgrest server does introspection on relationships between tables and supports returning query data from
tables with these included. **Foreign key constrains are required for postgrest to detect these relationships.**
This library implements the attribute, `Reference` to specify on a model when a relationship should be included in a
query.
- [One-to-one Relationships](https://postgrest.org/en/stable/api.html#one-to-one-relationships): One-to-one
relationships are detected if theres an unique constraint on a foreign key.
- [One-to-many Relationships](https://postgrest.org/en/stable/api.html#one-to-many-relationships): The inverse
one-to-many relationship between two tables is detected based on the foreign key reference.
- [Many-to-many Relationships](https://postgrest.org/en/stable/api.html#many-to-many-relationships): Many-to-many
relationships are detected based on the join table. The join table must contain foreign keys to other two tables and
they must be part of its composite key.
Given the following schema:
![example schema](.github/postgrest-relationship-example.drawio.png)
We can define the following models:
```c#
[Table("movie")]
public class Movie : BaseModel
{
[PrimaryKey("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
[Reference(typeof(Person))]
public List<Person> Persons { get; set; }
[Column("created_at")]
public DateTime CreatedAt { get; set; }
}
[Table("person")]
public class Person : BaseModel
{
[PrimaryKey("id")]
public int Id { get; set; }
[Column("first_name")]
public string FirstName { get; set; }
[Column("last_name")]
public string LastName { get; set; }
[Reference(typeof(Profile))]
public Profile Profile { get; set; }
[Column("created_at")]
public DateTime CreatedAt { get; set; }
}
[Table("profile")]
public class Profile : BaseModel
{
[Column("email")]
public string Email { get; set; }
}
```
**Note that each related model should inherit `BaseModel` and specify its `Table` and `Column` attributes as usual.**
The `Reference` Attribute by default will include the referenced model in all GET queries on the table (this can be
disabled
in its constructor).
As such, a query on the `Movie` model (given the above) would return something like:
```js
[
{
id: 1,
created_at: "2022-08-20T00:29:45.400188",
name: "Top Gun: Maverick",
person: [
{
id: 1,
created_at: "2022-08-20T00:30:02.120528",
first_name: "Tom",
last_name: "Cruise",
profile: {
profile_id: 1,
email: "tom.cruise@supabase.io",
created_at: "2022-08-20T00:30:33.72443"
}
},
{
id: 3,
created_at: "2022-08-20T00:30:33.72443",
first_name: "Bob",
last_name: "Saggett",
profile: {
profile_id: 3,
email: "bob.saggett@supabase.io",
created_at: "2022-08-20T00:30:33.72443"
}
}
]
},
// ...
]
```
### Circular References
Circular relations can be added between models, however, circular relations should only be parsed one level deep for
models. For example, given the
models [here](https://github.com/supabase-community/postgrest-csharp/blob/master/PostgrestTests/Models/LinkedModels.cs),
a raw response would look like the following (note that the `Person` object returns the root `Movie` and
the `Person->Profile` returns its root `Person` object).
If desired, this can be avoided by making specific join models that do not have the circular references.
```json
[
{
"id": "68722a22-6a6b-4410-a955-b4eb8ca7953f",
"created_at": "0001-01-01T05:51:00",
"name": "Supabase in Action",
"person": [
{
"id": "6aa849d8-dd09-4932-bc6f-6fe3b585e87f",
"first_name": "John",
"last_name": "Doe",
"created_at": "0001-01-01T05:51:00",
"movie": [
{
"id": "68722a22-6a6b-4410-a955-b4eb8ca7953f",
"name": "Supabase in Action",
"created_at": "0001-01-01T05:51:00"
}
],
"profile": {
"person_id": "6aa849d8-dd09-4932-bc6f-6fe3b585e87f",
"email": "john.doe@email.com",
"created_at": "0001-01-01T05:51:00",
"person": {
"id": "6aa849d8-dd09-4932-bc6f-6fe3b585e87f",
"first_name": "John",
"last_name": "Doe",
"created_at": "0001-01-01T05:51:00"
}
}
},
{
"id": "07abc67f-bf7d-4865-b2c0-76013dc2811f",
"first_name": "Jane",
"last_name": "Buck",
"created_at": "0001-01-01T05:51:00",
"movie": [
{
"id": "68722a22-6a6b-4410-a955-b4eb8ca7953f",
"name": "Supabase in Action",
"created_at": "0001-01-01T05:51:00"
}
],
"profile": {
"person_id": "07abc67f-bf7d-4865-b2c0-76013dc2811f",
"email": "jane.buck@email.com",
"created_at": "0001-01-01T05:51:00",
"person": {
"id": "07abc67f-bf7d-4865-b2c0-76013dc2811f",
"first_name": "Jane",
"last_name": "Buck",
"created_at": "0001-01-01T05:51:00"
}
}
}
]
}
]
```
### Top Level Filtering
**By default** relations expect to be used as top level filters on a query. If following the models above, this would
mean that a `Movie` with no `Person` relations on it would not return on a query **unless** the `Relation`
has `useInnerJoin` set to `false`:
The following model would return any movie, even if there are no `Person` models associated with it:
```c#
[Table("movie")]
public class Movie : BaseModel
{
[PrimaryKey("id")]
public string Id { get; set; }
[Column("name")]
public string? Name { get; set; }
[Reference(typeof(Person), useInnerJoin: false)]
public List<Person> People { get; set; } = new();
}
```
**Further Notes**:
- Postgrest _does not support nested inserts or upserts_. Relational keys on models will be ignored when attempting to
insert or upsert on a root model.
- The `Relation` attribute uses reflection to only select the attributes specified on the Class Model (i.e.
the `Profile` model has a property only for `email`, only the property will be requested in the query).
## Status
- [x] Connects to PostgREST Server
- [x] Authentication
- [x] Basic Query Features
- [x] CRUD
- [x] Single
- [x] Range (to & from)
- [x] Limit
- [x] Limit w/ Foreign Key
- [x] Offset
- [x] Offset w/ Foreign Key
- [x] Advanced Query Features
- [x] Filters
- [x] Ordering
- [ ] Custom Serializers
- [ ] [Postgres Range](https://www.postgresql.org/docs/9.3/rangetypes.html)
- [x] `int4range`, `int8range`
- [ ] `numrange`
- [ ] `tsrange`, `tstzrange`, `daterange`
- [x] Models
- [x] `BaseModel` to derive from
- [x] Coercion of data into Models
- [x] Unit Testing
- [x] Nuget Package and Release
## Package made possible through the efforts of:
| <img src="https://github.com/acupofjose.png" width="150" height="150"> | <img src="https://github.com/elrhomariyounes.png" width="150" height="150"> |
|:----------------------------------------------------------------------:|:---------------------------------------------------------------------------:|
| [acupofjose](https://github.com/acupofjose) | [elrhomariyounes](https://github.com/elrhomariyounes) |
## Contributing
We are more than happy to have contributions! Please submit a PR.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,294 @@
# Supabase.Realtime
[![Build and Test](https://github.com/supabase-community/realtime-csharp/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/supabase-community/realtime-csharp/actions/workflows/build-and-test.yml)
[![NuGet](https://img.shields.io/nuget/vpre/Supabase.Realtime)](https://www.nuget.org/packages/Supabase.Realtime/)
## [Notice]: v7.0.0 renames this package from `realtime-csharp` to `Supabase.Realtime`. The depreciation notice has been set in NuGet. The API remains the same.
## BREAKING CHANGES MOVING FROM v5.x.x to v6.x.x
- The realtime client now takes a "fail-fast" approach. On establishing an initial connection, client will throw
a `RealtimeException` in `ConnectAsync()` if the socket server is unreachable. After an initial connection has been
established, the **client will continue attempting reconnections indefinitely until disconnected.**
- [Major, New] C# `EventHandlers` have been changed to `delegates`. This should allow for cleaner event data access over
the previous subclassed `EventArgs` setup. Events are scoped accordingly. For example, the `RealtimeSocket` error
handlers will receive events regarding socket connectivity; whereas the `RealtimeChannel` error handlers will receive
events according to `Channel` joining/leaving/etc. This is implemented with the following methods prefixed by (
Add/Remove/Clear):
- `RealtimeBroadcast.AddBroadcastEventHandler`
- `RealtimePresence.AddPresenceEventHandler`
- `RealtimeSocket.AddStateChangedHandler`
- `RealtimeSocket.AddMessageReceivedHandler`
- `RealtimeSocket.AddHeartbeatHandler`
- `RealtimeSocket.AddErrorHandler`
- `RealtimeClient.AddDebugHandler`
- `RealtimeClient.AddStateChangedHandler`
- `RealtimeChannel.AddPostgresChangeHandler`
- `RealtimeChannel.AddMessageReceivedHandler`
- `RealtimeChannel.AddErrorHandler`
- `Push.AddMessageReceivedHandler`
- [Major, new] `ClientOptions.Logger` has been removed in favor of `Client.AddDebugHandler()` which allows for
implementing custom logging solutions if desired.
- A simple logger can be set up with the following:
```c#
client.AddDebugHandler((sender, message, exception) => Debug.WriteLine(message));
```
- [Major] `Connect()` has been marked `Obsolete` in favor of `ConnectAsync()`
- Custom reconnection logic has been removed in favor of using the built-in logic from `Websocket.Client@4.6.1`.
- Exceptions that are handled within this library have been marked as `RealtimeException`s.
- The local, docker-composed test suite has been brought back (as opposed to remotely testing on live supabase servers)
to test against.
- Comments have been added throughout the entire codebase and an `XML` file is now generated on build.
---
**See realtime-csharp in action [here](https://multiplayer-csharp.azurewebsites.net/).**
`realtime-csharp` is written as a client library for [supabase/realtime](https://github.com/supabase/realtime).
Documentation can be
found [here](https://supabase-community.github.io/realtime-csharp/api/Supabase.Realtime.Client.html).
The bulk of this library is a translation and c-sharp-ification of
the [supabase/realtime-js](https://github.com/supabase/realtime-js) library.
**The Websocket-sharp implementation that Realtime-csharp is dependent on does _not_ support TLS1.3**
## Getting Started
Care was had to make this API as _easy<sup>tm</sup>_ to interact with as possible. `Connect()` and `Subscribe()`
have `await`-able signatures
which allow Users to be assured that a connection exists prior to interacting with it.
```c#
var endpoint = "ws://realtime-dev.localhost:4000/socket";
client = new Client(endpoint);
await client.ConnectAsync();
// Shorthand for registering a postgres_changes subscription
var channel = client.Channel("realtime", "public", "todos");
// Listen to Updates
channel.AddPostgresChangeHandler(ListenType.Updates, (_, change) =>
{
var model = change.Model<Todo>();
var oldModel = change.OldModel<Todo>();
});
await channel.Subscribe();
```
Leveraging `Postgrest.BaseModel`s, one ought to be able to coerce SocketResponse Records into their associated models by
calling:
```c#
// ...
var channel = client.Channel("realtime", "public", "users");
channel.AddPostgresChangeHandler(ListenType.Inserts, (_, change) =>
{
var model = change.Model<Todo>();
});
await channel.Subscribe();
```
## Broadcast
"Broadcast follows the publish-subscribe pattern where a client publishes messages to a channel with a unique
identifier. For example, a user could send a message to a channel with id room-1.
Other clients can elect to receive the message in real-time by subscribing to the channel with id room-1. If these
clients are online and subscribed then they will receive the message.
Broadcast works by connecting your client to the nearest Realtime server, which will communicate with other servers to
relay messages to other clients.
A common use-case is sharing a user's cursor position with other clients in an online game."
[Find more information here](https://supabase.com/docs/guides/realtime#broadcast)
**Given the following model (`CursorBroadcast`):**
```c#
class MouseBroadcast : BaseBroadcast<MouseStatus> { }
class MouseStatus
{
[JsonProperty("mouseX")]
public float MouseX { get; set; }
[JsonProperty("mouseY")]
public float MouseY { get; set; }
[JsonProperty("userId")]
public string UserId { get; set; }
}
```
**Listen for typed broadcast events**:
```c#
var channel = supabase.Realtime.Channel("cursor");
var broadcast = channel.Register<MouseBroadcast>(false, true);
broadcast.AddBroadcastEventHandler((sender, _) =>
{
// Retrieved typed model.
var state = broadcast.Current();
Debug.WriteLine($"{state.Payload}: {state.Payload.MouseX}:{state.Payload.MouseY}");
});
await channel.Subscribe();
```
**Broadcast an event**:
```c#
var channel = supabase.Realtime.Channel("cursor");
var data = new CursorBroadcast { Event = "cursor", Payload = new MouseStatus { MouseX = 123, MouseY = 456 } };
channel.Send(ChannelType.Broadcast, data);
```
## Presence
"Presence utilizes an in-memory conflict-free replicated data type (CRDT) to track and synchronize shared state in an
eventually consistent manner. It computes the difference between existing state and new state changes and sends the
necessary updates to clients via Broadcast.
When a new client subscribes to a channel, it will immediately receive the channel's latest state in a single message
instead of waiting for all other clients to send their individual states.
Clients are free to come-and-go as they please, and as long as they are all subscribed to the same channel then they
will all have the same Presence state as each other.
The neat thing about Presence is that if a client is suddenly disconnected (for example, they go offline), their state
will be automatically removed from the shared state. If you've ever tried to build an “I'm online” feature which handles
unexpected disconnects, you'll appreciate how useful this is."
[Find more information here](https://supabase.com/docs/guides/realtime#presence)
**Given the following model: (`UserPresence`)**
```c#
class UserPresence: BasePresence
{
[JsonProperty("lastSeen")]
public DateTime LastSeen { get; set; }
}
```
**Listen for typed presence events**:
```c#
var presenceId = Guid.NewGuid().ToString();
var channel = supabase.Realtime.Channel("last-seen");
var presence = channel.Register<UserPresence>(presenceId);
presence.AddPresenceEventHandler(EventType.Sync, (sender, type) =>
{
foreach (var state in presence.CurrentState)
{
var userId = state.Key;
var lastSeen = state.Value.First().LastSeen;
Debug.WriteLine($"{userId}: {lastSeen}");
}
});
await channel.Subscribe();
```
**Track a user presence event**:
```c#
var presenceId = Guid.NewGuid().ToString();
var channel = supabase.Realtime.Channel("last-seen");
var presence = channel.Register<UserPresence>(presenceId);
presence.Track(new UserPresence { LastSeen = DateTime.Now });
```
## Postgres Changes
"Postgres Changes enable you to listen to database changes and have them broadcast to authorized clients based
on [Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security) policies.
This works by Realtime polling your database's logical replication slot for changes, passing those changes to
the [apply_rls](https://github.com/supabase/walrus#reading-wal) SQL function to determine which clients have permission,
and then using Broadcast to send those changes to clients.
Realtime requires a publication called `supabase_realtime` to determine which tables to poll. You must add tables to
this publication prior to clients subscribing to channels that want to listen for database changes.
We strongly encourage you to enable RLS on your database tables and have RLS policies in place to prevent unauthorized
parties from accessing your data."
[Find More Information here](https://supabase.com/docs/guides/realtime#postgres-changes)
**Using the new `Register` method:**
```c#
var channel = supabase.Realtime.Channel("public-users");
channel.Register(new PostgresChangesOptions("public", "users"));
channel.AddPostgresChangeHandler(ListenType.All, (sender, change) =>
{
switch (change.Event)
{
case EventType.Insert:
// User has been created
break;
case EventType.Update:
// User has been updated
break;
case EventType.Delete:
// User has been deleted
break;
}
});
await channel.Subscribe();
```
## Status
- [x] Client Connects to Websocket
- [x] Socket Event Handlers
- [x] Open
- [x] Close - when channel is explicitly closed by server or by calling `Channel.Unsubscribe()`
- [x] Error
- [x] Realtime Event Handlers
- [x] `INSERT`
- [x] `UPDATE`
- [x] `DELETE`
- [x] `*`
- [x] Join channels of format:
- [x] `{database}`
- [x] `{database}:{schema}`
- [x] `{database}:{schema}:{table}`
- [x] `{database}:{schema}:{table}:{col}.eq.{val}`
- [x] Responses supply a Generically Typed Model derived from `BaseModel`
- [x] Ability to remove subscription to Realtime Events
- [x] Ability to disconnect from socket.
- [x] Socket reconnects when possible
- [x] Unit Tests
- [x] Documentation
- [x] Nuget Release
## Package made possible through the efforts of:
Join the ranks! See a problem? Help fix it!
<a href="https://github.com/supabase-community/realtime-csharp/graphs/contributors">
<img src="https://contrib.rocks/image?repo=supabase-community/realtime-csharp" />
</a>
Made with [contrib.rocks](https://contrib.rocks/preview?repo=supabase-community%2Frealtime-csharp).
## Contributing
We are more than happy to have contributions! Please submit a PR.
## Testing
Note that the latest versions of `supabase/realtime` expect to be able to access a subdomain matching the tenant. For
the case of testing, this means that `realtime-dev.localhost:4000` should be available. To have tests run locally,
please add a hosts entry on your system for: `127.0.0.1 realtime-dev.localhost`

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

View File

@ -0,0 +1,12 @@
<Project>
<PropertyGroup>
<UseWindowsRxVersion Condition="'$(UseWpf)' == 'true' OR '$(UseWindowsForms)' == 'true'" >true</UseWindowsRxVersion>
<UseWindowsRxVersion Condition="'$(UseWindowsRxVersion)' == '' " >false</UseWindowsRxVersion>
</PropertyGroup>
<ItemGroup>
<Reference Condition="'$(UseWindowsRxVersion)' == 'true' " Include="$(MSBuildThisFileDirectory)..\..\build\netcoreapp3.1\System.Reactive.dll" />
<Reference Condition="'$(UseWindowsRxVersion)' != 'true' " Include="$(MSBuildThisFileDirectory)..\..\lib\netstandard2.0\System.Reactive.dll" />
<FrameworkReference Include="Microsoft.WindowsDesktop.App" Condition="'$(UseWindowsRxVersion)' == 'true' and !@(FrameworkReference->AnyHaveMetadataValue('Identity', 'Microsoft.WindowsDesktop.App'))" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More