# Player identity

Authentication, friends, avatars, and presence

Source: https://docs.wavedash.com/sdk/players

Players sign in on Wavedash before they launch your game. The injected SDK receives credentials from the host page and attaches them to each API call. You never embed login UI inside the game.

## Reading the current player

<CodeGroup>
```gdscript Godot
func print_player():
    var user = WavedashSDK.get_user()
    var user_id = WavedashSDK.get_user_id()
    var username = WavedashSDK.get_username()
    print(user_id, " ", username, " ", user)
```
```csharp Unity
void PrintPlayer()
{
    var userId = Wavedash.SDK.GetUserId();
    var user = Wavedash.SDK.GetUser();
    Debug.Log($"{userId} {user?["username"]}");
}
```
```lua Defold
local user = wavedash.get_user()
local user_id = wavedash.get_user_id()
local username = wavedash.get_username()
print(user_id, username, user)
```
```javascript JavaScript
const user = Wavedash.getUser();
const userId = Wavedash.getUserId();
const username = Wavedash.getUsername();
console.log(userId, username, user);
```
</CodeGroup>

| Method | Returns |
|--------|---------|
| `getUser()` | Full profile object for the signed-in player |
| `getUserId()` | Stable string ID |
| `getUsername()` | Display name |

### Looking up other players

`getUsername(userId)` returns the display name of any player your game has seen — a friend from `listFriends()`, or a lobby peer. It returns `null` for unknown users, so call `listFriends()` once on startup to populate the cache.

<CodeGroup>
```gdscript Godot
var name = WavedashSDK.get_username(peer_user_id)
```

```csharp Unity
string name = Wavedash.SDK.GetUsername(peerUserId);
```

```lua Defold
local name = wavedash.get_username(peer_user_id)
```

```javascript JavaScript
const name = Wavedash.getUsername(peerUserId);
```
</CodeGroup>

## Gameplay tokens

Gameplay tokens are game-scoped and short-lived. Wavedash issues them when the session starts and the SDK attaches them to all API calls. When a token nears expiry, the SDK requests a fresh one. You do not read or store these tokens in normal game code.

<Warning>
Treat gameplay tokens as secrets at runtime. Do not log them or send them to your own servers unless you control the receiving endpoint.
</Warning>

### Fetching the JWT explicitly

If you operate your own backend and want to authenticate the player using their Wavedash identity, call `getUserJwt()` to get a signed JWT you can forward to your server and verify there. The SDK returns a cached token when possible and refreshes it on demand.

<CodeGroup>
```gdscript Godot
func send_to_my_backend():
    var response = await WavedashSDK.get_user_jwt()
    if not response.success:
        return
    var jwt = response.data
    # forward `jwt` to your own server in the Authorization header
```

```csharp Unity
async void SendToMyBackend()
{
    string jwt = await Wavedash.SDK.GetUserJwt();
    if (string.IsNullOrEmpty(jwt))
        return;
    // forward `jwt` to your own server in the Authorization header
}
```

```lua Defold
local co = coroutine.create(function()
    local response = wavedash.get_user_jwt_async()
    if not response.success then
        return
    end

    local jwt = response.data
    -- forward `jwt` to your own server in the Authorization header
end)
assert(coroutine.resume(co))
```

```javascript JavaScript
const response = await Wavedash.getUserJwt();
if (response.success) {
  const jwt = response.data;
  // forward `jwt` to your own server in the Authorization header
}
```
</CodeGroup>

<Note>
Verify the JWT on your backend against the Wavedash public keys. The token is short-lived (1 hour); call `getUserJwt()` again if a previous token has expired.
</Note>

### Verifying the JWT on your backend

