@crowdedkingdoms/crowdyjs 17.1.0 → 17.2.0-test.1

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.
@@ -0,0 +1,136 @@
1
+ import { AbandonGamePublishDocument, AllHostedGamesDocument, BeginGamePublishDocument, ClaimGameHostingDocument, CompleteGamePublishDocument, HostedGameDocument, HostedGamePublishesDocument, HostedGamesDocument, MyHostedGamesDocument, SetHostedGameEnabledDocument, SetHostedGameListingDocument, TakeDownHostedGameDocument, } from '../generated/graphql.js';
2
+ /**
3
+ * Third-party game hosting on Crowdy Games (ck-api v2.1, 2026-09-13).
4
+ *
5
+ * A developer publishes a built static bundle to the platform; players reach it at
6
+ * `https://<games host>/<slug>/` (a first-party shell page) while it executes on
7
+ * `https://<slug>.<content host>`, an origin of its own. The API returns both URLs
8
+ * (`launchUrl`, `contentOrigin`); nothing here spells a hostname.
9
+ *
10
+ * WHICH TOKEN. Every mutation here needs an IDENTITY SESSION (`client.auth.login`
11
+ * from Node, or a Studio session) with `manage_apps` on the app -- never an app
12
+ * token. A game's own bundle can therefore never publish a replacement for itself.
13
+ * The two reads a page needs (`game`, `listed`) are public.
14
+ *
15
+ * THE FLOW: `claim` once per app -> build -> `beginPublish` with the manifest ->
16
+ * PUT every file to its presigned URL with exactly the returned headers ->
17
+ * `completePublish`. `@crowdedkingdoms/crowdyjs/hosting`'s `publishDirectory` does
18
+ * the last three steps for a `dist/` directory in Node.
19
+ */
20
+ export class HostingAPI {
21
+ constructor(api) {
22
+ this.api = api;
23
+ }
24
+ /** Public: a hosted game by slug, or null. The shell's question. */
25
+ async game(slug) {
26
+ const data = await this.api.request(HostedGameDocument, { slug });
27
+ return data.hostedGame;
28
+ }
29
+ /** Public: the LIVE, LISTED hosted games -- the lobby's list. */
30
+ async listed() {
31
+ const data = await this.api.request(HostedGamesDocument, {});
32
+ return data.hostedGames;
33
+ }
34
+ /** Operator: every hosted game on the tier. */
35
+ async all() {
36
+ const data = await this.api.request(AllHostedGamesDocument, {});
37
+ return data.allHostedGames;
38
+ }
39
+ /** The hosted games the caller can manage. Session token. */
40
+ async mine() {
41
+ const data = await this.api.request(MyHostedGamesDocument, {});
42
+ return data.myHostedGames;
43
+ }
44
+ /** Publish history for a game, newest first. Session token + manage_apps. */
45
+ async publishes(slug, limit) {
46
+ const data = await this.api.request(HostedGamePublishesDocument, { slug, limit: limit ?? null });
47
+ return data.hostedGamePublishes;
48
+ }
49
+ /**
50
+ * Claim (or re-assert) the hosting slug for an app. Idempotent; a different slug
51
+ * MOVES the game. Registers the shell page and the game origin as redirect URIs
52
+ * and sets `launch_url`. Refuses reserved, taken or malformed slugs
53
+ * (`HOSTED_SLUG_UNAVAILABLE`) and a tier without hosting (`CONTENT_HOSTING_DISABLED`).
54
+ */
55
+ async claim(params) {
56
+ const data = await this.api.request(ClaimGameHostingDocument, {
57
+ input: { appId: params.appId, slug: params.slug ?? null },
58
+ });
59
+ return data.claimGameHosting;
60
+ }
61
+ /** Declare a publish; returns one presigned PUT per file. */
62
+ async beginPublish(params) {
63
+ const data = await this.api.request(BeginGamePublishDocument, { input: params });
64
+ return data.beginGamePublish;
65
+ }
66
+ /** Verify, promote, record LIVE, invalidate. */
67
+ async completePublish(slug, publishId) {
68
+ const data = await this.api.request(CompleteGamePublishDocument, { slug, publishId });
69
+ return data.completeGamePublish;
70
+ }
71
+ async abandonPublish(slug, publishId) {
72
+ const data = await this.api.request(AbandonGamePublishDocument, { slug, publishId });
73
+ return data.abandonGamePublish;
74
+ }
75
+ /** Developer switch: off (DISABLED) or on (LIVE). */
76
+ async setEnabled(slug, enabled) {
77
+ const data = await this.api.request(SetHostedGameEnabledDocument, { input: { slug, enabled } });
78
+ return data.setHostedGameEnabled;
79
+ }
80
+ /** Operator: list or unlist in the lobby and the management UI. */
81
+ async setListing(slug, listed) {
82
+ const data = await this.api.request(SetHostedGameListingDocument, { input: { slug, listed } });
83
+ return data.setHostedGameListing;
84
+ }
85
+ /** Operator: take down (or restore with `false`). */
86
+ async takeDown(slug, takenDown = true) {
87
+ const data = await this.api.request(TakeDownHostedGameDocument, { slug, takenDown });
88
+ return data.takeDownHostedGame;
89
+ }
90
+ }
91
+ /**
92
+ * Upload every file of a publish to its presigned URL, with bounded concurrency and
93
+ * a retry per file. Works in Node 20+ and in a browser (`fetch`, `Blob`/`Uint8Array`
94
+ * bodies). `read(path)` returns the bytes for a manifest path.
95
+ */
96
+ export async function uploadPublishFiles(uploads, read, options = {}) {
97
+ const concurrency = Math.max(1, options.concurrency ?? 8);
98
+ const retries = Math.max(0, options.retries ?? 2);
99
+ const queue = [...uploads];
100
+ const failures = [];
101
+ const worker = async () => {
102
+ for (;;) {
103
+ const u = queue.shift();
104
+ if (!u)
105
+ return;
106
+ let ok = false;
107
+ for (let attempt = 0; attempt <= retries && !ok; attempt++) {
108
+ try {
109
+ const body = await read(u.path);
110
+ const res = await fetch(u.url, {
111
+ method: u.method,
112
+ headers: Object.fromEntries(u.headers.map((h) => [h.name, h.value])),
113
+ body: body,
114
+ });
115
+ ok = res.ok;
116
+ if (!ok && res.status >= 400 && res.status < 500 && res.status !== 429) {
117
+ // A 4xx that is not throttling will not get better: BadDigest, expired URL.
118
+ failures.push(`${u.path}: HTTP ${res.status} ${(await res.text()).slice(0, 200)}`);
119
+ break;
120
+ }
121
+ }
122
+ catch (error) {
123
+ if (attempt === retries)
124
+ failures.push(`${u.path}: ${error.message}`);
125
+ }
126
+ }
127
+ options.onFile?.(u.path, ok);
128
+ if (!ok && !failures.some((f) => f.startsWith(`${u.path}:`)))
129
+ failures.push(`${u.path}: upload failed`);
130
+ }
131
+ };
132
+ await Promise.all(Array.from({ length: Math.min(concurrency, uploads.length) }, worker));
133
+ if (failures.length) {
134
+ throw new Error(`${failures.length} upload(s) failed:\n${failures.join('\n')}`);
135
+ }
136
+ }
@@ -39,6 +39,7 @@
39
39
  */
