@onlyworlds/sdk 4.45.0 → 4.46.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ earlier history lives in git log only.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.46.0] - 2026-10-22
9
+
10
+ ### Added
11
+ - **Sign in with OnlyWorlds** (keel PR 154, live 2026-10-22 01:50; the handoff sheet §3): `signInWithOnlyWorlds({ clientId, redirectUri, resume, storage, navigate })` mints `state` and a PKCE S256 verifier, keeps them under the client id in `storage` (sessionStorage by default; required in Node), refuses a `redirectUri` that is not an `https:` string before sending, and sends the browser to `/signin/authorize`. `completeSignIn({ clientId, storage, url })` on the callback page checks `state` and the page, takes `code` and `state` out of the address bar, trades the code once at `POST /api/v2/auth/token` with no credential, and returns `OwSignInResult` (`token`, `account`, `resume`); the record is cleared either way and the SDK keeps no token. `OwSignInError` gains `invalid_grant` and `state_mismatch`. `pkceChallenge` and `base64url` exported.
12
+
8
13
  ## [4.45.0] - 2026-10-22
9
14
 
10
15
  ### Added
package/dist/index.d.ts CHANGED
@@ -2898,7 +2898,7 @@ declare class OwNetworkError extends Error {
2898
2898
  * - `mint_failed`: the password was right, but keel made no token; the session was still ended.
2899
2899
  * - `failed`: any other refusal; the message is keel's when it gave one.
2900
2900
  */
2901
- type OwSignInReason = 'invalid_credentials' | 'email_unverified' | 'rate_limited' | 'offline' | 'mint_failed' | 'failed';
2901
+ type OwSignInReason = 'invalid_credentials' | 'invalid_grant' | 'state_mismatch' | 'email_unverified' | 'rate_limited' | 'offline' | 'mint_failed' | 'failed';
2902
2902
  /**
2903
2903
  * A refused `signInWithPassword`. `reason` is what to tell the user; `status` and `code` are
2904
2904
  * keel's answer when there was one (`code`: keel's error code, e.g. `email_password_mismatch`).
@@ -2958,6 +2958,65 @@ declare function kindCanWrite(kind: OwKeyKind): boolean;
2958
2958
  */
2959
2959
  declare function pinExpectation(kind: OwKeyKind): 'never' | 'optional' | 'required-for-private-reads';
2960
2960
 
2961
+ /**
2962
+ * Sign in with OnlyWorlds: OAuth 2.1's authorization code with PKCE (S256) for public clients
2963
+ * (keel PR 154, live 2026-10-22; the handoff sheet, Orrery `keel/signin-handoff.md` §3).
2964
+ *
2965
+ * Two calls. `beginSignIn` mints `state` and a PKCE verifier, keeps them under the client id in a
2966
+ * storage the page shares with its callback (sessionStorage by default), and hands back the
2967
+ * authorize URL (sending the browser there where a `location` exists). `completeSignIn` runs on the
2968
+ * callback page: it checks `state` and that the page is the registered return address, takes the
2969
+ * code out of the address bar, trades it at `POST /api/v2/auth/token` with no credential, and
2970
+ * returns the token with the account and the `resume` the tool parked before the redirect. The
2971
+ * SDK keeps nothing: the record is cleared on success and on refusal, and the token is the tool's
2972
+ * to store.
2973
+ */
2974
+ /** Where the pending sign-in's record lives between the two pages: string in, string out. */
2975
+ interface OwSignInStorage {
2976
+ get(key: string): string | null | undefined;
2977
+ set(key: string, value: string): void;
2978
+ remove(key: string): void;
2979
+ }
2980
+ interface OwSignInBegin {
2981
+ /** The `client_id` keel's registry lists the tool under (`GET /api/v2/auth/clients`). */
2982
+ clientId: string;
2983
+ /** The registered return address, the exact string, `https:` only; never built from `location.origin`. */
2984
+ redirectUri: string;
2985
+ /** Small JSON the tool wants back after the redirect (an open link, a pending join code). */
2986
+ resume?: unknown;
2987
+ /** Defaults to `globalThis.sessionStorage`; without one (Node) the option is required. */
2988
+ storage?: OwSignInStorage;
2989
+ /** How the browser is sent: a function taking the URL, or `false` to only return it. Default: `location.assign` where there is one. */
2990
+ navigate?: ((url: string) => void) | false;
2991
+ }
2992
+ interface OwSignInBegun {
2993
+ /** The authorize URL: `https://www.onlyworlds.com/signin/authorize?...`. */
2994
+ url: string;
2995
+ state: string;
2996
+ }
2997
+ interface OwSignInComplete {
2998
+ clientId: string;
2999
+ storage?: OwSignInStorage;
3000
+ /** The callback page's address; defaults to `location.href`. */
3001
+ url?: string;
3002
+ }
3003
+ /** What `completeSignIn` returns: the account token, shown only here, and what the tool parked. */
3004
+ interface OwSignInResult {
3005
+ /** An `ow_a_` account token: keep it, it is the only copy (the SDK stores none). */
3006
+ token: string;
3007
+ tokenType: 'bearer';
3008
+ account: {
3009
+ id: string;
3010
+ username: string;
3011
+ };
3012
+ /** The `resume` given to `beginSignIn`, or null. */
3013
+ resume: unknown;
3014
+ }
3015
+ /** Base64url without padding (RFC 4648 §5), as PKCE wants it. */
3016
+ declare function base64url(bytes: Uint8Array): string;
3017
+ /** The S256 code challenge of a verifier (RFC 7636 §4.2). */
3018
+ declare function pkceChallenge(verifier: string): Promise<string>;
3019
+
2961
3020
  /**
2962
3021
  * keel v2 engine absorbed from an earlier v2 client (ow-v2-client v0.9.0),
2963
3022
  * wire-corrected against live staging fixtures 2026-07-18.
@@ -3486,6 +3545,26 @@ declare class OwV2Client {
3486
3545
  scope?: 'read' | 'write';
3487
3546
  name?: string;
3488
3547
  }): Promise<OwMintedKey>;
3548
+ /**
3549
+ * Start Sign in with OnlyWorlds from a tool's page: mint `state` and a PKCE verifier, keep them
3550
+ * under `clientId` in `storage` (sessionStorage by default; in Node pass one), and send the
3551
+ * browser to `https://www.onlyworlds.com/signin/authorize` (or only return the URL with
3552
+ * `navigate: false`). `redirectUri` is the registered return address as a string, `https:`
3553
+ * only, never built from `location.origin`: a tool's registration is read at
3554
+ * `GET /api/v2/auth/clients`. `resume` is small JSON handed back by `completeSignIn`. No request
3555
+ * is sent; any client, keyless included. The handoff sheet: Orrery `keel/signin-handoff.md` §3.
3556
+ */
3557
+ signInWithOnlyWorlds(opts: OwSignInBegin): Promise<OwSignInBegun>;
3558
+ /**
3559
+ * Finish Sign in with OnlyWorlds on the callback page: check `state` against the record and the
3560
+ * page against the registered address, take `code` and `state` out of the address bar, trade
3561
+ * the code once at `POST /auth/token` with the PKCE verifier and no credential, and return the
3562
+ * `ow_a_` token with the account and `resume`. The record is cleared on success and on refusal;
3563
+ * the SDK keeps no token: the tool does. Refusals are `OwSignInError`: `state_mismatch` (no
3564
+ * pending sign-in, a different state, or the wrong page; nothing sent), `invalid_grant` (keel's
3565
+ * 400: used, expired, or another client's code), `rate_limited`, `offline`, `failed`.
3566
+ */
3567
+ completeSignIn(opts: OwSignInComplete): Promise<OwSignInResult>;
3489
3568
  /**
3490
3569
  * POST /account/ask -- a question about a world the account owns or is in, answered by
3491
3570
  * keel's AI proxy from `context` only (build it with buildAskContext from the elements
@@ -4528,4 +4607,4 @@ declare class TokenResource {
4528
4607
  getEncryptionInfo(): Promise<EncryptionInfo>;
4529
4608
  }
4530
4609
 
4531
- export { AGENT_DOORS, ASK_LIMITS, type AccessKeyResponse, CALENDAR_FIELD, CALENDAR_SUPERTYPE, type CalendarConfig, type CalendarEra, type CalendarIntercalary, type CalendarMonths, type CalendarWeek, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, type EraDomain, FAMILY_COLORS, FAMILY_ORDER, FIELD_ACRONYMS, FIELD_SCHEMA, FILE_EXTENSIONS, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, type OwAccountWorld, type OwAgentJoined, type OwAllowance, OwApiError, type OwAskAnswer, type OwAskContext, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwCalendarElement, type OwChange, type OwChangeDelete, type OwChangeUpsert, type OwChangesPage, type OwClientConfig, type OwCreatedWorldLink, type OwDeleteReason, type OwElement, type OwElementBase, type OwErrorBody, type OwFileAddedBy, type OwFileDownload, type OwFileKind, type OwFileRow, type OwFileTicket, type OwJoinPreview, type OwJoined, type OwJoinedMembership, type OwKeyKind, type OwLinkEdit, type OwLookupElement, type OwLookupHit, type OwLookupLink, type OwLookupOptions, type OwLookupReason, type OwMe, type OwMediaTicket, type OwMember, type OwMemberRole, type OwMintedKey, OwNetworkError, type OwPage, type OwPhotoOptions, type OwPhotoPlan, type OwPlayerJoined, OwPreconditionFailedError, type OwPreparedPhoto, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwRenderOptions, type OwResyncReason, type OwSeat, type OwSeatCreated, OwSignInError, type OwSignInReason, type OwSignedIn, type OwSortAddToRow, type OwSortAnswer, type OwSortAskRow, type OwSortLinkRow, type OwSortMakeRow, type OwSortNote, type OwSortRef, type OwSortRow, type OwUploadedFile, type OwUploadedImage, OwV2Client, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldFileRow, type OwWorldFiles, type OwWorldIndex, type OwWorldLink, type OwWorldMeta, type OwWriteDoor, PHOTO_MAX_BYTES, PHOTO_TYPES, PUBLIC_FIELDS, type ParseError, type ParseOptions, type ParseResult, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type StepBreakdown, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, type WorldCalendar, absYearForEraYear, absoluteYear, approxYearLength, breakdownStep, buildAskContext, buildWorldIndex, detectKeyKind, displayYear, elementColor, eraForStep, errorFromResponse, familyOf, fieldLabel, fileKindOf, floorDiv, floorMod, formatDuration, formatMidLabel, formatStep, formatYearLabel, getElementIcon, getElementLabel, isAgentDoor, isDemoKey, isKnownOf, isLeapYear, isParseError, kindCanWrite, lookupWorld, monthStartSteps, parseDate, parseErrorEnvelope, pinExpectation, planPhoto, preparePhoto, readCalendarConfig, readCalendarValue, readWorldFile, renderElement, resolveCalendarLens, resolveEraDomains, validateCalendarConfig, weekdayForStep, worldCalendars, writeWorldFile, yearLength, yearStartStep };
4610
+ export { AGENT_DOORS, ASK_LIMITS, type AccessKeyResponse, CALENDAR_FIELD, CALENDAR_SUPERTYPE, type CalendarConfig, type CalendarEra, type CalendarIntercalary, type CalendarMonths, type CalendarWeek, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, type EraDomain, FAMILY_COLORS, FAMILY_ORDER, FIELD_ACRONYMS, FIELD_SCHEMA, FILE_EXTENSIONS, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, type OwAccountWorld, type OwAgentJoined, type OwAllowance, OwApiError, type OwAskAnswer, type OwAskContext, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwCalendarElement, type OwChange, type OwChangeDelete, type OwChangeUpsert, type OwChangesPage, type OwClientConfig, type OwCreatedWorldLink, type OwDeleteReason, type OwElement, type OwElementBase, type OwErrorBody, type OwFileAddedBy, type OwFileDownload, type OwFileKind, type OwFileRow, type OwFileTicket, type OwJoinPreview, type OwJoined, type OwJoinedMembership, type OwKeyKind, type OwLinkEdit, type OwLookupElement, type OwLookupHit, type OwLookupLink, type OwLookupOptions, type OwLookupReason, type OwMe, type OwMediaTicket, type OwMember, type OwMemberRole, type OwMintedKey, OwNetworkError, type OwPage, type OwPhotoOptions, type OwPhotoPlan, type OwPlayerJoined, OwPreconditionFailedError, type OwPreparedPhoto, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwRenderOptions, type OwResyncReason, type OwSeat, type OwSeatCreated, type OwSignInBegin, type OwSignInBegun, type OwSignInComplete, OwSignInError, type OwSignInReason, type OwSignInResult, type OwSignInStorage, type OwSignedIn, type OwSortAddToRow, type OwSortAnswer, type OwSortAskRow, type OwSortLinkRow, type OwSortMakeRow, type OwSortNote, type OwSortRef, type OwSortRow, type OwUploadedFile, type OwUploadedImage, OwV2Client, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldFileRow, type OwWorldFiles, type OwWorldIndex, type OwWorldLink, type OwWorldMeta, type OwWriteDoor, PHOTO_MAX_BYTES, PHOTO_TYPES, PUBLIC_FIELDS, type ParseError, type ParseOptions, type ParseResult, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type StepBreakdown, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, type WorldCalendar, absYearForEraYear, absoluteYear, approxYearLength, base64url, breakdownStep, buildAskContext, buildWorldIndex, detectKeyKind, displayYear, elementColor, eraForStep, errorFromResponse, familyOf, fieldLabel, fileKindOf, floorDiv, floorMod, formatDuration, formatMidLabel, formatStep, formatYearLabel, getElementIcon, getElementLabel, isAgentDoor, isDemoKey, isKnownOf, isLeapYear, isParseError, kindCanWrite, lookupWorld, monthStartSteps, parseDate, parseErrorEnvelope, pinExpectation, pkceChallenge, planPhoto, preparePhoto, readCalendarConfig, readCalendarValue, readWorldFile, renderElement, resolveCalendarLens, resolveEraDomains, validateCalendarConfig, weekdayForStep, worldCalendars, writeWorldFile, yearLength, yearStartStep };
package/dist/index.js CHANGED
@@ -338,6 +338,159 @@ function pinExpectation(kind) {
338
338
  }
339
339
  }
340
340
 
341
+ // src/v2/oauth.ts
342
+ var RECORD_PREFIX = "ow_signin:";
343
+ function defaultStorage(name) {
344
+ const ss = globalThis.sessionStorage;
345
+ if (!ss || typeof ss.getItem !== "function") {
346
+ throw new TypeError(`${name}: no sessionStorage here; pass storage ({get, set, remove}) to hold the pending sign-in`);
347
+ }
348
+ return { get: (k) => ss.getItem(k), set: (k, v) => ss.setItem(k, v), remove: (k) => ss.removeItem(k) };
349
+ }
350
+ function webCrypto() {
351
+ const c = globalThis.crypto;
352
+ if (!c || typeof c.getRandomValues !== "function" || !c.subtle) {
353
+ throw new TypeError("Sign in with OnlyWorlds needs Web Crypto (crypto.getRandomValues and crypto.subtle)");
354
+ }
355
+ return c;
356
+ }
357
+ function base64url(bytes) {
358
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
359
+ let out = "";
360
+ for (let i = 0; i < bytes.length; i += 3) {
361
+ const b0 = bytes[i], b1 = bytes[i + 1], b2 = bytes[i + 2];
362
+ const n = b0 << 16 | (b1 ?? 0) << 8 | (b2 ?? 0);
363
+ out += ALPHABET[n >> 18 & 63] + ALPHABET[n >> 12 & 63];
364
+ if (b1 !== void 0) out += ALPHABET[n >> 6 & 63];
365
+ if (b2 !== void 0) out += ALPHABET[n & 63];
366
+ }
367
+ return out;
368
+ }
369
+ async function pkceChallenge(verifier) {
370
+ const digest = await webCrypto().subtle.digest("SHA-256", new TextEncoder().encode(verifier));
371
+ return base64url(new Uint8Array(digest));
372
+ }
373
+ function randomToken(bytes) {
374
+ const buf = new Uint8Array(bytes);
375
+ webCrypto().getRandomValues(buf);
376
+ return base64url(buf);
377
+ }
378
+ function sitePage(baseUrl, path) {
379
+ return `${new URL(baseUrl).origin}${path}`;
380
+ }
381
+ async function beginSignIn(baseUrl, opts) {
382
+ const { clientId, redirectUri, resume = null } = opts ?? {};
383
+ if (typeof clientId !== "string" || clientId === "") throw new TypeError("signInWithOnlyWorlds needs clientId");
384
+ if (typeof redirectUri !== "string" || !redirectUri.startsWith("https://")) {
385
+ throw new TypeError("signInWithOnlyWorlds: redirectUri must be the registered https: address, as a string");
386
+ }
387
+ const storage = opts.storage ?? defaultStorage("signInWithOnlyWorlds");
388
+ const state = randomToken(32);
389
+ const verifier = randomToken(32);
390
+ const challenge = await pkceChallenge(verifier);
391
+ storage.set(RECORD_PREFIX + clientId, JSON.stringify({ state, verifier, redirectUri, resume }));
392
+ const url = new URL(sitePage(baseUrl, "/signin/authorize"));
393
+ url.searchParams.set("client_id", clientId);
394
+ url.searchParams.set("redirect_uri", redirectUri);
395
+ url.searchParams.set("state", state);
396
+ url.searchParams.set("code_challenge", challenge);
397
+ url.searchParams.set("code_challenge_method", "S256");
398
+ const href = url.toString();
399
+ if (opts.navigate !== false) {
400
+ const go = opts.navigate ?? globalThis.location?.assign?.bind(globalThis.location);
401
+ if (go) go(href);
402
+ }
403
+ return { url: href, state };
404
+ }
405
+ function samePage(a, b) {
406
+ try {
407
+ const x = new URL(a), y = new URL(b);
408
+ return x.origin === y.origin && x.pathname.replace(/\/$/, "") === y.pathname.replace(/\/$/, "");
409
+ } catch {
410
+ return false;
411
+ }
412
+ }
413
+ async function completeSignIn(baseUrl, fetchImpl, opts) {
414
+ const { clientId } = opts ?? {};
415
+ if (typeof clientId !== "string" || clientId === "") throw new TypeError("completeSignIn needs clientId");
416
+ const storage = opts.storage ?? defaultStorage("completeSignIn");
417
+ const here = opts.url ?? globalThis.location?.href;
418
+ if (typeof here !== "string") throw new TypeError("completeSignIn: no location here; pass url (the callback page's address)");
419
+ const page = new URL(here);
420
+ const code = page.searchParams.get("code");
421
+ const state = page.searchParams.get("state");
422
+ const hist = globalThis.history;
423
+ if (hist && typeof hist.replaceState === "function" && (page.searchParams.has("code") || page.searchParams.has("state"))) {
424
+ const clean = new URL(here);
425
+ clean.searchParams.delete("code");
426
+ clean.searchParams.delete("state");
427
+ try {
428
+ hist.replaceState(hist.state, "", clean.toString());
429
+ } catch {
430
+ }
431
+ }
432
+ const key = RECORD_PREFIX + clientId;
433
+ const raw = storage.get(key);
434
+ storage.remove(key);
435
+ let record = null;
436
+ try {
437
+ record = raw ? JSON.parse(raw) : null;
438
+ } catch {
439
+ record = null;
440
+ }
441
+ if (!record || typeof record.state !== "string" || typeof record.verifier !== "string" || typeof record.redirectUri !== "string") {
442
+ throw new OwSignInError("state_mismatch", "No sign-in is pending for this tool here: start again from the sign-in button.");
443
+ }
444
+ if (!code || !state || state !== record.state) {
445
+ throw new OwSignInError("state_mismatch", "This page was not reached from the sign-in that was started here: start again.");
446
+ }
447
+ if (!samePage(here, record.redirectUri)) {
448
+ throw new OwSignInError("state_mismatch", "This page is not the return address the sign-in was started with.");
449
+ }
450
+ let res;
451
+ try {
452
+ res = await fetchImpl(`${baseUrl}/auth/token`, {
453
+ method: "POST",
454
+ headers: { "Content-Type": "application/json" },
455
+ body: JSON.stringify({
456
+ grant_type: "authorization_code",
457
+ code,
458
+ code_verifier: record.verifier,
459
+ client_id: clientId,
460
+ redirect_uri: record.redirectUri
461
+ })
462
+ });
463
+ } catch {
464
+ throw new OwSignInError("offline", "OnlyWorlds could not be reached: signing in needs a connection.");
465
+ }
466
+ const retryAfter = parseRetryAfter(res.headers?.get?.("Retry-After") ?? null) ?? void 0;
467
+ let body = null;
468
+ try {
469
+ body = await res.json();
470
+ } catch {
471
+ body = null;
472
+ }
473
+ const err = body?.error;
474
+ const errCode = typeof err?.code === "string" ? err.code : void 0;
475
+ const errMessage = typeof err?.message === "string" ? err.message : void 0;
476
+ if (res.status === 429) {
477
+ throw new OwSignInError("rate_limited", errMessage ?? "Too many sign-in attempts. Wait a while, then try again.", { status: 429, code: errCode, retryAfter });
478
+ }
479
+ if (res.status === 400 && errCode === "invalid_grant") {
480
+ throw new OwSignInError("invalid_grant", errMessage ?? "The sign-in code could not be exchanged: it was used, expired, or made for another tool. Start again.", { status: 400, code: errCode });
481
+ }
482
+ const ok = body;
483
+ if (!res.ok || typeof ok?.token !== "string" || ok.token === "" || typeof ok.account?.id !== "string") {
484
+ throw new OwSignInError("failed", errMessage ?? `Signing in did not work (${res.status}).`, { status: res.status, code: errCode });
485
+ }
486
+ return {
487
+ token: ok.token,
488
+ tokenType: "bearer",
489
+ account: { id: ok.account.id, username: typeof ok.account.username === "string" ? ok.account.username : "" },
490
+ resume: record.resume ?? null
491
+ };
492
+ }
493
+
341
494
  // src/v2/client.ts
342
495
  var DEFAULT_BASE_URL = "https://www.onlyworlds.com/api/v2";
343
496
  var DEFAULT_PAGE_SIZE = 100;
@@ -1177,6 +1330,31 @@ var OwV2Client = class {
1177
1330
  if (opts.name !== void 0) body.name = opts.name;
1178
1331
  return this.request("POST", `/account/worlds/${encodeURIComponent(worldId)}/keys`, { write: true, body });
1179
1332
  }
1333
+ // -- Sign in with OnlyWorlds (OAuth 2.1 code + PKCE, keel PR 154) ---------
1334
+ /**
1335
+ * Start Sign in with OnlyWorlds from a tool's page: mint `state` and a PKCE verifier, keep them
1336
+ * under `clientId` in `storage` (sessionStorage by default; in Node pass one), and send the
1337
+ * browser to `https://www.onlyworlds.com/signin/authorize` (or only return the URL with
1338
+ * `navigate: false`). `redirectUri` is the registered return address as a string, `https:`
1339
+ * only, never built from `location.origin`: a tool's registration is read at
1340
+ * `GET /api/v2/auth/clients`. `resume` is small JSON handed back by `completeSignIn`. No request
1341
+ * is sent; any client, keyless included. The handoff sheet: Orrery `keel/signin-handoff.md` §3.
1342
+ */
1343
+ async signInWithOnlyWorlds(opts) {
1344
+ return beginSignIn(this.baseUrl, opts);
1345
+ }
1346
+ /**
1347
+ * Finish Sign in with OnlyWorlds on the callback page: check `state` against the record and the
1348
+ * page against the registered address, take `code` and `state` out of the address bar, trade
1349
+ * the code once at `POST /auth/token` with the PKCE verifier and no credential, and return the
1350
+ * `ow_a_` token with the account and `resume`. The record is cleared on success and on refusal;
1351
+ * the SDK keeps no token: the tool does. Refusals are `OwSignInError`: `state_mismatch` (no
1352
+ * pending sign-in, a different state, or the wrong page; nothing sent), `invalid_grant` (keel's
1353
+ * 400: used, expired, or another client's code), `rate_limited`, `offline`, `failed`.
1354
+ */
1355
+ async completeSignIn(opts) {
1356
+ return completeSignIn(this.baseUrl, this.fetchImpl, opts);
1357
+ }
1180
1358
  // -- Ask your world (keel #159) -------------------------------------------
1181
1359
  /**
1182
1360
  * POST /account/ask -- a question about a world the account owns or is in, answered by
@@ -3676,6 +3854,7 @@ export {
3676
3854
  absYearForEraYear,
3677
3855
  absoluteYear,
3678
3856
  approxYearLength,
3857
+ base64url,
3679
3858
  breakdownStep,
3680
3859
  buildAskContext,
3681
3860
  buildWorldIndex,
@@ -3706,6 +3885,7 @@ export {
3706
3885
  parseDate,
3707
3886
  parseErrorEnvelope,
3708
3887
  pinExpectation,
3888
+ pkceChallenge,
3709
3889
  planPhoto,
3710
3890
  preparePhoto,
3711
3891
  readCalendarConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlyworlds/sdk",
3
- "version": "4.45.0",
3
+ "version": "4.46.0",
4
4
  "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
5
  "type": "module",
6
6
  "exports": {