# Majacloud Unity SDK 2.1

Backend services for Unity games — player auth, leaderboards, signed remote config, cloud save and rewards — with an `async/await` API and coroutine wrappers. Works on Unity 2021.3 LTS and newer, including WebGL and IL2CPP.

## Requirements

- Unity 2021.3 or newer with uGUI (TextMeshPro is included with uGUI 2.x on Unity 6; on 2021/2022 it is the default `com.unity.textmeshpro` package).
- A MAJACLOUD project: Project ID and Client Key from the dashboard (Project ▸ API keys).

## Install

1. Import `Majacloud-Unity-SDK-2.1.0.unitypackage` (Assets ▸ Import Package), or add the `Assets/Majacloud` folder through Package Manager ▸ *Add package from disk* using `package.json`.
2. Open **Tools ▸ Majacloud ▸ Setup**, paste your Base URL, Project ID and Client Key, press **Test connection**. This creates `Assets/Resources/MajacloudSettings.asset` and stores the project's Remote Config public key.
3. Open **Tools ▸ Majacloud ▸ Open Starter Sample** (the `00 Home` demo scene) and press Play.

## Demo scenes

`Samples/Starter/Scenes` has one Home scene that links to all the others:

| Scene | What it shows |
| --- | --- |
| `00 Home` | Logo, connection status, player badge and a button for every demo |
| `01 All-in-One` | Every service in one screen with tabs |
| `02 Sign-in` … `06 Remote Config` | One service per scene, each with a short code example and a Copy button |
| `07 Test Data` | Playground: submit scores, add 5 test players, save any JSON, claim rewards, read config, and **Run all tests** |
| `08 Docs` | Short code examples for every service |

Every scene also works on its own. With the default sample content (leaderboard `coins`, reward `daily_coins`, config `welcome_message`, `enemy_damage`, `maintenance`) nothing else needs to be created.

The Client Key is public and safe to ship. The server Secret Key never belongs in a Unity project.

## Use

```csharp
using MajacloudSdk;

// Sign in (restores the stored session or creates a guest)
var signIn = await Majacloud.Auth.SignInAnonymouslyAsync();
if (!signIn.Ok) Debug.Log(signIn.Error.Message);

// Leaderboards
await Majacloud.Leaderboards.SubmitScoreAsync("coins", 120);
var top = await Majacloud.Leaderboards.GetTopAsync("coins", 10);
foreach (var e in top.Value.Entries) Debug.Log($"#{e.Rank} {e.PlayerName} {e.Score}");
var me = await Majacloud.Leaderboards.GetMyRankAsync("coins");

// Remote config (fetched at start, cached offline, RSA-verified)
int damage = Majacloud.RemoteConfig.GetInt("enemy_damage", 25);
bool maintenance = Majacloud.RemoteConfig.GetBool("maintenance_mode", false);
Majacloud.RemoteConfig.ConfigUpdated += cfg => Debug.Log("Config v" + cfg.Version);

// Cloud save (any [Serializable] type)
await Majacloud.CloudSave.SaveAsync("progress", progress);
var loaded = await Majacloud.CloudSave.LoadAsync<PlayerProgress>("progress");
if (loaded.IsNotFound) { /* first run */ }

// Rewards (server-authoritative)
var claim = await Majacloud.Rewards.ClaimAsync("daily_coins");
if (!claim.Ok && claim.Error.Code == MajacloudErrorCode.Conflict)
    Debug.Log("Next claim at " + claim.Error.GetDetailDateTime("next_claim_at"));

// Link the guest to an email/Google account (opens the browser)
var linked = await Majacloud.Auth.LinkAccountAsync();
```

Every call returns a `MajacloudResult<T>`: check `Ok`, read `Value`, or inspect `Error.Code` (`Unauthorized`, `PlanLimit`, `RateLimited`, `RevisionMismatch`, …). Results never throw; use `ValueOrThrow()` if you prefer exceptions.

### Coroutines

```csharp
StartCoroutine(Majacloud.Leaderboards.GetTop("coins", 10, result =>
{
    if (result.Ok) Debug.Log(result.Value.Entries.Count);
    else Debug.Log(result.Error.Message);
}));
```

Every service exposes the same methods without the `Async` suffix taking a callback.

## Sessions

`SignInAnonymouslyAsync` restores the session stored in PlayerPrefs (refreshing the access token when needed) or creates a new guest. Tokens refresh automatically before SDK calls; a 401 triggers one refresh and a retry. When the session cannot be refreshed, `Majacloud.Auth.SessionExpired` fires — sign in again. Linking an account merges the guest's scores, saves and coins into that account; `Auth.PlayerChanged` reports the new player.

## WebGL notes

- Browsers block popups outside a click: call `StartLinkAsync()` early, then `flow.OpenInBrowser()` inside your button handler and `await flow.WaitForCompletionAsync()`.
- PlayerPrefs is limited (~1 MB); very large Remote Config payloads are not cached (`configCacheMaxBytes`).
- Every request is a CORS preflight; the server answers them without touching the database.

## IL2CPP

`link.xml` keeps `System.Security.Cryptography` alive so RSA verification survives code stripping.

## Settings

`MajacloudSettings` (Project Settings ▸ Majacloud): base URL, keys, request timeout, retries, whether to fetch config on start, log level, and `allowUnsignedConfigOnLocalhost` (off by default; development only — accepts configs that can't be verified when the base URL is localhost. Signed configs are always verified).

## Troubleshooting

| Message | Fix |
| --- | --- |
| `NotConfigured` / "No MajacloudSettings asset" | Run Tools ▸ Majacloud ▸ Setup. |
| "Use https:// for any server other than localhost" | Plain http is only allowed for localhost. |
| `SignatureInvalid` on Remote Config | Test the connection again to store the current public key; make sure the server has a signing key. |
| `PlanLimit` | The project's plan cap (players, entries, storage…) was reached — upgrade in the dashboard. |
| Demo scenes show no text | Import TMP Essential Resources (Window ▸ TextMeshPro) and rebuild the sample. |
| Unity 2021/2022: demo text runs off the screen | The scenes ship saved with Unity 6. **Open Starter Sample** offers to rebuild them; or run **Rebuild Starter Sample**. |

Rebuild the demo scenes and prefabs at any time with **Tools ▸ Majacloud ▸ Rebuild Starter Sample**; export a package with **Tools ▸ Majacloud ▸ Export .unitypackage**.
