@onlyworlds/sdk 4.45.0 → 4.47.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 +11 -0
- package/dist/index.d.ts +174 -2
- package/dist/index.js +205 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ earlier history lives in git log only.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [4.47.0] - 2026-10-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `draught(characterId, policy)`: `GET /character/{id}/context` (keel PR 155, D143, live 2026-10-22 02:05), one Character's context in one read, typed as `OwDraught` with the policy's seven keys (`components`, `mode`, `asOf`, `undated`, `since`, `budget`, `depth`) as `OwDraughtPolicy`.
|
|
12
|
+
- `list` takes `ids` (1 to 200, the list's own order; one the key cannot see is absent) and `createdBy` (a membership id, or `'owner'`), the two list filters of the same PR; both were already reachable through `filter`.
|
|
13
|
+
|
|
14
|
+
## [4.46.0] - 2026-10-22
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **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.
|
|
18
|
+
|
|
8
19
|
## [4.45.0] - 2026-10-22
|
|
9
20
|
|
|
10
21
|
### Added
|
package/dist/index.d.ts
CHANGED
|
@@ -2431,6 +2431,83 @@ interface OwSignedIn {
|
|
|
2431
2431
|
* (`'collaborator'`, with `member_role` the membership's role). Keys by name and last four only,
|
|
2432
2432
|
* never the key; on a shared world only the member's own keys (keel H8).
|
|
2433
2433
|
*/
|
|
2434
|
+
/** The draught's policy (keel PR 155, D143): what GET /character/{id}/context serves and how. */
|
|
2435
|
+
interface OwDraughtPolicy {
|
|
2436
|
+
/** Sections to serve; default every one but `messages`. */
|
|
2437
|
+
components?: OwDraughtComponent[];
|
|
2438
|
+
/** `voice`: her slice, as she would read (default). `act`: through the caller's own view. */
|
|
2439
|
+
mode?: 'voice' | 'act';
|
|
2440
|
+
/** The horizon in the world's time unit; default the world's `time_range_current`. */
|
|
2441
|
+
asOf?: number;
|
|
2442
|
+
/** Undated Events: `include` (default, marked `dated: false`, after the dated) or `exclude`. */
|
|
2443
|
+
undated?: 'include' | 'exclude';
|
|
2444
|
+
/** For `recent`: a /changes cursor, or an ISO 8601 time (as /changes' `after`). Absent, `recent` is empty. */
|
|
2445
|
+
since?: string;
|
|
2446
|
+
/** Items per section, 1 to 1000, default 50; `cut` says what was dropped. */
|
|
2447
|
+
budget?: number;
|
|
2448
|
+
/** The sheet's reach: 1 (default) what her own element links to, 0 none. */
|
|
2449
|
+
depth?: 0 | 1;
|
|
2450
|
+
}
|
|
2451
|
+
type OwDraughtComponent = 'self' | 'role' | 'work' | 'style' | 'events' | 'knowledge' | 'names' | 'sheet' | 'recent' | 'messages' | 'roster';
|
|
2452
|
+
/** An element as the draught serves it: the list route's body plus how it got here. */
|
|
2453
|
+
type OwDraughtItem = OwElement & {
|
|
2454
|
+
/** The link fields that brought it (on the element, or on her own element in `sheet`). */
|
|
2455
|
+
via: string[];
|
|
2456
|
+
/** How many of the packet's elements link to it. */
|
|
2457
|
+
her_links: number;
|
|
2458
|
+
grade: 'whole' | 'known_of';
|
|
2459
|
+
/** On an Event: false when it carries no date (ranked after the dated). */
|
|
2460
|
+
dated?: boolean;
|
|
2461
|
+
};
|
|
2462
|
+
/** One Character's context in one read, GET /character/{id}/context (keel PR 155, D143; the draught). */
|
|
2463
|
+
interface OwDraught {
|
|
2464
|
+
world: {
|
|
2465
|
+
id: string;
|
|
2466
|
+
name: string;
|
|
2467
|
+
description: string;
|
|
2468
|
+
};
|
|
2469
|
+
/** The world's own time fields, as it carries them. */
|
|
2470
|
+
calendar: Record<string, unknown>;
|
|
2471
|
+
as_of: number | null;
|
|
2472
|
+
now: number | null;
|
|
2473
|
+
mode: 'voice' | 'act';
|
|
2474
|
+
/** The policy served under, every key resolved. */
|
|
2475
|
+
policy: Record<string, unknown>;
|
|
2476
|
+
/** {section: items the budget dropped}; empty when whole. */
|
|
2477
|
+
cut: Record<string, number>;
|
|
2478
|
+
recent_cursor: string | null;
|
|
2479
|
+
recent_more: boolean | null;
|
|
2480
|
+
/** Her own element (`self: true`), or a stub when this reader knows her only as known of. */
|
|
2481
|
+
character: (OwElement & {
|
|
2482
|
+
self: true;
|
|
2483
|
+
grade: 'whole' | 'known_of';
|
|
2484
|
+
}) | null;
|
|
2485
|
+
role?: OwDraughtItem[];
|
|
2486
|
+
work?: OwDraughtItem[];
|
|
2487
|
+
style?: OwDraughtItem | null;
|
|
2488
|
+
events?: OwDraughtItem[];
|
|
2489
|
+
knowledge?: OwDraughtItem[];
|
|
2490
|
+
/** Everything else that names her, by type. */
|
|
2491
|
+
names?: Record<string, OwDraughtItem[]>;
|
|
2492
|
+
sheet?: OwDraughtItem[];
|
|
2493
|
+
messages?: OwDraughtItem[];
|
|
2494
|
+
recent?: Array<{
|
|
2495
|
+
op: 'upsert' | 'delete';
|
|
2496
|
+
type: string;
|
|
2497
|
+
id: string;
|
|
2498
|
+
when: string | null;
|
|
2499
|
+
who: string | null;
|
|
2500
|
+
[k: string]: unknown;
|
|
2501
|
+
}>;
|
|
2502
|
+
roster?: Array<{
|
|
2503
|
+
id: string | null;
|
|
2504
|
+
kind: string;
|
|
2505
|
+
role: string;
|
|
2506
|
+
character: string | null;
|
|
2507
|
+
status: string;
|
|
2508
|
+
name: string;
|
|
2509
|
+
}>;
|
|
2510
|
+
}
|
|
2434
2511
|
interface OwAccountWorld {
|
|
2435
2512
|
id: string;
|
|
2436
2513
|
name: string;
|
|
@@ -2639,6 +2716,10 @@ interface OwFileDownload {
|
|
|
2639
2716
|
interface ListParams {
|
|
2640
2717
|
limit?: number;
|
|
2641
2718
|
cursor?: string;
|
|
2719
|
+
/** Only these ids (1 to 200), in the list's own order; one this key cannot see is simply absent (keel PR 155). */
|
|
2720
|
+
ids?: string[];
|
|
2721
|
+
/** A membership id, or `'owner'` for the owner's own writes: only what that member created (keel PR 155). */
|
|
2722
|
+
createdBy?: string;
|
|
2642
2723
|
/** One-level stub expansion, e.g. ['friends', 'location']. */
|
|
2643
2724
|
expand?: string[];
|
|
2644
2725
|
/** Sparse include-set of field names. */
|
|
@@ -2898,7 +2979,7 @@ declare class OwNetworkError extends Error {
|
|
|
2898
2979
|
* - `mint_failed`: the password was right, but keel made no token; the session was still ended.
|
|
2899
2980
|
* - `failed`: any other refusal; the message is keel's when it gave one.
|
|
2900
2981
|
*/
|
|
2901
|
-
type OwSignInReason = 'invalid_credentials' | 'email_unverified' | 'rate_limited' | 'offline' | 'mint_failed' | 'failed';
|
|
2982
|
+
type OwSignInReason = 'invalid_credentials' | 'invalid_grant' | 'state_mismatch' | 'email_unverified' | 'rate_limited' | 'offline' | 'mint_failed' | 'failed';
|
|
2902
2983
|
/**
|
|
2903
2984
|
* A refused `signInWithPassword`. `reason` is what to tell the user; `status` and `code` are
|
|
2904
2985
|
* keel's answer when there was one (`code`: keel's error code, e.g. `email_password_mismatch`).
|
|
@@ -2958,6 +3039,65 @@ declare function kindCanWrite(kind: OwKeyKind): boolean;
|
|
|
2958
3039
|
*/
|
|
2959
3040
|
declare function pinExpectation(kind: OwKeyKind): 'never' | 'optional' | 'required-for-private-reads';
|
|
2960
3041
|
|
|
3042
|
+
/**
|
|
3043
|
+
* Sign in with OnlyWorlds: OAuth 2.1's authorization code with PKCE (S256) for public clients
|
|
3044
|
+
* (keel PR 154, live 2026-10-22; the handoff sheet, Orrery `keel/signin-handoff.md` §3).
|
|
3045
|
+
*
|
|
3046
|
+
* Two calls. `beginSignIn` mints `state` and a PKCE verifier, keeps them under the client id in a
|
|
3047
|
+
* storage the page shares with its callback (sessionStorage by default), and hands back the
|
|
3048
|
+
* authorize URL (sending the browser there where a `location` exists). `completeSignIn` runs on the
|
|
3049
|
+
* callback page: it checks `state` and that the page is the registered return address, takes the
|
|
3050
|
+
* code out of the address bar, trades it at `POST /api/v2/auth/token` with no credential, and
|
|
3051
|
+
* returns the token with the account and the `resume` the tool parked before the redirect. The
|
|
3052
|
+
* SDK keeps nothing: the record is cleared on success and on refusal, and the token is the tool's
|
|
3053
|
+
* to store.
|
|
3054
|
+
*/
|
|
3055
|
+
/** Where the pending sign-in's record lives between the two pages: string in, string out. */
|
|
3056
|
+
interface OwSignInStorage {
|
|
3057
|
+
get(key: string): string | null | undefined;
|
|
3058
|
+
set(key: string, value: string): void;
|
|
3059
|
+
remove(key: string): void;
|
|
3060
|
+
}
|
|
3061
|
+
interface OwSignInBegin {
|
|
3062
|
+
/** The `client_id` keel's registry lists the tool under (`GET /api/v2/auth/clients`). */
|
|
3063
|
+
clientId: string;
|
|
3064
|
+
/** The registered return address, the exact string, `https:` only; never built from `location.origin`. */
|
|
3065
|
+
redirectUri: string;
|
|
3066
|
+
/** Small JSON the tool wants back after the redirect (an open link, a pending join code). */
|
|
3067
|
+
resume?: unknown;
|
|
3068
|
+
/** Defaults to `globalThis.sessionStorage`; without one (Node) the option is required. */
|
|
3069
|
+
storage?: OwSignInStorage;
|
|
3070
|
+
/** How the browser is sent: a function taking the URL, or `false` to only return it. Default: `location.assign` where there is one. */
|
|
3071
|
+
navigate?: ((url: string) => void) | false;
|
|
3072
|
+
}
|
|
3073
|
+
interface OwSignInBegun {
|
|
3074
|
+
/** The authorize URL: `https://www.onlyworlds.com/signin/authorize?...`. */
|
|
3075
|
+
url: string;
|
|
3076
|
+
state: string;
|
|
3077
|
+
}
|
|
3078
|
+
interface OwSignInComplete {
|
|
3079
|
+
clientId: string;
|
|
3080
|
+
storage?: OwSignInStorage;
|
|
3081
|
+
/** The callback page's address; defaults to `location.href`. */
|
|
3082
|
+
url?: string;
|
|
3083
|
+
}
|
|
3084
|
+
/** What `completeSignIn` returns: the account token, shown only here, and what the tool parked. */
|
|
3085
|
+
interface OwSignInResult {
|
|
3086
|
+
/** An `ow_a_` account token: keep it, it is the only copy (the SDK stores none). */
|
|
3087
|
+
token: string;
|
|
3088
|
+
tokenType: 'bearer';
|
|
3089
|
+
account: {
|
|
3090
|
+
id: string;
|
|
3091
|
+
username: string;
|
|
3092
|
+
};
|
|
3093
|
+
/** The `resume` given to `beginSignIn`, or null. */
|
|
3094
|
+
resume: unknown;
|
|
3095
|
+
}
|
|
3096
|
+
/** Base64url without padding (RFC 4648 §5), as PKCE wants it. */
|
|
3097
|
+
declare function base64url(bytes: Uint8Array): string;
|
|
3098
|
+
/** The S256 code challenge of a verifier (RFC 7636 §4.2). */
|
|
3099
|
+
declare function pkceChallenge(verifier: string): Promise<string>;
|
|
3100
|
+
|
|
2961
3101
|
/**
|
|
2962
3102
|
* keel v2 engine absorbed from an earlier v2 client (ow-v2-client v0.9.0),
|
|
2963
3103
|
* wire-corrected against live staging fixtures 2026-07-18.
|
|
@@ -3006,6 +3146,18 @@ declare class OwV2Client {
|
|
|
3006
3146
|
members(): Promise<OwMember[]>;
|
|
3007
3147
|
/** GET /{type}/ -- one cursor page. */
|
|
3008
3148
|
list(type: ElementType | string, params?: ListParams): Promise<OwPage>;
|
|
3149
|
+
/**
|
|
3150
|
+
* GET /character/{id}/context -- the draught (keel PR 155, D143): what one Character knows, as the
|
|
3151
|
+
* recorded world holds it, in one read: her element, what she holds and sits in, her open work,
|
|
3152
|
+
* how she works, the Events she was in, the Narratives she knows, everything else that names
|
|
3153
|
+
* her, her sheet, what changed around her (`since`), and who plays whom; the world's name and
|
|
3154
|
+
* calendar beside. Every body is the list route's for the same reader, with `via`, `her_links`
|
|
3155
|
+
* and `grade`; nothing is inferred. The owner's keys, co-builders and contributors for any
|
|
3156
|
+
* Character they can see; a player seat or a guest for its own Character only (another id is
|
|
3157
|
+
* 404). `asOf` is a filter on the elements' own dates, not time travel. A `cut` that is not
|
|
3158
|
+
* empty means the serving is partial. Served `Cache-Control: private, no-store`.
|
|
3159
|
+
*/
|
|
3160
|
+
draught(characterId: string, policy?: OwDraughtPolicy): Promise<OwDraught>;
|
|
3009
3161
|
/** Cursor-walk every page of a type. Politeness: uses config pageSize. */
|
|
3010
3162
|
listAll(type: ElementType | string, params?: Omit<ListParams, 'cursor'>): AsyncGenerator<OwElement>;
|
|
3011
3163
|
/**
|
|
@@ -3486,6 +3638,26 @@ declare class OwV2Client {
|
|
|
3486
3638
|
scope?: 'read' | 'write';
|
|
3487
3639
|
name?: string;
|
|
3488
3640
|
}): Promise<OwMintedKey>;
|
|
3641
|
+
/**
|
|
3642
|
+
* Start Sign in with OnlyWorlds from a tool's page: mint `state` and a PKCE verifier, keep them
|
|
3643
|
+
* under `clientId` in `storage` (sessionStorage by default; in Node pass one), and send the
|
|
3644
|
+
* browser to `https://www.onlyworlds.com/signin/authorize` (or only return the URL with
|
|
3645
|
+
* `navigate: false`). `redirectUri` is the registered return address as a string, `https:`
|
|
3646
|
+
* only, never built from `location.origin`: a tool's registration is read at
|
|
3647
|
+
* `GET /api/v2/auth/clients`. `resume` is small JSON handed back by `completeSignIn`. No request
|
|
3648
|
+
* is sent; any client, keyless included. The handoff sheet: Orrery `keel/signin-handoff.md` §3.
|
|
3649
|
+
*/
|
|
3650
|
+
signInWithOnlyWorlds(opts: OwSignInBegin): Promise<OwSignInBegun>;
|
|
3651
|
+
/**
|
|
3652
|
+
* Finish Sign in with OnlyWorlds on the callback page: check `state` against the record and the
|
|
3653
|
+
* page against the registered address, take `code` and `state` out of the address bar, trade
|
|
3654
|
+
* the code once at `POST /auth/token` with the PKCE verifier and no credential, and return the
|
|
3655
|
+
* `ow_a_` token with the account and `resume`. The record is cleared on success and on refusal;
|
|
3656
|
+
* the SDK keeps no token: the tool does. Refusals are `OwSignInError`: `state_mismatch` (no
|
|
3657
|
+
* pending sign-in, a different state, or the wrong page; nothing sent), `invalid_grant` (keel's
|
|
3658
|
+
* 400: used, expired, or another client's code), `rate_limited`, `offline`, `failed`.
|
|
3659
|
+
*/
|
|
3660
|
+
completeSignIn(opts: OwSignInComplete): Promise<OwSignInResult>;
|
|
3489
3661
|
/**
|
|
3490
3662
|
* POST /account/ask -- a question about a world the account owns or is in, answered by
|
|
3491
3663
|
* keel's AI proxy from `context` only (build it with buildAskContext from the elements
|
|
@@ -4528,4 +4700,4 @@ declare class TokenResource {
|
|
|
4528
4700
|
getEncryptionInfo(): Promise<EncryptionInfo>;
|
|
4529
4701
|
}
|
|
4530
4702
|
|
|
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 };
|
|
4703
|
+
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 OwDraught, type OwDraughtComponent, type OwDraughtItem, type OwDraughtPolicy, 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;
|
|
@@ -406,11 +559,36 @@ var OwV2Client = class {
|
|
|
406
559
|
cursor: params.cursor,
|
|
407
560
|
expand: params.expand?.join(","),
|
|
408
561
|
fields: params.fields?.join(","),
|
|
562
|
+
ids: params.ids?.join(","),
|
|
563
|
+
created_by: params.createdBy,
|
|
409
564
|
...params.filter,
|
|
410
565
|
as: params.viewAs
|
|
411
566
|
});
|
|
412
567
|
return this.request("GET", `/${type}/`, { query });
|
|
413
568
|
}
|
|
569
|
+
/**
|
|
570
|
+
* GET /character/{id}/context -- the draught (keel PR 155, D143): what one Character knows, as the
|
|
571
|
+
* recorded world holds it, in one read: her element, what she holds and sits in, her open work,
|
|
572
|
+
* how she works, the Events she was in, the Narratives she knows, everything else that names
|
|
573
|
+
* her, her sheet, what changed around her (`since`), and who plays whom; the world's name and
|
|
574
|
+
* calendar beside. Every body is the list route's for the same reader, with `via`, `her_links`
|
|
575
|
+
* and `grade`; nothing is inferred. The owner's keys, co-builders and contributors for any
|
|
576
|
+
* Character they can see; a player seat or a guest for its own Character only (another id is
|
|
577
|
+
* 404). `asOf` is a filter on the elements' own dates, not time travel. A `cut` that is not
|
|
578
|
+
* empty means the serving is partial. Served `Cache-Control: private, no-store`.
|
|
579
|
+
*/
|
|
580
|
+
async draught(characterId, policy = {}) {
|
|
581
|
+
const query = buildQuery({
|
|
582
|
+
components: policy.components?.join(","),
|
|
583
|
+
mode: policy.mode,
|
|
584
|
+
as_of: policy.asOf,
|
|
585
|
+
undated: policy.undated,
|
|
586
|
+
since: policy.since,
|
|
587
|
+
budget: policy.budget,
|
|
588
|
+
depth: policy.depth
|
|
589
|
+
});
|
|
590
|
+
return this.request("GET", `/character/${characterId}/context`, { query });
|
|
591
|
+
}
|
|
414
592
|
/** Cursor-walk every page of a type. Politeness: uses config pageSize. */
|
|
415
593
|
async *listAll(type, params = {}) {
|
|
416
594
|
let cursor;
|
|
@@ -1177,6 +1355,31 @@ var OwV2Client = class {
|
|
|
1177
1355
|
if (opts.name !== void 0) body.name = opts.name;
|
|
1178
1356
|
return this.request("POST", `/account/worlds/${encodeURIComponent(worldId)}/keys`, { write: true, body });
|
|
1179
1357
|
}
|
|
1358
|
+
// -- Sign in with OnlyWorlds (OAuth 2.1 code + PKCE, keel PR 154) ---------
|
|
1359
|
+
/**
|
|
1360
|
+
* Start Sign in with OnlyWorlds from a tool's page: mint `state` and a PKCE verifier, keep them
|
|
1361
|
+
* under `clientId` in `storage` (sessionStorage by default; in Node pass one), and send the
|
|
1362
|
+
* browser to `https://www.onlyworlds.com/signin/authorize` (or only return the URL with
|
|
1363
|
+
* `navigate: false`). `redirectUri` is the registered return address as a string, `https:`
|
|
1364
|
+
* only, never built from `location.origin`: a tool's registration is read at
|
|
1365
|
+
* `GET /api/v2/auth/clients`. `resume` is small JSON handed back by `completeSignIn`. No request
|
|
1366
|
+
* is sent; any client, keyless included. The handoff sheet: Orrery `keel/signin-handoff.md` §3.
|
|
1367
|
+
*/
|
|
1368
|
+
async signInWithOnlyWorlds(opts) {
|
|
1369
|
+
return beginSignIn(this.baseUrl, opts);
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Finish Sign in with OnlyWorlds on the callback page: check `state` against the record and the
|
|
1373
|
+
* page against the registered address, take `code` and `state` out of the address bar, trade
|
|
1374
|
+
* the code once at `POST /auth/token` with the PKCE verifier and no credential, and return the
|
|
1375
|
+
* `ow_a_` token with the account and `resume`. The record is cleared on success and on refusal;
|
|
1376
|
+
* the SDK keeps no token: the tool does. Refusals are `OwSignInError`: `state_mismatch` (no
|
|
1377
|
+
* pending sign-in, a different state, or the wrong page; nothing sent), `invalid_grant` (keel's
|
|
1378
|
+
* 400: used, expired, or another client's code), `rate_limited`, `offline`, `failed`.
|
|
1379
|
+
*/
|
|
1380
|
+
async completeSignIn(opts) {
|
|
1381
|
+
return completeSignIn(this.baseUrl, this.fetchImpl, opts);
|
|
1382
|
+
}
|
|
1180
1383
|
// -- Ask your world (keel #159) -------------------------------------------
|
|
1181
1384
|
/**
|
|
1182
1385
|
* POST /account/ask -- a question about a world the account owns or is in, answered by
|
|
@@ -3676,6 +3879,7 @@ export {
|
|
|
3676
3879
|
absYearForEraYear,
|
|
3677
3880
|
absoluteYear,
|
|
3678
3881
|
approxYearLength,
|
|
3882
|
+
base64url,
|
|
3679
3883
|
breakdownStep,
|
|
3680
3884
|
buildAskContext,
|
|
3681
3885
|
buildWorldIndex,
|
|
@@ -3706,6 +3910,7 @@ export {
|
|
|
3706
3910
|
parseDate,
|
|
3707
3911
|
parseErrorEnvelope,
|
|
3708
3912
|
pinExpectation,
|
|
3913
|
+
pkceChallenge,
|
|
3709
3914
|
planPhoto,
|
|
3710
3915
|
preparePhoto,
|
|
3711
3916
|
readCalendarConfig,
|