@bunnyland/ui-web 0.2.0

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.
Files changed (57) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +211 -0
  3. package/assets/bunnyland-api.js +345 -0
  4. package/assets/bunnyland-play.js +1223 -0
  5. package/assets/bunnyland-ui.css +1634 -0
  6. package/assets/bunnyland-ui.js +795 -0
  7. package/dist/admin-widgets.d.ts +31 -0
  8. package/dist/admin-widgets.d.ts.map +1 -0
  9. package/dist/admin-widgets.js +100 -0
  10. package/dist/admin-widgets.js.map +1 -0
  11. package/dist/api.d.ts +37 -0
  12. package/dist/api.d.ts.map +1 -0
  13. package/dist/api.js +125 -0
  14. package/dist/api.js.map +1 -0
  15. package/dist/index.d.ts +7 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +7 -0
  18. package/dist/play.d.ts +265 -0
  19. package/dist/play.d.ts.map +1 -0
  20. package/dist/play.js +806 -0
  21. package/dist/play.js.map +1 -0
  22. package/dist/player-widgets.d.ts +11 -0
  23. package/dist/player-widgets.d.ts.map +1 -0
  24. package/dist/player-widgets.js +21 -0
  25. package/dist/player-widgets.js.map +1 -0
  26. package/dist/preact/auth.d.ts +37 -0
  27. package/dist/preact/auth.d.ts.map +1 -0
  28. package/dist/preact/components.d.ts +58 -0
  29. package/dist/preact/components.d.ts.map +1 -0
  30. package/dist/preact/theme.d.ts +16 -0
  31. package/dist/preact/theme.d.ts.map +1 -0
  32. package/dist/preact.d.ts +4 -0
  33. package/dist/preact.d.ts.map +1 -0
  34. package/dist/preact.js +488 -0
  35. package/dist/preact.js.map +1 -0
  36. package/dist/theme.d.ts +31 -0
  37. package/dist/theme.d.ts.map +1 -0
  38. package/dist/theme.js +165 -0
  39. package/dist/theme.js.map +1 -0
  40. package/dist/widgets.d.ts +7 -0
  41. package/dist/widgets.d.ts.map +1 -0
  42. package/dist/widgets.js +31 -0
  43. package/dist/widgets.js.map +1 -0
  44. package/docs/admin/custom-web-themes.md +112 -0
  45. package/docs/developer/design-language.md +116 -0
  46. package/package.json +101 -0
  47. package/src/admin-widgets.ts +189 -0
  48. package/src/api.ts +193 -0
  49. package/src/index.ts +6 -0
  50. package/src/play.ts +1151 -0
  51. package/src/player-widgets.ts +29 -0
  52. package/src/preact/auth.tsx +338 -0
  53. package/src/preact/components.tsx +249 -0
  54. package/src/preact/theme.tsx +72 -0
  55. package/src/preact.ts +3 -0
  56. package/src/theme.ts +225 -0
  57. package/src/widgets.ts +41 -0
