castle-web-sdk 0.4.27 → 0.4.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/commands.d.ts +14 -0
- package/dist/user.d.ts +4 -0
- package/dist/user.js +36 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -416,7 +416,10 @@ const dailyPuzzle = (date.daysSinceCastleEpoch % 30) + 1;
|
|
|
416
416
|
Returns the signed-in player. Throws `CastleError`
|
|
417
417
|
(`LOGIN_REQUIRED`) when nobody is signed in.
|
|
418
418
|
|
|
419
|
-
|
|
419
|
+
A `CastleUser` has `userId`, `username`, `isAnonymous`, `isActive`, `photoUrl`
|
|
420
|
+
(their 256px avatar), `frameUrl` (the 96px frame drawn around it) and `color`
|
|
421
|
+
(the hex their username is shown in); the last three are `null` when unset. The
|
|
422
|
+
urls are `data:` urls, so they go straight into an `<img>` or a texture.
|
|
420
423
|
Anonymous accounts are real logins with generated `anonymous-user-...` names;
|
|
421
424
|
check `isAnonymous` before accepting writes into anything other players see,
|
|
422
425
|
such as a shared gallery.
|
|
@@ -426,6 +429,19 @@ const me = await User.getCurrent();
|
|
|
426
429
|
greet(me.username);
|
|
427
430
|
```
|
|
428
431
|
|
|
432
|
+
### `User.get(userId): Promise<CastleUser | null>`
|
|
433
|
+
|
|
434
|
+
Another player as anyone may see them, or `null` when no such user exists.
|
|
435
|
+
Results are cached per id for the life of the page. The ids come from the
|
|
436
|
+
session's player list or from `Store.user(id)` rows.
|
|
437
|
+
|
|
438
|
+
```js
|
|
439
|
+
const them = await User.get(player.userId);
|
|
440
|
+
if (them?.photoUrl) {
|
|
441
|
+
avatar.src = them.photoUrl;
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
429
445
|
## Pass
|
|
430
446
|
|
|
431
447
|
A pass is something a creator sells to players for Castle bricks (the
|
package/dist/commands.d.ts
CHANGED
|
@@ -120,6 +120,9 @@ export interface CommandParams {
|
|
|
120
120
|
score?: number | null;
|
|
121
121
|
};
|
|
122
122
|
"user.getCurrent": Record<string, never>;
|
|
123
|
+
"user.get": {
|
|
124
|
+
userId: string;
|
|
125
|
+
};
|
|
123
126
|
"time.getServerTime": Record<string, never>;
|
|
124
127
|
"pass.has": {
|
|
125
128
|
passId: string;
|
|
@@ -214,6 +217,9 @@ export interface CommandResult {
|
|
|
214
217
|
isAnonymous?: boolean;
|
|
215
218
|
} | null;
|
|
216
219
|
};
|
|
220
|
+
"user.get": {
|
|
221
|
+
user: UserPayload | null;
|
|
222
|
+
};
|
|
217
223
|
"time.getServerTime": {
|
|
218
224
|
timestamp: number;
|
|
219
225
|
timezoneOffset: number;
|
|
@@ -254,6 +260,14 @@ export interface CommandResult {
|
|
|
254
260
|
};
|
|
255
261
|
"lifecycle.restoreState": LifecycleRestoreStateResult;
|
|
256
262
|
}
|
|
263
|
+
export interface UserPayload {
|
|
264
|
+
userId: string;
|
|
265
|
+
username: string;
|
|
266
|
+
isAnonymous: boolean;
|
|
267
|
+
photoUrl: string | null;
|
|
268
|
+
frameUrl: string | null;
|
|
269
|
+
color: string | null;
|
|
270
|
+
}
|
|
257
271
|
export type CommandName = keyof CommandParams;
|
|
258
272
|
export interface SerializedCommandError {
|
|
259
273
|
code: string;
|
package/dist/user.d.ts
CHANGED
|
@@ -3,8 +3,12 @@ export interface CastleUser {
|
|
|
3
3
|
username: string;
|
|
4
4
|
isAnonymous: boolean;
|
|
5
5
|
isActive: boolean;
|
|
6
|
+
photoUrl: string | null;
|
|
7
|
+
frameUrl: string | null;
|
|
8
|
+
color: string | null;
|
|
6
9
|
}
|
|
7
10
|
export interface CastleUserApi {
|
|
8
11
|
getCurrent(): Promise<CastleUser>;
|
|
12
|
+
get(userId: string): Promise<CastleUser | null>;
|
|
9
13
|
}
|
|
10
14
|
export declare const User: CastleUserApi;
|
package/dist/user.js
CHANGED
|
@@ -2,8 +2,12 @@ import { CastleError } from "./errors";
|
|
|
2
2
|
import { hostRequest } from "./transport";
|
|
3
3
|
let currentUser = null;
|
|
4
4
|
let currentUserPromise = null;
|
|
5
|
+
// Profiles by user id. A profile changes rarely, and a multiplayer deck asks for
|
|
6
|
+
// the same few ids every time a player joins.
|
|
7
|
+
const profiles = new Map();
|
|
5
8
|
export const User = {
|
|
6
9
|
getCurrent,
|
|
10
|
+
get,
|
|
7
11
|
};
|
|
8
12
|
async function getCurrent() {
|
|
9
13
|
if (currentUser)
|
|
@@ -25,15 +29,46 @@ async function fetchCurrentUser() {
|
|
|
25
29
|
});
|
|
26
30
|
}
|
|
27
31
|
const username = requiredString(user.username, "user.username", operation);
|
|
32
|
+
const userId = requiredString(user.userId, "user.userId", operation);
|
|
33
|
+
// The host answers identity from what it already knows. The profile fields
|
|
34
|
+
// come from the same read `get` does; a host that predates that command
|
|
35
|
+
// leaves them null.
|
|
36
|
+
const profile = await get(userId).catch(() => null);
|
|
28
37
|
return {
|
|
29
|
-
userId
|
|
38
|
+
userId,
|
|
30
39
|
username,
|
|
31
40
|
// A host that predates the flag omits it; anonymous account usernames are
|
|
32
41
|
// always minted as `anonymous-user-<uuid>`, so the prefix is the fallback.
|
|
33
42
|
isAnonymous: user.isAnonymous === true || username.toLowerCase().startsWith("anonymous-user-"),
|
|
34
43
|
isActive: true,
|
|
44
|
+
photoUrl: profile?.photoUrl ?? null,
|
|
45
|
+
frameUrl: profile?.frameUrl ?? null,
|
|
46
|
+
color: profile?.color ?? null,
|
|
35
47
|
};
|
|
36
48
|
}
|
|
49
|
+
// One user by id, or null when no such user exists. Throws `CastleError` on a
|
|
50
|
+
// host that predates the command (`UNKNOWN_COMMAND`).
|
|
51
|
+
async function get(userId) {
|
|
52
|
+
if (typeof userId !== "string" || userId.length === 0) {
|
|
53
|
+
throw new CastleError({
|
|
54
|
+
code: "INVALID_ARGUMENT",
|
|
55
|
+
message: "User.get(userId) needs a user id.",
|
|
56
|
+
operation: "User.get",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
let pending = profiles.get(userId);
|
|
60
|
+
if (!pending) {
|
|
61
|
+
pending = fetchProfile(userId);
|
|
62
|
+
profiles.set(userId, pending);
|
|
63
|
+
// A failed read is not kept, so the next call asks again.
|
|
64
|
+
pending.catch(() => profiles.delete(userId));
|
|
65
|
+
}
|
|
66
|
+
return pending;
|
|
67
|
+
}
|
|
68
|
+
async function fetchProfile(userId) {
|
|
69
|
+
const { user } = await hostRequest("user.get", { userId });
|
|
70
|
+
return user ? { ...user, isActive: true } : null;
|
|
71
|
+
}
|
|
37
72
|
function requiredString(value, field, operation) {
|
|
38
73
|
if (typeof value === "string" && value.length > 0)
|
|
39
74
|
return value;
|