Tokens are signed with RS256 and the public keys are published at [`https://auth.wavedash.com/.well-known/jwks.json`](https://auth.wavedash.com/.well-known/jwks.json). Use any JWKS-aware library to verify — the following example uses [`jose`](https://github.com/panva/jose) in Node.js:

```typescript
import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(
  new URL("https://auth.wavedash.com/.well-known/jwks.json")
);

export async function verifyWavedashJwt(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: "https://auth.wavedash.com",
    audience: "gameplay.wavedash.com"
  });
  return payload;
}

async function getVerifiedWavedashUserId(jwt: string) {
    const payload = await verifyWavedashJwt(jwt);
    const userId = payload.sub;
    reutrn userId;
}
```

A verified gameplay JWT payload looks like:

```typescript
interface GameplayJwtPayload {
  sub: string;    // Wavedash user ID — use this as the player's identity
  iss: "https://auth.wavedash.com";
  aud: "gameplay.wavedash.com";
  scope: "gameplay";
  gbid: string;     // game build ID
  gcid: string;     // game cloud ID (differentiates playtest from production)
  iat: number;      // issued-at (seconds since epoch)
  nbf: number;      // not-before (seconds since epoch)
  exp: number;      // expiry (seconds since epoch)
}
```

Use `payload.sub` as the stable user ID for your own records. Reject the token if `jwtVerify` throws an error — it handles signature, issuer, audience, and expiry checks for you.

<Note>
Wavedash loads your game in a [cross-origin-isolated](https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated) context, so any HTTP response your backend sends to the game must include `Cross-Origin-Resource-Policy: cross-origin` and `Access-Control-Allow-Origin: *`, or the browser will block it. WebSockets are unaffected — if you check the `Origin` header, just confirm the host ends with `.builds.wavedash.com`.
</Note>

## Friends

The Friends API lets you retrieve the current player's friends list and display user avatars.

### Listing friends

<CodeGroup>
```gdscript Godot
func _ready():
    WavedashSDK.got_friends.connect(_on_got_friends)

func load_friends():
    WavedashSDK.list_friends()

func _on_got_friends(response):
    if response.get("success", false):
        for friend in response.get("data", []):
            print(friend["username"], " - Online: ", friend["isOnline"])
```

```csharp Unity
async void Start()
{
    var friends = await Wavedash.SDK.ListFriends();
    if (friends != null)
    {
        foreach (var friend in friends)
            Debug.Log($"{friend["username"]} - Online: {friend["isOnline"]}");
    }
}
```

```lua Defold
local co = coroutine.create(function()
    local response = wavedash.list_friends_async()
    if response.success then
        for _, friend in ipairs(response.data) do
            print(friend.username, "- Online:", friend.isOnline)
        end
    end
end)
assert(coroutine.resume(co))
```

```javascript JavaScript
const response = await Wavedash.listFriends();
if (response.success) {
  response.data.forEach(friend => {
    console.log(`${friend.username} - Online: ${friend.isOnline}`);
  });
}
```
</CodeGroup>

### Friend data

| Field | Type | Description |
|-------|------|-------------|
| `userId` | `Id<"users">` | Friend's user ID |
| `username` | string | Display name |
| `avatarUrl` | string? | URL to avatar (if set) |
| `isOnline` | boolean | Whether currently online |

### User avatars

Get a CDN URL for a user's avatar. Users must be cached (seen via friends list or lobby membership) first.

| JS constant | Native size constant | Pixels | Use case |
|-------------|----------------------|--------|----------|
| `Wavedash.AvatarSize.SMALL` | `WavedashConstants.AVATAR_SIZE_SMALL` (Godot) / `AvatarSize.SMALL` (Unity) | 64px | Lists, compact UI |
| `Wavedash.AvatarSize.MEDIUM` | `WavedashConstants.AVATAR_SIZE_MEDIUM` (Godot) / `AvatarSize.MEDIUM` (Unity) | 128px | Profile cards, chat |
| `Wavedash.AvatarSize.LARGE` | `WavedashConstants.AVATAR_SIZE_LARGE` (Godot) / `AvatarSize.LARGE` (Unity) | 256px | Full profile view |

<CodeGroup>
```gdscript Godot
# Just the URL — cheap, synchronous
var url = WavedashSDK.get_user_avatar_url(user_id, WavedashConstants.AVATAR_SIZE_MEDIUM)

# Or: async helper that fetches + decodes to a Texture2D and emits a signal
func _ready():
    WavedashSDK.user_avatar_loaded.connect(_on_avatar_loaded)

func load_friend_avatar(user_id: String):
    WavedashSDK.get_user_avatar(user_id, WavedashConstants.AVATAR_SIZE_MEDIUM)

func _on_avatar_loaded(texture: Texture2D, user_id: String):
    if texture:
        $AvatarSprite.texture = texture
```

```csharp Unity
// Just the URL — cheap, synchronous
string url = Wavedash.SDK.GetUserAvatarUrl(userId, WavedashConstants.AvatarSize.MEDIUM);

// Or: async helper that fetches + decodes to a Texture2D
public async void LoadFriendAvatar(string userId)
{
    var texture = await Wavedash.SDK.GetUserAvatar(userId, WavedashConstants.AvatarSize.MEDIUM);
    if (texture != null)
        avatarImage.texture = texture;
}
```

```lua Defold
local url = wavedash.get_user_avatar_url(user_id, wavedash.AVATAR_SIZE_MEDIUM)
if url then
    print("Avatar URL:", url)
end
```

```javascript JavaScript
const url = Wavedash.getUserAvatarUrl(userId, Wavedash.AvatarSize.MEDIUM);
if (url) {
  const img = new Image();
  img.src = url;
}
```
</CodeGroup>

<Note>
If `getUserAvatarUrl()` / `GetUserAvatarUrl()` / `get_user_avatar_url()` returns `null`, the user has not been cached yet or has no avatar set. Call `listFriends()` early to populate the cache.
</Note>

## Presence

The Presence API lets you share player status and activity with friends.

`updateUserPresence` takes a data object with two optional fields:

| Field | Description |
|-------|-------------|
| `status` | One-line activity, shown as the primary line on friends-list rows (e.g. "Traveling in a group"). |
| `details` | Secondary context, shown beneath the status (e.g. the current zone or game mode). |

<CodeGroup>
```gdscript Godot
await WavedashSDK.update_user_presence({
    "status": "Traveling in a group",
    "details": "Eastfield Forest"
})
```

```csharp Unity
await Wavedash.SDK.UpdateUserPresence(new Dictionary<string, object> {
    { "status", "Traveling in a group" },
    { "details", "Eastfield Forest" }
});
```

```lua Defold
local co = coroutine.create(function()
    local response = wavedash.update_user_presence_async(json.encode({
        status = "Traveling in a group",
        details = "Eastfield Forest",
    }))
    print("Presence updated:", response.success)
end)
assert(coroutine.resume(co))
```

```javascript JavaScript
await Wavedash.updateUserPresence({
  status: "Traveling in a group",
  details: "Eastfield Forest"
});
```
</CodeGroup>

### Clearing presence

Pass an empty dictionary or object to clear all active presence fields.

<CodeGroup>
```gdscript Godot
await WavedashSDK.update_user_presence({})
```

```csharp Unity
await Wavedash.SDK.UpdateUserPresence(new Dictionary<string, object>());
```

```lua Defold
local co = coroutine.create(function()
    wavedash.update_user_presence_async(json.encode({}))
end)
assert(coroutine.resume(co))
```

```javascript JavaScript
await Wavedash.updateUserPresence({});
```
</CodeGroup>