40
40
  import type { GraphQLClient } from '../client.js';
41
41
  import type { SessionStore } from '../session.js';
42
+ import type { EmbeddedHost } from './embedded-host.js';
42
43
  export interface AppTokenResponse {
43
44
  /** Opaque app-scoped gameplay token. Send to the app's Game API as a Bearer. */
44
45
  token: string;
@@ -162,9 +163,15 @@ export interface SignInParams {
162
163
  /**
163
164
  * `false` to only compute and return the URL without navigating (tests,
164
165
  * SSR, a custom link). Default: navigate with `location.assign` when a
165
- * browser `location` exists.
166
+ * browser `location` exists -- or, when a Crowdy Games shell frames this page,
167
+ * ask the shell to navigate (see `embedded-host.ts`).
166
168
  */
167
169
  navigate?: boolean;
170
+ /**
171
+ * `false` to ignore a framing Crowdy Games shell and run the top-level flow with
172
+ * the given `redirectUri` even when embedded. Default: consult the shell.
173
+ */
174
+ embedded?: boolean;
168
175
  }
169
176
  /**
170
177
  * The hosted sign-in page for the tier a GraphQL endpoint belongs to.
@@ -187,7 +194,21 @@ export declare class PortalAPI {
187
194
  private readonly api;
188
195
  private readonly session;
189
196
  private readonly pkceStore;
190
- constructor(api: GraphQLClient, session: SessionStore, pkceStore?: PkceStore);
197
+ /**
198
+ * The Crowdy Games shell bridge, when this page may be framed by one
199
+ * (`createCrowdyClient` supplies it in a browser; `embeddedHost: false` opts out).
200
+ * See `embedded-host.ts` for what changes when a shell answers.
201
+ */
202
+ private readonly embeddedHost;
203
+ constructor(api: GraphQLClient, session: SessionStore, pkceStore?: PkceStore,
204
+ /**
205
+ * The Crowdy Games shell bridge, when this page may be framed by one
206
+ * (`createCrowdyClient` supplies it in a browser; `embeddedHost: false` opts out).
207
+ * See `embedded-host.ts` for what changes when a shell answers.
208
+ */
209
+ embeddedHost?: EmbeddedHost | null);
210
+ /** The shell framing this game, if one has said hello. Null when top-level or self-hosted. */
211
+ embeddedHostInfo(): import("./embedded-host.js").EmbeddedHostInfo | null;
191
212
  /**
192
213
  * Native/direct mint: exchange the caller's identity session token for an
193
214
  * app-scoped gameplay token. Returns the token; it is NOT stored on this
@@ -1 +1 @@
1
- {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/domains/portal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/domains/portal.ts"],"names":[],"mappings":"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"}
@@ -117,10 +117,21 @@ export function isHostedSignInRequiredError(error) {
117
117
  return /HOSTED_SIGN_IN_REQUIRED|only available to first-party/i.test(typeof e?.message === 'string' ? e.message : '');
118
118
  }
119
119
  export class PortalAPI {
120
- constructor(api, session, pkceStore = new BrowserSessionPkceStore()) {
120
+ constructor(api, session, pkceStore = new BrowserSessionPkceStore(),
121
+ /**
122
+ * The Crowdy Games shell bridge, when this page may be framed by one
123
+ * (`createCrowdyClient` supplies it in a browser; `embeddedHost: false` opts out).
124
+ * See `embedded-host.ts` for what changes when a shell answers.
125
+ */
126
+ embeddedHost = null) {
121
127
  this.api = api;
122
128
  this.session = session;
123
129
  this.pkceStore = pkceStore;
130
+ this.embeddedHost = embeddedHost;
131
+ }
132
+ /** The shell framing this game, if one has said hello. Null when top-level or self-hosted. */
133
+ embeddedHostInfo() {
134
+ return this.embeddedHost?.current() ?? null;
124
135
  }