package/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # Bunnyland UI Web
2
+
3
+ Shared Preact components, web UI assets, browser helpers, and gameplay display utilities for
4
+ Bunnyland browser clients. The package is publish-ready as `@bunnyland/ui-web` and keeps its
5
+ legacy browser globals intact while clients migrate incrementally.
6
+
7
+ ## Static Browser Clients
8
+
9
+ Static clients can copy these assets into their served `assets/` directory:
10
+
11
+ - `assets/bunnyland-ui.css`
12
+ - `assets/bunnyland-ui.js`
13
+ - `assets/bunnyland-api.js`
14
+ - `assets/bunnyland-play.js`
15
+
16
+ The JavaScript assets preserve the browser globals used by the existing clients:
17
+
18
+ - `window.BunnylandUI`
19
+ - `window.BunnylandApi`
20
+ - `window.BunnylandPlay`
21
+
22
+ ## Vite, TypeScript, And Preact Clients
23
+
24
+ Install the package and its deliberately small Preact peer dependency:
25
+
26
+ ```sh
27
+ npm install @bunnyland/ui-web preact
28
+ ```
29
+
30
+ Then import the typed helpers. Prefer narrow modules so Vite and Rollup can tree shake unrelated admin or player code:
31
+
32
+ ```ts
33
+ import { requestSceneImage } from '@bunnyland/ui-web/api';
34
+ import { filterActions } from '@bunnyland/ui-web/play';
35
+ import { bindThemeSelect } from '@bunnyland/ui-web/theme';
36
+ import { renderGalleryItems } from '@bunnyland/ui-web/player-widgets';
37
+ import '@bunnyland/ui-web/assets/bunnyland-ui.css';
38
+ ```
39
+
40
+ Use direct Preact imports for shared functional components and hooks; React and
41
+ `preact/compat` are not required:
42
+
43
+ ```tsx
44
+ import { render } from 'preact';
45
+ import { useState } from 'preact/hooks';
46
+ import {
47
+ Button,
48
+ Field,
49
+ Pane,
50
+ SearchSelect,
51
+ ThemeSelect,
52
+ Toolbar,
53
+ ToolbarRow,
54
+ useTheme,
55
+ } from '@bunnyland/ui-web/preact';
56
+ ```
57
+
58
+ `preact` is a peer dependency so each application owns one runtime and Vite can deduplicate
59
+ it. The library build externalizes `preact` and `preact/hooks` rather than bundling a second
60
+ copy. See the canonical [Bunnyland web design language](docs/developer/design-language.md)
61
+ for component boundaries, interaction states, and migration rules.
62
+
63
+ ## Package Releases
64
+
65
+ Stable GitHub releases publish the matching package version to the public npm registry with
66
+ provenance. The release tag must exactly match `v<package.json version>`; for example,
67
+ package version `0.2.0` is published from tag `v0.2.0`. The dedicated `publish.yml` workflow
68
+ runs the complete package gate again before publishing.
69
+
70
+ The first publication needs a granular npm automation token with access to the `@bunnyland`
71
+ scope in the repository's `NPM_TOKEN` Actions secret. Once the package exists, configure its
72
+ npm trusted publisher with organization `thalismind`, repository `bunnyland-ui-web`, workflow
73
+ `publish.yml`, and the `npm publish` action. Then revoke the bootstrap token; the same
74
+ workflow uses GitHub Actions OIDC and automatically attaches npm provenance.
75
+
76
+ Consumers should pin an exact registry version:
77
+
78
+ ```sh
79
+ npm install --save-exact @bunnyland/ui-web@0.2.0 preact
80
+ ```
81
+
82
+ ## Versioned Package Artifact
83
+
84
+ Build a standalone npm tarball for local package inspection with:
85
+
86
+ ```sh
87
+ npm ci
88
+ npm run pack:artifact
89
+ ```
90
+
91
+ The output is `artifacts/bunnyland-ui-web-<version>.tgz`. It contains `package.json`, typed
92
+ `dist/` entry points, legacy `assets/`, source maps, and documentation, and has no dependency
93
+ on an adjacent checkout. CI uploads this tarball for every checked revision as a diagnostic
94
+ artifact. Release consumers use the exact published registry version, never an adjacent
95
+ source directory or a checked-in tarball.
96
+
97
+ ## Themes
98
+
99
+ The package owns the shared `--bl-*` CSS variables and theme selector helpers. Built-in
100
+ palette names are:
101
+
102
+ - `purple-blue`
103
+ - `candy`
104
+ - `earth`
105
+ - `ocean`
106
+ - `sunset`
107
+ - `high-contrast`
108
+
109
+ Each palette follows `prefers-color-scheme` by default. The shared client menu can force
110
+ Dark or Light appearance, or return to Auto (System). Use `bindThemeSelect(select)` for
111
+ palette selectors, `bindColorSchemeSelect(select)` for appearance selectors, and
112
+ `setTheme(theme)` or `setColorScheme(scheme)` for direct changes. Old paired values such as
113
+ `anime-light` continue to load as migration aliases (`candy` with Light forced). New colors
114
+ should be added as CSS variables before clients depend on them.
115
+
116
+ Deployments can add their own theme choices without changing this package:
117
+
118
+ ```ts
119
+ import { registerThemeOptions } from '@bunnyland/ui-web/theme';
120
+
121
+ registerThemeOptions([
122
+ { value: 'server-night', label: 'Server Night' },
123
+ ]);
124
+ ```
125
+
126
+ Then serve CSS for the matching root class:
127
+
128
+ ```css
129
+ :root.bl-theme-server-night {
130
+ color-scheme: dark;
131
+ --bl-bg: #101218;
132
+ --bl-surface: #202532;
133
+ --bl-text: #f0f4ff;
134
+ --bl-accent: #74c7ec;
135
+ }
136
+ ```
137
+
138
+ Static browser clients also read a `themes` array from deployment `config.json` and register
139
+ those options automatically. Theme values must use lowercase letters, numbers, and hyphens so
140
+ the value maps directly to `bl-theme-<value>`. Set `theme` in `config.json` to make one of
141
+ those values the deployment default, or share a link with `?theme=<value>` to choose a theme
142
+ from the URL.
143
+
144
+ See [custom web themes](docs/admin/custom-web-themes.md) for the server-admin setup guide.
145
+
146
+ ## Widgets
147
+
148
+ The framework-neutral widget helpers are split by audience:
149
+
150
+ - `@bunnyland/ui-web/widgets`: common storage, escaping, and simple data helpers.
151
+ - `@bunnyland/ui-web/player-widgets`: contextual player controls such as photo galleries.
152
+ - `@bunnyland/ui-web/admin-widgets`: detailed admin/editor controls such as tag editors and search dropdowns.
153
+
154
+ Framework-specific UI packages can wrap these behaviors in components without duplicating the underlying logic. Use narrow imports instead of the root package when a client only needs one audience-specific surface.
155
+
156
+ Player clients should favor contextual, rich controls for interacting with the current room, visible entities, exits, inventory, queued actions, and images. Admin clients should favor detailed inspection and editing controls such as structured fields, sliders, selectors, tag editors, and lower-level component data. Keep those surfaces separate when adding reusable widgets.
157
+
158
+ ## Player live updates and disclosed facts
159
+
160
+ `@bunnyland/ui-web/play` owns the shared remote-player update coordinator. It authenticates
161
+ the character WebSocket in the first frame so claim secrets never appear in URLs, coalesces
162
+ bursts into serialized refreshes, reconnects with backoff, and resumes the character-scoped
163
+ recent-event fallback while disconnected. It deduplicates at-least-once event delivery by
164
+ `event_id`, detects `stream_sequence` gaps, and refreshes the character projection after a
165
+ gap or `resync`. Use one coordinator per claimed character rather than giving each panel its
166
+ own socket or polling loop.
167
+
168
+ Player activity renderers understand serialized `facts` entries with stable `key`, `text`,
169
+ and numeric `detail`. Render the provided text in server order; do not reinterpret component
170
+ state or apply a second client-side cutoff. Action controls likewise consume the serialized
171
+ registry `actions` and `target_groups`. Missing metadata means an empty/disabled action state,
172
+ not a static browser verb catalogue.
173
+
174
+ ## Storybook
175
+
176
+ A component storybook renders the shared Preact foundation, theme selector, form controls,
177
+ legacy widget helpers, and every semantic state as a live gallery. Build it with:
178
+
179
+ ```sh
180
+ npm run storybook
181
+ ```
182
+
183
+ That builds the package and writes `storybook-dist/`:
184
+
185
+ - `index.html` — the live, interactive storybook (theme selector, tag editor, search dropdown, client menu).
186
+ - `screenshots.html` — a gallery of one PNG per theme plus the client-menu overlay.
187
+ - `screenshots/*.png` — the captured screenshots.
188
+ - `storybook.zip` — an offline bundle of the HTML, CSS, JS, and screenshots.
189
+
190
+ The capture uses Playwright's bundled Chromium (`npx playwright install chromium`) and falls
191
+ back to a system Chrome via `STORYBOOK_BROWSER_CHANNEL` when the bundled browser is missing.
192
+
193
+ CI publishes the `storybook-dist/` directory and `storybook.zip` as build artifacts and builds
194
+ `Dockerfile.storybook` into an nginx image (`bunnyland-ui-web-storybook`) that serves the
195
+ browsable storybook.
196
+
197
+ ## Checks
198
+
199
+ Run:
200
+
201
+ ```sh
202
+ npm run check
203
+ ```
204
+
205
+ That runs ESLint, the Vite 8 library build, TypeScript declaration generation, Node test
206
+ coverage, and the Preact component tests in a browser-like DOM.
207
+
208
+ ## License
209
+
210
+ Licensed under the GNU Affero General Public License v3.0 or later. See
211
+ [LICENSE](LICENSE).
@@ -0,0 +1,345 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ let playerAuthHeader = null;
5
+ let rotationTimer = null;
6
+ let browserClientId = typeof globalThis.crypto?.randomUUID === 'function'
7
+ ? globalThis.crypto.randomUUID()
8
+ : `web-${Date.now()}-${Math.random().toString(16).slice(2)}`;
9
+
10
+ class ApiError extends Error {
11
+ constructor(message, status) {
12
+ super(message);
13
+ this.name = 'ApiError';
14
+ this.status = status;
15
+ }
16
+ }
17
+
18
+ function normalizeBase(url) {
19
+ return String(url || '').trim().replace(/\/$/, '');
20
+ }
21
+
22
+ function assertSameOriginBase(base) {
23
+ const normalized = normalizeBase(base);
24
+ const resolved = new URL(normalized || '/', location.href);
25
+ if (resolved.origin !== location.origin) {
26
+ throw new Error('Bunnyland browser connections must use the page origin');
27
+ }
28
+ return normalized;
29
+ }
30
+
31
+ function serverFromUrl() {
32
+ const configured = new URLSearchParams(location.search).get('server') || '';
33
+ return configured ? assertSameOriginBase(configured) : '';
34
+ }
35
+
36
+ function setServerInUrl(base) {
37
+ const url = new URL(location.href);
38
+ const normalized = assertSameOriginBase(base);
39
+ if (normalized) url.searchParams.set('server', normalized);
40
+ else url.searchParams.delete('server');
41
+ history.replaceState(null, '', url);
42
+ }
43
+
44
+ function truthyConfig(value) {
45
+ return value === true || value === 'true' || value === '1';
46
+ }
47
+
48
+ function mergedJsonHeaders(headers = null) {
49
+ return {
50
+ ...jsonHeaders(playerAuthHeader),
51
+ ...(headers || {}),
52
+ };
53
+ }
54
+
55
+ function scheduleRotation(base, status) {
56
+ if (rotationTimer) clearTimeout(rotationTimer);
57
+ if (!status?.rotate_after) return;
58
+ const delay = Math.max(0, (Number(status.rotate_after) * 1000) - Date.now());
59
+ rotationTimer = setTimeout(async () => {
60
+ try {
61
+ const next = await rotateAuth(base);
62
+ scheduleRotation(base, next);
63
+ } catch (_error) {
64
+ rotationTimer = null;
65
+ }
66
+ }, Math.min(delay, 2147483647));
67
+ }
68
+
69
+ async function authMe(base) {
70
+ const res = await fetch(`${assertSameOriginBase(base)}/auth/session`, {
71
+ credentials: 'include', headers: jsonHeaders(playerAuthHeader),
72
+ });
73
+ return parseJsonResponse(res);
74
+ }
75
+
76
+ async function login(base, username, password) {
77
+ const res = await fetch(`${assertSameOriginBase(base)}/auth/session`, {
78
+ method: 'POST',
79
+ credentials: 'include',
80
+ headers: jsonHeaders(playerAuthHeader),
81
+ body: JSON.stringify({ username, password, delivery: 'cookie' }),
82
+ });
83
+ const status = await parseJsonResponse(res);
84
+ scheduleRotation(base, status);
85
+ return status;
86
+ }
87
+
88
+ async function rotateAuth(base) {
89
+ const res = await fetch(`${assertSameOriginBase(base)}/auth/session`, {
90
+ method: 'PATCH',
91
+ credentials: 'include',
92
+ headers: jsonHeaders(playerAuthHeader),
93
+ });
94
+ return parseJsonResponse(res);
95
+ }
96
+
97
+ async function logout(base) {
98
+ const res = await fetch(`${assertSameOriginBase(base)}/auth/session`, {
99
+ method: 'DELETE',
100
+ credentials: 'include',
101
+ headers: jsonHeaders(playerAuthHeader),
102
+ });
103
+ if (rotationTimer) clearTimeout(rotationTimer);
104
+ rotationTimer = null;
105
+ return parseJsonResponse(res);
106
+ }
107
+
108
+ async function ensurePlayerAuth(base) {
109
+ try {
110
+ const status = await authMe(base);
111
+ scheduleRotation(base, status);
112
+ return true;
113
+ } catch (_error) {
114
+ return promptPlayerAuth(base);
115
+ }
116
+ }
117
+
118
+ async function applyConfigToInput({ inputId = 'api-url', isConnected = () => false, connect = null } = {}) {
119
+ const config = await BunnylandUI.loadConfig();
120
+ const input = document.getElementById(inputId);
121
+ if (config.serverUrl && input && !isConnected()) input.value = config.serverUrl;
122
+ if (config.autoConnect && config.serverUrl && !isConnected() && connect) {
123
+ if (!truthyConfig(config.playerAuthRequired) || await ensurePlayerAuth(config.serverUrl)) {
124
+ connect(config.serverUrl);
125
+ }
126
+ }
127
+ return config;
128
+ }
129
+
130
+ function applyServerParam({ inputId = 'api-url', connect = null } = {}) {
131
+ const server = serverFromUrl();
132
+ if (!server) return '';
133
+ const input = document.getElementById(inputId);
134
+ if (input) input.value = server;
135
+ if (connect) connect(server);
136
+ return server;
137
+ }
138
+
139
+ function jsonHeaders(authHeader = null) {
140
+ return {
141
+ 'Content-Type': 'application/json',
142
+ 'X-Bunnyland-Client-Id': browserClientId,
143
+ ...(String(authHeader || '').startsWith('Bearer ') ? { Authorization: authHeader } : {}),
144
+ };
145
+ }
146
+
147
+ function adminHeaders(authHeader = null, contentType = null) {
148
+ const headers = { 'X-Bunnyland-Client-Id': browserClientId };
149
+ if (contentType) headers['Content-Type'] = contentType;
150
+ if (String(authHeader || '').startsWith('Bearer ')) {
151
+ headers.Authorization = authHeader;
152
+ }
153
+ return headers;
154
+ }
155
+
156
+ function claimHeaders(control = null) {
157
+ return {
158
+ ...jsonHeaders(playerAuthHeader),
159
+ 'X-Bunnyland-Client-Id': control?.clientId || browserClientId,
160
+ ...(control?.claimSecret ? { 'X-Bunnyland-Claim-Secret': control.claimSecret } : {}),
161
+ };
162
+ }
163
+
164
+ async function parseJsonResponse(res) {
165
+ const data = await res.json().catch(() => ({}));
166
+ if (!res.ok) throw new ApiError(data.detail || `HTTP ${res.status}`, res.status);
167
+ return data;
168
+ }
169
+
170
+ async function sendJson(base, path, { method = 'GET', body = null, headers = null, promptAuth = true } = {}) {
171
+ const { data } = await sendJsonWithResponse(base, path, { method, body, headers, promptAuth });
172
+ return data;
173
+ }
174
+
175
+ async function sendJsonWithResponse(base, path, {
176
+ method = 'GET', body = null, headers = null, promptAuth = true,
177
+ } = {}) {
178
+ const currentHeaders = () => mergedJsonHeaders(headers);
179
+ let res = await fetch(`${assertSameOriginBase(base)}${path}`, {
180
+ method,
181
+ headers: currentHeaders(),
182
+ body,
183
+ credentials: 'include',
184
+ });
185
+ if (res.status === 401 && promptAuth) {
186
+ if (await promptPlayerAuth(base)) {
187
+ res = await fetch(`${assertSameOriginBase(base)}${path}`, {
188
+ method,
189
+ headers: currentHeaders(),
190
+ body,
191
+ credentials: 'include',
192
+ });
193
+ }
194
+ }
195
+ return { data: await parseJsonResponse(res), response: res };
196
+ }
197
+
198
+ async function promptPlayerAuth(base) {
199
+ const username = window.prompt('Bunnyland username');
200
+ if (!username) return null;
201
+ const password = window.prompt('Bunnyland password');
202
+ if (password == null) return null;
203
+ try {
204
+ await login(base, username, password);
205
+ return true;
206
+ } catch (error) {
207
+ window.alert(error.message || 'Login failed');
208
+ return false;
209
+ }
210
+ }
211
+
212
+ function setPlayerAuth(authHeader = null) {
213
+ playerAuthHeader = String(authHeader || '').startsWith('Bearer ') ? authHeader : null;
214
+ }
215
+
216
+ function getPlayerAuth() {
217
+ return playerAuthHeader;
218
+ }
219
+
220
+ function setClientId(clientId) {
221
+ const normalized = String(clientId || '').trim();
222
+ if (normalized) browserClientId = normalized;
223
+ }
224
+
225
+ function getClientId() {
226
+ return browserClientId;
227
+ }
228
+
229
+ async function sendAdmin(base, path, {
230
+ method = 'GET',
231
+ body = null,
232
+ prompt = true,
233
+ getAuth = () => null,
234
+ } = {}) {
235
+ const currentHeaders = () => jsonHeaders(getAuth());
236
+ let res = await fetch(`${assertSameOriginBase(base)}${path}`, {
237
+ method,
238
+ headers: currentHeaders(),
239
+ body,
240
+ credentials: 'include',
241
+ });
242
+ if (res.status === 401 && prompt) {
243
+ if (await promptPlayerAuth(base)) {
244
+ res = await fetch(`${assertSameOriginBase(base)}${path}`, {
245
+ method,
246
+ headers: currentHeaders(),
247
+ body,
248
+ credentials: 'include',
249
+ });
250
+ }
251
+ }
252
+ return parseJsonResponse(res);
253
+ }
254
+
255
+ async function uploadCharacterImage(base, characterId, purpose, file, {
256
+ prompt = true,
257
+ getAuth = () => null,
258
+ } = {}) {
259
+ const path = `/admin/media/character/${encodeURIComponent(characterId)}/${encodeURIComponent(purpose)}`;
260
+ const form = new FormData();
261
+ form.append('file', file);
262
+ const currentHeaders = () => adminHeaders(getAuth());
263
+ let res = await fetch(`${assertSameOriginBase(base)}${path}`, {
264
+ method: 'PUT',
265
+ headers: currentHeaders(),
266
+ body: form,
267
+ credentials: 'include',
268
+ });
269
+ if (res.status === 401 && prompt) {
270
+ if (await promptPlayerAuth(base)) {
271
+ res = await fetch(`${assertSameOriginBase(base)}${path}`, {
272
+ method: 'PUT',
273
+ headers: currentHeaders(),
274
+ body: form,
275
+ credentials: 'include',
276
+ });
277
+ }
278
+ }
279
+ return parseJsonResponse(res);
280
+ }
281
+
282
+ function socketUrl(base, path = '/admin/world/stream', _authHeader = null) {
283
+ const normalized = assertSameOriginBase(base);
284
+ return new URL(`${normalized}${path}`, location.href).href.replace(/^http/, 'ws');
285
+ }
286
+
287
+ function mediaUrl(base, url) {
288
+ if (!url) return '';
289
+ if (url.startsWith('data:')) return url;
290
+ if (/^https?:\/\//.test(url)) {
291
+ assertSameOriginBase(url);
292
+ return url;
293
+ }
294
+ return `${assertSameOriginBase(base)}${url}`;
295
+ }
296
+
297
+ async function requestSceneImage(base, characterId, control = null) {
298
+ void characterId;
299
+ if (!control?.claimId) throw new Error('A character claim is required');
300
+ return sendJson(base, `/play/claims/${encodeURIComponent(control.claimId)}/jobs`, {
301
+ method: 'POST',
302
+ headers: claimHeaders(control),
303
+ body: JSON.stringify({ kind: 'scene_image' }),
304
+ });
305
+ }
306
+
307
+ async function requestEventImage(base, recordId, extra = '') {
308
+ return sendJson(base, '/admin/world/generation-jobs', {
309
+ method: 'POST',
310
+ body: JSON.stringify({ kind: 'image', entity_id: recordId, purpose: 'event', extra }),
311
+ });
312
+ }
313
+
314
+ window.BunnylandApi = {
315
+ ApiError,
316
+ applyConfigToInput,
317
+ applyServerParam,
318
+ adminHeaders,
319
+ assertSameOriginBase,
320
+ authMe,
321
+ claimHeaders,
322
+ ensurePlayerAuth,
323
+ getClientId,
324
+ getPlayerAuth,
325
+ jsonHeaders,
326
+ login,
327
+ logout,
328
+ mediaUrl,
329
+ normalizeBase,
330
+ parseJsonResponse,
331
+ promptPlayerAuth,
332
+ requestEventImage,
333
+ requestSceneImage,
334
+ sendAdmin,
335
+ sendJson,
336
+ sendJsonWithResponse,
337
+ serverFromUrl,
338
+ setServerInUrl,
339
+ setClientId,
340
+ setPlayerAuth,
341
+ socketUrl,
342
+ rotateAuth,
343
+ uploadCharacterImage,
344
+ };
345
+ }());