125
136
  /**
126
137
  * Native/direct mint: exchange the caller's identity session token for an
@@ -225,16 +236,29 @@ export class PortalAPI {
225
236
  * navigated to (useful when `navigate: false`).
226
237
  */
227
238
  async signIn(params) {
228
- const authorizeUrl = params.authorizeUrl ?? defaultHostedSignInUrl(this.api.getEndpoint());
239
+ // EMBEDDED IN THE CROWDY GAMES SHELL? Ask (bounded) before deciding where the code
240
+ // returns and who navigates. A game that is top-level, self-hosted, or framed by a
241
+ // page that is not a shell gets a null here and the flow below is exactly what it
242
+ // was. See embedded-host.ts.
243
+ const host = params.embedded === false ? null : await this.embeddedHost?.hello() ?? null;
244
+ const authorizeUrl = params.authorizeUrl ??
245
+ (host ? `${host.authorizeOrigin}/authorize` : defaultHostedSignInUrl(this.api.getEndpoint()));
229
246
  const url = await this.beginEntry({
230
247
  appId: params.appId,
231
248
  authorizeUrl,
232
- redirectUri: params.redirectUri,
249
+ // The shell's page is the registered return leg; the iframe's own URL is not.
250
+ redirectUri: host ? host.returnUrl : params.redirectUri,
233
251
  state: params.state,
234
252
  });
253
+ if (params.navigate === false)
254
+ return url;
255
+ if (host) {
256
+ this.embeddedHost.navigate(url);
257
+ return url;
258
+ }
235
259
  const loc = globalThis
236
260
  .location;
237
- if (params.navigate !== false && typeof loc?.assign === 'function') {
261
+ if (typeof loc?.assign === 'function') {
238
262
  loc.assign(url);
239
263
  }
240
264
  return url;