@fleetless/sdk 2.1.0 → 3.0.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 +91 -0
- package/README.md +105 -59
- package/dist/index.cjs +2068 -1938
- package/dist/index.d.cts +460 -568
- package/dist/index.d.ts +460 -568
- package/dist/index.js +2068 -1938
- package/package.json +4 -5
package/dist/index.d.ts
CHANGED
|
@@ -18,9 +18,9 @@ import { z } from 'zod';
|
|
|
18
18
|
* admits it lost track.
|
|
19
19
|
*/
|
|
20
20
|
declare const jobState: z.ZodEnum<{
|
|
21
|
+
failed: "failed";
|
|
21
22
|
running: "running";
|
|
22
23
|
succeeded: "succeeded";
|
|
23
|
-
failed: "failed";
|
|
24
24
|
cancelled: "cancelled";
|
|
25
25
|
lost: "lost";
|
|
26
26
|
}>;
|
|
@@ -30,9 +30,9 @@ declare const job: z.ZodObject<{
|
|
|
30
30
|
robot_id: z.ZodUUID;
|
|
31
31
|
slug: z.ZodString;
|
|
32
32
|
state: z.ZodEnum<{
|
|
33
|
+
failed: "failed";
|
|
33
34
|
running: "running";
|
|
34
35
|
succeeded: "succeeded";
|
|
35
|
-
failed: "failed";
|
|
36
36
|
cancelled: "cancelled";
|
|
37
37
|
lost: "lost";
|
|
38
38
|
}>;
|
|
@@ -65,9 +65,9 @@ declare const jobEvent: z.ZodObject<{
|
|
|
65
65
|
robot_id: z.ZodUUID;
|
|
66
66
|
slug: z.ZodString;
|
|
67
67
|
state: z.ZodEnum<{
|
|
68
|
+
failed: "failed";
|
|
68
69
|
running: "running";
|
|
69
70
|
succeeded: "succeeded";
|
|
70
|
-
failed: "failed";
|
|
71
71
|
cancelled: "cancelled";
|
|
72
72
|
lost: "lost";
|
|
73
73
|
}>;
|
|
@@ -97,9 +97,9 @@ declare const busyDetails: z.ZodObject<{
|
|
|
97
97
|
robot_id: z.ZodUUID;
|
|
98
98
|
slug: z.ZodString;
|
|
99
99
|
state: z.ZodEnum<{
|
|
100
|
+
failed: "failed";
|
|
100
101
|
running: "running";
|
|
101
102
|
succeeded: "succeeded";
|
|
102
|
-
failed: "failed";
|
|
103
103
|
cancelled: "cancelled";
|
|
104
104
|
lost: "lost";
|
|
105
105
|
}>;
|
|
@@ -256,6 +256,9 @@ type DatapointEvent = z.infer<typeof datapointEvent>;
|
|
|
256
256
|
* Access plus refresh (spec §3.4). The access token is short-lived; the
|
|
257
257
|
* refresh token rotates on every use, so a stolen one is detectable when the
|
|
258
258
|
* original is presented again.
|
|
259
|
+
*
|
|
260
|
+
* One shape for both identity spaces: a session is a session, and the claims
|
|
261
|
+
* inside the token are what differ.
|
|
259
262
|
*/
|
|
260
263
|
declare const sessionTokens: z.ZodObject<{
|
|
261
264
|
access_token: z.ZodString;
|
|
@@ -265,105 +268,149 @@ declare const sessionTokens: z.ZodObject<{
|
|
|
265
268
|
type SessionTokens = z.infer<typeof sessionTokens>;
|
|
266
269
|
|
|
267
270
|
/**
|
|
268
|
-
* **
|
|
269
|
-
*
|
|
271
|
+
* **Why a federated sign-in ended without a session, in a code the app can
|
|
272
|
+
* branch on** — carried back to the app's own `redirect_uri` as `error`, not
|
|
273
|
+
* rendered by Fleetless (D2). The only Fleetless-rendered page in this flow is
|
|
274
|
+
* the one for a state that can no longer be resolved to a redirect URI, because
|
|
275
|
+
* then there is nowhere to send the answer.
|
|
276
|
+
*
|
|
277
|
+
* The five rows of D4's table are the first five values plus `no_access`:
|
|
270
278
|
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
279
|
+
* - `no_access` — the identity is unknown and nothing admits it, or the account
|
|
280
|
+
* it names is not `active`. **One code for both**, because to the person the
|
|
281
|
+
* remedy is the same — ask somebody to let you in — and a code that split an
|
|
282
|
+
* outcome nobody acts on differently would tell a stranger which half applied.
|
|
283
|
+
* - `email_taken` — the address already belongs to another app user, and the
|
|
284
|
+
* provider is not permitted to link (`link_verified_emails`, or the provider
|
|
285
|
+
* did not assert `email_verified`). Deliberately not `no_access`: the remedy
|
|
286
|
+
* is different — *sign in the way you signed up*.
|
|
287
|
+
* - `email_unverified` — the provider asserted an address without
|
|
288
|
+
* `email_verified`. **An unverified address never produces or links an
|
|
289
|
+
* account**, whatever the rest of the policy says.
|
|
290
|
+
* - `domain_not_allowed`, `registration_closed` — the self-registration policy
|
|
291
|
+
* refused. Honest, because neither is about whether a person exists.
|
|
292
|
+
* - `idp_unavailable`, `exchange_failed`, `claims_incomplete`,
|
|
293
|
+
* `provider_misconfigured`, `provider_disabled` — the provider's or the
|
|
294
|
+
* developer's to fix, and the app can say so.
|
|
295
|
+
* - `invalid_request` — the start parameters did not hold up.
|
|
296
|
+
* - `quota_exceeded` — the org has as many app users as its `max_end_users`
|
|
297
|
+
* quota allows, so no account can be created for this identity. Named rather
|
|
298
|
+
* than folded into `no_access`, for `domain_not_allowed`'s reason: it is not
|
|
299
|
+
* about the person, the app can say what happened, and the remedy belongs to
|
|
300
|
+
* the developer rather than to whoever is trying to sign in. It is raised
|
|
301
|
+
* **only where an account would be created** — an identity that already has
|
|
302
|
+
* one signs in at the quota exactly as it does under it, because refusing a
|
|
303
|
+
* sign-in would turn a protection limit into an outage.
|
|
304
|
+
*/
|
|
305
|
+
declare const clientOidcErrorCode: z.ZodEnum<{
|
|
306
|
+
no_access: "no_access";
|
|
307
|
+
email_taken: "email_taken";
|
|
308
|
+
email_unverified: "email_unverified";
|
|
309
|
+
domain_not_allowed: "domain_not_allowed";
|
|
310
|
+
registration_closed: "registration_closed";
|
|
311
|
+
idp_unavailable: "idp_unavailable";
|
|
312
|
+
exchange_failed: "exchange_failed";
|
|
313
|
+
claims_incomplete: "claims_incomplete";
|
|
314
|
+
provider_misconfigured: "provider_misconfigured";
|
|
315
|
+
provider_disabled: "provider_disabled";
|
|
316
|
+
invalid_request: "invalid_request";
|
|
317
|
+
quota_exceeded: "quota_exceeded";
|
|
318
|
+
}>;
|
|
319
|
+
type ClientOidcErrorCode = z.infer<typeof clientOidcErrorCode>;
|
|
320
|
+
/**
|
|
321
|
+
* **A pending MCP authorization, as the app's own consent screen reads it**
|
|
322
|
+
* (D7). Fleetless renders no page here either: `authorize` redirects to the
|
|
323
|
+
* app's `mcp_login_url` with an interaction id, the app authenticates the user
|
|
324
|
+
* with its normal UI, shows this, and approves or denies through the API.
|
|
277
325
|
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
326
|
+
* `client_name_verified` is `z.literal(false)`, and that is the whole point of
|
|
327
|
+
* the field. The name comes from an **unauthenticated** dynamic registration —
|
|
328
|
+
* the client typed it about itself, nobody checked it — so a consent screen
|
|
329
|
+
* that rendered it as though it were an identity would be teaching people to
|
|
330
|
+
* trust a string an attacker chooses. A literal rather than a boolean because
|
|
331
|
+
* there is no verified case to distinguish: an app that reads this field at all
|
|
332
|
+
* has to handle the untrusted one, and a `true` branch would be dead code
|
|
333
|
+
* pretending to be a safeguard.
|
|
334
|
+
*/
|
|
335
|
+
declare const clientMcpInteraction: z.ZodObject<{
|
|
336
|
+
id: z.ZodString;
|
|
337
|
+
app_id: z.ZodUUID;
|
|
338
|
+
client_name: z.ZodNullable<z.ZodString>;
|
|
339
|
+
client_name_verified: z.ZodLiteral<false>;
|
|
340
|
+
scopes: z.ZodArray<z.ZodString>;
|
|
341
|
+
already_granted: z.ZodBoolean;
|
|
342
|
+
expires_at: z.ZodISODateTime;
|
|
343
|
+
}, z.core.$strip>;
|
|
344
|
+
type ClientMcpInteraction = z.infer<typeof clientMcpInteraction>;
|
|
345
|
+
/**
|
|
346
|
+
* **One standing MCP consent, as both withdrawal doors list it.**
|
|
281
347
|
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* them apart makes the choice for everyone.
|
|
348
|
+
* A grant is what lets a later authorization skip the app's consent screen:
|
|
349
|
+
* `clientMcpInteraction.already_granted` is a read of exactly this row. It is
|
|
350
|
+
* written when a person approves and it is removed by neither the client's
|
|
351
|
+
* registration lapsing nor its access token expiring — so without a door it
|
|
352
|
+
* was a decision a person could make once and never unmake.
|
|
288
353
|
*
|
|
289
|
-
* **
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* sweep shows the mechanism plainly: `sdk d065447` is literally titled *"logout
|
|
294
|
-
* says three separable things"* — correct when written, never carried forward
|
|
295
|
-
* when `hint_unavailable` arrived in `b417d2a`.
|
|
354
|
+
* **Standing only.** A withdrawn grant is stamped rather than deleted, so the
|
|
355
|
+
* store still holds it; neither listing returns one. The question both doors
|
|
356
|
+
* ask is *what is connected right now*, and a row that answered "connected,
|
|
357
|
+
* but no" would be a state every caller has to filter for itself.
|
|
296
358
|
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
359
|
+
* `client_name_verified` is `z.literal(false)` for the reason
|
|
360
|
+
* `clientMcpInteraction` gives at length: the name comes from an
|
|
361
|
+
* unauthenticated dynamic registration, the client chose it about itself, and
|
|
362
|
+
* a list that rendered it as an identity would be teaching people to trust a
|
|
363
|
+
* string an attacker picked. Here it matters more than on the consent screen,
|
|
364
|
+
* not less — a "connected apps" list is read long after the moment of
|
|
365
|
+
* approval, when nobody remembers what they clicked.
|
|
301
366
|
*/
|
|
302
|
-
declare const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
status: z.ZodLiteral<"not_federated">;
|
|
308
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
309
|
-
status: z.ZodLiteral<"unsupported_by_idp">;
|
|
310
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
311
|
-
status: z.ZodLiteral<"hint_unavailable">;
|
|
312
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
313
|
-
status: z.ZodLiteral<"session_unknown">;
|
|
314
|
-
}, z.core.$strip>], "status">;
|
|
367
|
+
declare const mcpConsentGrant: z.ZodObject<{
|
|
368
|
+
client_id: z.ZodString;
|
|
369
|
+
client_name: z.ZodNullable<z.ZodString>;
|
|
370
|
+
client_name_verified: z.ZodLiteral<false>;
|
|
371
|
+
granted_at: z.ZodISODateTime;
|
|
315
372
|
}, z.core.$strip>;
|
|
316
|
-
type
|
|
373
|
+
type McpConsentGrant = z.infer<typeof mcpConsentGrant>;
|
|
317
374
|
/**
|
|
318
375
|
* Who the caller turned out to be. Returned by the "who am I" endpoint and by
|
|
319
376
|
* the realtime `auth_ok` frame, so a client can render a session without
|
|
320
377
|
* decoding a token itself — decoding a JWT in the client is how apps end up
|
|
321
378
|
* trusting claims nobody verified.
|
|
322
379
|
*
|
|
323
|
-
* **Three kinds of caller reach the client API
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
* tab) subscribe on `/realtime` as one. A developer is **org-scoped, not
|
|
380
|
+
* **Three kinds of caller reach the client API.** Besides app users and server
|
|
381
|
+
* keys, a **developer** does: the console's playground runs over the real
|
|
382
|
+
* client API and appears in the audit as the developer, and the console's own
|
|
383
|
+
* live views subscribe on `/realtime` as one. A developer is **org-scoped, not
|
|
328
384
|
* app-scoped** — they own the configuration of every robot in their org — so
|
|
329
385
|
* `app_id` and `role_id` are null for them, and roles do not filter what they
|
|
330
|
-
* see. `kind` states this explicitly rather than leaving it to be inferred
|
|
331
|
-
*
|
|
386
|
+
* see. `kind` states this explicitly rather than leaving it to be inferred from
|
|
387
|
+
* which id happens to be set.
|
|
332
388
|
*
|
|
333
|
-
* **`
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
* audit as "Admin A as User B / as role X", and a client can render the "you
|
|
340
|
-
* are acting as …" banner without decoding the token.
|
|
389
|
+
* **`end_user_id` became `app_user_id`, and that is a rename with a meaning.**
|
|
390
|
+
* The old subject was a member of the org's one pool, reachable through an
|
|
391
|
+
* assignment; the new one is a row that belongs to exactly one app. Renaming
|
|
392
|
+
* rather than keeping the key is deliberate: a consumer reading `.end_user_id`
|
|
393
|
+
* would have typechecked and meant something subtly different, which is the
|
|
394
|
+
* quietest way for a cut like this to go wrong.
|
|
341
395
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* exactly when the effective identity was impersonated — that pairing is the
|
|
348
|
-
* cloud's, minted at the authorize step. Only the admin's **id** rides here:
|
|
349
|
-
* the label is resolved by whoever renders it, not carried as a second
|
|
350
|
-
* unverified name on the wire.
|
|
396
|
+
* **`act` is gone.** It named the org admin behind an impersonation (the RFC
|
|
397
|
+
* 8693 pattern). Impersonation is deleted with no successor (D1), so a field
|
|
398
|
+
* that could still arrive would describe a delegation nothing can mint — and a
|
|
399
|
+
* client rendering "you are acting as …" from it would be showing a state the
|
|
400
|
+
* platform cannot enter.
|
|
351
401
|
*/
|
|
352
402
|
declare const clientIdentity: z.ZodObject<{
|
|
353
403
|
kind: z.ZodEnum<{
|
|
354
|
-
server_key: "server_key";
|
|
355
404
|
developer: "developer";
|
|
356
|
-
|
|
405
|
+
server_key: "server_key";
|
|
406
|
+
app_user: "app_user";
|
|
357
407
|
}>;
|
|
358
408
|
developer_id: z.ZodNullable<z.ZodUUID>;
|
|
359
|
-
|
|
409
|
+
app_user_id: z.ZodNullable<z.ZodUUID>;
|
|
360
410
|
server_key_id: z.ZodNullable<z.ZodUUID>;
|
|
361
411
|
app_id: z.ZodNullable<z.ZodUUID>;
|
|
362
412
|
role_id: z.ZodNullable<z.ZodUUID>;
|
|
363
413
|
email: z.ZodNullable<z.ZodEmail>;
|
|
364
|
-
act: z.ZodOptional<z.ZodObject<{
|
|
365
|
-
admin_user_id: z.ZodUUID;
|
|
366
|
-
}, z.core.$strict>>;
|
|
367
414
|
}, z.core.$strip>;
|
|
368
415
|
type ClientIdentity = z.infer<typeof clientIdentity>;
|
|
369
416
|
|
|
@@ -423,9 +470,9 @@ declare const assetSyncStatus: z.ZodObject<{
|
|
|
423
470
|
sync_id: z.ZodUUID;
|
|
424
471
|
robot_id: z.ZodUUID;
|
|
425
472
|
state: z.ZodEnum<{
|
|
473
|
+
failed: "failed";
|
|
426
474
|
running: "running";
|
|
427
475
|
succeeded: "succeeded";
|
|
428
|
-
failed: "failed";
|
|
429
476
|
}>;
|
|
430
477
|
done: z.ZodNumber;
|
|
431
478
|
total: z.ZodNumber;
|
|
@@ -467,9 +514,9 @@ declare const assetListResponse: z.ZodObject<{
|
|
|
467
514
|
sync_id: z.ZodUUID;
|
|
468
515
|
robot_id: z.ZodUUID;
|
|
469
516
|
state: z.ZodEnum<{
|
|
517
|
+
failed: "failed";
|
|
470
518
|
running: "running";
|
|
471
519
|
succeeded: "succeeded";
|
|
472
|
-
failed: "failed";
|
|
473
520
|
}>;
|
|
474
521
|
done: z.ZodNumber;
|
|
475
522
|
total: z.ZodNumber;
|
|
@@ -543,79 +590,9 @@ type ParameterInvalidDetails = z.infer<typeof parameterInvalidDetails>;
|
|
|
543
590
|
* list is the shared vocabulary, not a closed set, so a new refusal never
|
|
544
591
|
* needs a contracts release before it can be reported honestly.
|
|
545
592
|
*/
|
|
546
|
-
declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "
|
|
593
|
+
declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "job_lost", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "asset_too_large", "dynamic_registration_disabled", "client_limit_reached", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "target_state_conflict", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml", "registration_closed", "domain_not_allowed", "email_unverified", "origin_not_allowed", "template_invalid", "provider_disabled", "provider_misconfigured", "invalid_redirect_uri", "interaction_expired"];
|
|
547
594
|
type ErrorCode = (typeof ERROR_CODES)[number];
|
|
548
595
|
|
|
549
|
-
/**
|
|
550
|
-
* **What an end user sees about their own consents, and why it is not
|
|
551
|
-
* `consentGrant` (W9c, DEF-099).**
|
|
552
|
-
*
|
|
553
|
-
* `consentGrant` is the *record* — four ids and a scope string. A person
|
|
554
|
-
* deciding whether to revoke something needs to recognise it, and an id is
|
|
555
|
-
* not recognisable. So this carries the names that were on the screen when
|
|
556
|
-
* they consented: the client's, the app's, and the role's.
|
|
557
|
-
*
|
|
558
|
-
* `client_id` stays, because it is what a revocation addresses — the names
|
|
559
|
-
* are for reading, the id is for acting.
|
|
560
|
-
*/
|
|
561
|
-
declare const consentGrantSummary: z.ZodObject<{
|
|
562
|
-
client_id: z.ZodString;
|
|
563
|
-
client_name: z.ZodString;
|
|
564
|
-
app_id: z.ZodUUID;
|
|
565
|
-
app_name: z.ZodString;
|
|
566
|
-
role_id: z.ZodUUID;
|
|
567
|
-
role_name: z.ZodString;
|
|
568
|
-
scope: z.ZodString;
|
|
569
|
-
granted_at: z.ZodISODateTime;
|
|
570
|
-
}, z.core.$strip>;
|
|
571
|
-
type ConsentGrantSummary = z.infer<typeof consentGrantSummary>;
|
|
572
|
-
/**
|
|
573
|
-
* **Revoking one grant must end the access it authorised, not merely forget
|
|
574
|
-
* that it happened (W9c, DEF-099).**
|
|
575
|
-
*
|
|
576
|
-
* The register row is about a user who wants a specific client to stop, and
|
|
577
|
-
* the failure mode to avoid is a revocation that deletes the consent row
|
|
578
|
-
* while every already-issued token keeps working until it expires. So the
|
|
579
|
-
* response says what was actually ended, and a caller can tell *nothing
|
|
580
|
-
* matched* from *matched and ended*.
|
|
581
|
-
*
|
|
582
|
-
* `tokens_revoked` is the count of refresh **families** ended. It is not a
|
|
583
|
-
* count of access tokens, and deliberately so: an access token is stateless
|
|
584
|
-
* and short-lived, and a number that claimed to have revoked one would be the
|
|
585
|
-
* kind of sentence this project keeps having to take back.
|
|
586
|
-
*
|
|
587
|
-
* **What actually happens to the access token is stronger than this comment
|
|
588
|
-
* first claimed, and narrower than its correction (W9c, 2026-08-19).** The
|
|
589
|
-
* first version said *"let the access token expire"*. It does not. The
|
|
590
|
-
* correction then said *"the same access token dies"*, which is true and
|
|
591
|
-
* under-specified — Data-W9c read `resolveAnyToken` instead of copying the
|
|
592
|
-
* sentence and found what the check is actually bound to:
|
|
593
|
-
*
|
|
594
|
-
* `if (claims.client_id) { ...consent-grant lookup... }`
|
|
595
|
-
*
|
|
596
|
-
* So the immediate death is scoped to **`client_id`, not to a session and not
|
|
597
|
-
* to the end user.** Every currently-valid token issued through *this
|
|
598
|
-
* client's* OAuth flow for this end user dies at once — including a second
|
|
599
|
-
* tab holding a different token from the same client. A plain `auth.login()`
|
|
600
|
-
* session is untouched, because it carries no `client_id` for the check to
|
|
601
|
-
* read, and a token bound to a *different* client is untouched too.
|
|
602
|
-
*
|
|
603
|
-
* That distinction is the whole point of revoking one grant rather than
|
|
604
|
-
* logging somebody out: *"without touching any other client's access"* is
|
|
605
|
-
* what this route promises, and the check is what makes it true.
|
|
606
|
-
*
|
|
607
|
-
* That is a better outcome than the contract promised, and it is written down
|
|
608
|
-
* here for one reason: **a caller must not build on the weaker sentence.** If
|
|
609
|
-
* this platform ever moves to stateless verification without the revocation
|
|
610
|
-
* re-check, the access token would start living out its TTL again, and
|
|
611
|
-
* anything that quietly relied on immediate death would break silently.
|
|
612
|
-
*/
|
|
613
|
-
declare const consentRevokeResponse: z.ZodObject<{
|
|
614
|
-
revoked: z.ZodBoolean;
|
|
615
|
-
tokens_revoked: z.ZodNumber;
|
|
616
|
-
}, z.core.$strip>;
|
|
617
|
-
type ConsentRevokeResponse = z.infer<typeof consentRevokeResponse>;
|
|
618
|
-
|
|
619
596
|
/**
|
|
620
597
|
* Codes the SDK produces itself rather than relaying from the server. Kept
|
|
621
598
|
* out of `@fleetless/contracts`' `ERROR_CODES` deliberately — that list is
|
|
@@ -656,7 +633,12 @@ type ConsentRevokeResponse = z.infer<typeof consentRevokeResponse>;
|
|
|
656
633
|
* signature and is still passing an options object third; and a
|
|
657
634
|
* `concurrency` on `assets.prepareUrdfScene` that is not a positive
|
|
658
635
|
* integer, which would otherwise fetch nothing and return a scene that
|
|
659
|
-
* renders blank with no error to explain why.
|
|
636
|
+
* renders blank with no error to explain why. **A fourth since 3.0.0:**
|
|
637
|
+
* every `auth` method needing an app user's own session, called on a client
|
|
638
|
+
* built with a `serverKey` — `register`, `login`, `logout`, the password
|
|
639
|
+
* and invitation calls, both OIDC calls, the two MCP decisions and the two
|
|
640
|
+
* grant calls. Those threw a bare `Error` before, which a caller could only
|
|
641
|
+
* catch by message. All of them are refused
|
|
660
642
|
* before any request is sent — as a rejection, since every one of those
|
|
661
643
|
* methods is `async`. A client-side mistake to fix, not something a
|
|
662
644
|
* server response could ever produce, which is why this code belongs here
|
|
@@ -675,17 +657,6 @@ type ConsentRevokeResponse = z.infer<typeof consentRevokeResponse>;
|
|
|
675
657
|
* rather than left to surface as a confusing downstream failure from
|
|
676
658
|
* `URDFLoader.parse(undefined)` or similar — the caller's fix is "sync a
|
|
677
659
|
* URDF first", which this error can say directly.
|
|
678
|
-
* - `no_hosted_login_attempt`: `auth.completeHostedLogin()` was called with
|
|
679
|
-
* an empty `expectedState` — nothing was persisted for this attempt. A
|
|
680
|
-
* callback landing in a different tab or window than the one that called
|
|
681
|
-
* `beginHostedLogin`, a restored session, or storage cleared in between
|
|
682
|
-
* all produce exactly this, and none of them is an attack. Told apart
|
|
683
|
-
* from `state_mismatch` on purpose: the two diagnoses have different
|
|
684
|
-
* remedies ("check how you persisted the value" versus "this response
|
|
685
|
-
* belongs to a login you did not start"). It also closes a real gap —
|
|
686
|
-
* comparing two *empty* strings with `!==` is `false`, so without this
|
|
687
|
-
* check first, a caller with nothing persisted at all could reach
|
|
688
|
-
* `state_mismatch`'s comparison having contributed no defence whatsoever.
|
|
689
660
|
* - `aborted`: `assets.prepareUrdfScene()` was given an `AbortSignal` and it
|
|
690
661
|
* fired — either already-aborted before the call started, or mid-flight
|
|
691
662
|
* while a fetch was in progress. Normalized to this one code regardless of
|
|
@@ -698,18 +669,27 @@ type ConsentRevokeResponse = z.infer<typeof consentRevokeResponse>;
|
|
|
698
669
|
* created (`blob:` URLs) is revoked before this throws — an aborted load
|
|
699
670
|
* must not leak what it fetched before the signal fired, the same
|
|
700
671
|
* guarantee a failed load already had.
|
|
701
|
-
* - `state_mismatch`: `auth.
|
|
702
|
-
* that does not match the `expectedState` its own `
|
|
703
|
-
* returned for this attempt
|
|
672
|
+
* - `state_mismatch`: `auth.completeOidcLogin()` was called with a `state`
|
|
673
|
+
* that does not match the `expectedState` its own `beginOidcLogin()`
|
|
674
|
+
* returned for this attempt — or with no `state` at all (`beginOidcLogin`
|
|
704
675
|
* always sets one, so a callback carrying none does not look like a reply
|
|
705
|
-
* to a flow this client started)
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
676
|
+
* to a flow this client started), or with an **empty `expectedState`**,
|
|
677
|
+
* meaning nothing was persisted for this attempt at all. That last case is
|
|
678
|
+
* folded in rather than given its own code: two empty strings compare
|
|
679
|
+
* equal, so it has to be checked explicitly or the comparison defends
|
|
680
|
+
* nothing for exactly the callers most likely to hit it — but a caller
|
|
681
|
+
* branching on the code has the same next step either way, which is to
|
|
682
|
+
* start the sign-in again. Which of the two happened is in the message,
|
|
683
|
+
* because the *developer's* remedies do differ ("check how your app
|
|
684
|
+
* persisted the value" versus "this response belongs to a sign-in you did
|
|
685
|
+
* not start"). Thrown before `/api/client/oidc/exchange` is ever called:
|
|
686
|
+
* RFC 6749 section 10.12's whole point is that a caller must not complete
|
|
687
|
+
* an authorization response it did not itself request, so this check
|
|
688
|
+
* happens client-side, first, rather than being left to the server to
|
|
689
|
+
* catch — by which point a one-time code would already have been spent for
|
|
690
|
+
* a flow this client never started.
|
|
711
691
|
*/
|
|
712
|
-
declare const SDK_ERROR_CODES: readonly ["no_session", "no_websocket", "unparseable_error", "command_timeout", "command_outcome_unknown", "unexpected_response", "invalid_option", "untrusted_absolute_url", "state_mismatch", "
|
|
692
|
+
declare const SDK_ERROR_CODES: readonly ["no_session", "no_websocket", "unparseable_error", "command_timeout", "command_outcome_unknown", "unexpected_response", "invalid_option", "untrusted_absolute_url", "state_mismatch", "no_urdf_synced", "aborted"];
|
|
713
693
|
/**
|
|
714
694
|
* The union of `SDK_ERROR_CODES` — the SDK's own client-side error
|
|
715
695
|
* vocabulary. A `FleetlessError` whose `code` is one of these was raised by
|
|
@@ -1270,389 +1250,351 @@ declare class InMemoryTokenStore implements TokenStore {
|
|
|
1270
1250
|
}
|
|
1271
1251
|
|
|
1272
1252
|
/**
|
|
1273
|
-
* `
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
1253
|
+
* `register()`'s input. The app identifier is **not** here: the client already
|
|
1254
|
+
* holds one (`createClient({ appIdentifier })`) and sends it itself, so there
|
|
1255
|
+
* is no way for a caller to register somebody into a different app than the
|
|
1256
|
+
* one this client speaks for.
|
|
1276
1257
|
*/
|
|
1277
|
-
interface
|
|
1258
|
+
interface RegisterOptions {
|
|
1259
|
+
/** The address the verification mail goes to. Nothing works until that link is spent. */
|
|
1260
|
+
email: string;
|
|
1261
|
+
/** At least 12 characters — `clientRegisterRequest` refuses less with a `validation_error`. */
|
|
1262
|
+
password: string;
|
|
1278
1263
|
/**
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1264
|
+
* What the app should call this person. Optional, and **omitted from the
|
|
1265
|
+
* request entirely** when you do not pass it — `clientRegisterRequest` is a
|
|
1266
|
+
* strict schema, so a key carrying `undefined` would be a `422` rather than a
|
|
1267
|
+
* default.
|
|
1283
1268
|
*/
|
|
1284
|
-
|
|
1269
|
+
displayName?: string;
|
|
1270
|
+
}
|
|
1271
|
+
/** `acceptInvitation()`'s input — the token out of the mailed link, plus the password the account gets. */
|
|
1272
|
+
interface AcceptInvitationOptions {
|
|
1273
|
+
/** The `token` from the invitation link the developer's app was linked to. */
|
|
1274
|
+
token: string;
|
|
1275
|
+
/** At least 12 characters. The invitation fixes the role; this call fixes the credential. */
|
|
1276
|
+
password: string;
|
|
1277
|
+
/** Optional, and omitted from the request entirely when absent — same strict-schema reason as `RegisterOptions.displayName`. */
|
|
1278
|
+
displayName?: string;
|
|
1279
|
+
}
|
|
1280
|
+
/** One sign-in button on the app's own login screen, as `listProviders()` lists it. */
|
|
1281
|
+
interface ProviderButton {
|
|
1282
|
+
/** What `beginOidcLogin` addresses this provider by. */
|
|
1283
|
+
slug: string;
|
|
1284
|
+
/** The label the developer configured, to be rendered on the button. */
|
|
1285
|
+
name: string;
|
|
1286
|
+
}
|
|
1287
|
+
/** `beginOidcLogin()`'s input: which provider, and where Fleetless should send the browser back to. */
|
|
1288
|
+
interface BeginOidcLoginOptions {
|
|
1289
|
+
/** A `slug` from `listProviders()`. An unknown one is a `404` when the browser reaches the start route, not here. */
|
|
1290
|
+
slug: string;
|
|
1285
1291
|
/**
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1288
|
-
*
|
|
1292
|
+
* Where the browser comes back to with `?code=…&state=…` (or `?error=…`).
|
|
1293
|
+
* Checked against the app's **allowed origins** server-side, by origin — so
|
|
1294
|
+
* the path is yours to choose and the origin is not.
|
|
1295
|
+
*
|
|
1296
|
+
* **Two shapes are refused outright, before the origin is compared**, and
|
|
1297
|
+
* neither refusal mentions them: a URL carrying a **fragment**
|
|
1298
|
+
* (`https://app.example.com/#/auth/callback`) and one carrying **userinfo**
|
|
1299
|
+
* (`https://someone@app.example.com/cb`). Both come back as a flat
|
|
1300
|
+
* `400 invalid_redirect_uri` reading "The redirect_uri is not an origin this
|
|
1301
|
+
* app answers for", which sends people to re-check an allow-list that was
|
|
1302
|
+
* never the problem.
|
|
1303
|
+
*
|
|
1304
|
+
* The fragment case is the one that costs time, because hash routing is the
|
|
1305
|
+
* default for a static-hosted SPA with no server rewrite. Give the callback
|
|
1306
|
+
* a real path (`/auth/callback`) and let your router pick the hash route up
|
|
1307
|
+
* from there; a fragment is a browser-side construct the redirect could not
|
|
1308
|
+
* carry a code in anyway.
|
|
1289
1309
|
*/
|
|
1290
1310
|
redirectUri: string;
|
|
1311
|
+
}
|
|
1312
|
+
/** What `beginOidcLogin()` returns. Nothing here has touched the network. */
|
|
1313
|
+
interface OidcLoginRequest {
|
|
1291
1314
|
/**
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
/**
|
|
1297
|
-
* RFC 8707 audience binding: the resource this
|
|
1298
|
-
* session's token should be usable against. **Omit it for an ordinary app
|
|
1299
|
-
* login.** A token with no `resource` carries no `aud` and works
|
|
1300
|
-
* unrestricted against this app's own REST surface exactly as it always
|
|
1301
|
-
* has; that path is unaffected by this field's existence. Only a caller
|
|
1302
|
-
* that is itself going to present the token to an audience-checking
|
|
1303
|
-
* resource needs to ask for one.
|
|
1304
|
-
*
|
|
1305
|
-
* **`/oauth/authorize` accepts exactly two shapes** — anything else is
|
|
1306
|
-
* refused with `invalid_target` on the redirect back, before a code is
|
|
1307
|
-
* ever issued (the cloud's own known-resource check):
|
|
1308
|
-
*
|
|
1309
|
-
* - `<base>/mcp-stub/resource` — the global OAuth resource stub, matched
|
|
1310
|
-
* byte-for-byte.
|
|
1311
|
-
* - `<base>/mcp-stub/resource/<app_identifier>` — the per-app stub, and
|
|
1312
|
-
* only for **the calling client's own app**. This is an existence check
|
|
1313
|
-
* plus an ownership check, not a shape check: the app must exist *and*
|
|
1314
|
-
* be the one this `clientId` is registered to, so a client on app A can
|
|
1315
|
-
* never be minted a token whose `aud` names app B.
|
|
1316
|
-
*
|
|
1317
|
-
* Both are served and validated by the platform's resource stub, whose
|
|
1318
|
-
* validator refuses a token with **no** `aud` exactly as hard as one with
|
|
1319
|
-
* the wrong `aud` —
|
|
1320
|
-
* "unscoped" must never read as "for me" — comparing by equality, never
|
|
1321
|
-
* by prefix.
|
|
1322
|
-
*
|
|
1323
|
-
* **`<base>/mcp/<app_identifier>` is not a resource any more.** There is
|
|
1324
|
-
* no per-app MCP endpoint: the cloud registers one central, non-parametric
|
|
1325
|
-
* `POST /mcp` (the contracts' `MCP_ENDPOINT_PATH`), and the
|
|
1326
|
-
* `/oauth/authorize` branch that used to
|
|
1327
|
-
* accept a `/mcp/<app>` resource was deleted along with the app-level
|
|
1328
|
-
* `mcp_enabled` flag. Asking for one now yields `invalid_target` for
|
|
1329
|
-
* every app, **including your own**. The central MCP endpoint has its own
|
|
1330
|
-
* OAuth flow, which this SDK's hosted login does not drive —
|
|
1331
|
-
* `beginHostedLogin` always targets
|
|
1332
|
-
* `OAUTH_PATHS.authorize`.
|
|
1333
|
-
*
|
|
1334
|
-
* Whatever you name here is re-checked at the token exchange: it must
|
|
1335
|
-
* match what the code was authorized for, and it must still name a
|
|
1336
|
-
* resource this client may be issued a token for.
|
|
1315
|
+
* Send the end user's browser here. **This SDK does not navigate** — it has
|
|
1316
|
+
* no opinion about whether that is a full page load, a popup or a native web
|
|
1317
|
+
* view, the same boundary `cameras.live` draws by handing back a URL and a
|
|
1318
|
+
* token and stopping there.
|
|
1337
1319
|
*/
|
|
1338
|
-
resource?: string;
|
|
1339
|
-
}
|
|
1340
|
-
/** What `beginHostedLogin()` returns — nothing here has touched the network yet. */
|
|
1341
|
-
interface HostedLoginRequest {
|
|
1342
|
-
/** Send the end user's browser here to start the hosted login page. */
|
|
1343
1320
|
url: string;
|
|
1344
1321
|
/**
|
|
1345
|
-
* Persist this
|
|
1346
|
-
* both back into `
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
* use case while looking like it worked for anything that never actually
|
|
1353
|
-
* navigates away. `sessionStorage`, a signed cookie, or a plain variable
|
|
1354
|
-
* (a popup flow that never truly navigates) are all valid — that choice is
|
|
1355
|
-
* the caller's.
|
|
1322
|
+
* Persist this next to `codeVerifier` **before navigating away**, and pass
|
|
1323
|
+
* both back into `completeOidcLogin`. The SDK does not persist them for you:
|
|
1324
|
+
* the redirect back is a fresh page load for a browser app, and nothing kept
|
|
1325
|
+
* in this SDK's memory survives it. `sessionStorage`, a signed cookie or a
|
|
1326
|
+
* plain variable (a popup flow that never truly navigates) are all valid —
|
|
1327
|
+
* that choice is the caller's, and an in-memory default here would look like
|
|
1328
|
+
* it worked right up until the first real redirect.
|
|
1356
1329
|
*/
|
|
1357
1330
|
state: string;
|
|
1358
1331
|
/**
|
|
1359
|
-
* The PKCE code verifier for this attempt
|
|
1360
|
-
*
|
|
1361
|
-
*
|
|
1332
|
+
* The PKCE code verifier for this attempt — persist it exactly as `state`.
|
|
1333
|
+
* **The app runs its own PKCE against Fleetless**, a second exchange
|
|
1334
|
+
* independent of the one Fleetless runs against the identity provider, which
|
|
1335
|
+
* is what makes the one-time code in the redirect worth nothing to whoever
|
|
1336
|
+
* else reads that URL.
|
|
1362
1337
|
*/
|
|
1363
1338
|
codeVerifier: string;
|
|
1364
1339
|
}
|
|
1365
|
-
/** `
|
|
1366
|
-
interface
|
|
1367
|
-
/** The `code` query parameter from the redirect back to `redirectUri`. */
|
|
1340
|
+
/** `completeOidcLogin()`'s input — the redirect back, plus what `beginOidcLogin` returned for this same attempt. */
|
|
1341
|
+
interface CompleteOidcLoginOptions {
|
|
1342
|
+
/** The `code` query parameter from the redirect back to `redirectUri`. It lives 60 seconds. */
|
|
1368
1343
|
code: string;
|
|
1369
1344
|
/** The `state` query parameter from that same redirect. */
|
|
1370
1345
|
state: string;
|
|
1371
|
-
/**
|
|
1372
|
-
* The `state` this attempt's `beginHostedLogin` returned. Checked against
|
|
1373
|
-
* `state` above **before any network call** — the whole point of RFC
|
|
1374
|
-
* 6749 section 10.12 is that a caller must not complete an authorization
|
|
1375
|
-
* response it did not itself request.
|
|
1376
|
-
*/
|
|
1346
|
+
/** The `state` this attempt's `beginOidcLogin` returned. Compared **before any network call**. */
|
|
1377
1347
|
expectedState: string;
|
|
1378
|
-
/** The `codeVerifier` this attempt's `
|
|
1348
|
+
/** The `codeVerifier` this attempt's `beginOidcLogin` returned. */
|
|
1379
1349
|
codeVerifier: string;
|
|
1380
|
-
/** Must be the exact same `client_id` passed to `beginHostedLogin`. */
|
|
1381
|
-
clientId: string;
|
|
1382
|
-
/** Must be the exact same string passed to `beginHostedLogin`. */
|
|
1383
|
-
redirectUri: string;
|
|
1384
|
-
/**
|
|
1385
|
-
* Must be the exact same string passed to `beginHostedLogin`, if any.
|
|
1386
|
-
* Resending it here is not what binds the audience — the server already
|
|
1387
|
-
* bound `resource` to the authorization code at `/oauth/authorize` and
|
|
1388
|
-
* mints `aud` from that stored value regardless of what this call sends —
|
|
1389
|
-
* but RFC 8707 section 2 expects a client to name the resource at both steps,
|
|
1390
|
-
* and the cloud rejects a *mismatched* resend outright (`invalid_target`).
|
|
1391
|
-
* Omit it here exactly when it was omitted at `beginHostedLogin`.
|
|
1392
|
-
*/
|
|
1393
|
-
resource?: string;
|
|
1394
1350
|
}
|
|
1395
1351
|
/**
|
|
1396
|
-
* What `
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1399
|
-
* **`idp_logout` is `null` whenever the server never answered**, which
|
|
1400
|
-
* happens for two different reasons: there was no local session to ask
|
|
1401
|
-
* about, or the request itself failed. Either way there is nothing to
|
|
1402
|
-
* report — not even "not federated", because this client never learned that
|
|
1403
|
-
* either.
|
|
1404
|
-
*
|
|
1405
|
-
* `revoked` does not identify which of the two you got. It is `false` only
|
|
1406
|
-
* for the failed request; **a logout with no local session at all resolves
|
|
1407
|
-
* `{ revoked: true, idp_logout: null }`**, since nothing lingers
|
|
1408
|
-
* server-side and that counts as revoked. So `revoked: false` does imply
|
|
1409
|
-
* `idp_logout: null`, and the converse does not hold. Do not read the two
|
|
1410
|
-
* fields as one bit.
|
|
1352
|
+
* What `approveMcpInteraction`/`denyMcpInteraction` resolve with: **where to
|
|
1353
|
+
* send the browser**, and nothing else.
|
|
1411
1354
|
*
|
|
1412
|
-
*
|
|
1413
|
-
* is
|
|
1414
|
-
*
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
1423
|
-
* session, and Fleetless has nothing to ask it with: the stored
|
|
1424
|
-
* `id_token_hint` could not be decrypted, or the session predates the fix
|
|
1425
|
-
* that started keeping one.
|
|
1426
|
-
* - `{ status: 'session_unknown' }` — the server did not find this session at
|
|
1427
|
-
* all (the token was unknown, already superseded, revoked, or expired), so
|
|
1428
|
-
* it can say **nothing** about an IdP.
|
|
1355
|
+
* A denial carries a redirect too, with `error=access_denied` on it — a client
|
|
1356
|
+
* that is refused has to learn so from its own callback rather than from a page
|
|
1357
|
+
* nobody sent it, so both outcomes end the same way for the app: navigate here.
|
|
1358
|
+
*/
|
|
1359
|
+
interface McpInteractionDecision {
|
|
1360
|
+
/** The absolute URL to navigate to. Wire field `redirect_to`. */
|
|
1361
|
+
redirectTo: string;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Who the caller is, reachable as `client.auth` — the whole client auth API of
|
|
1365
|
+
* spec §4, as JSON.
|
|
1429
1366
|
*
|
|
1430
|
-
*
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1433
|
-
*
|
|
1367
|
+
* **Fleetless serves an app user no page.** The developer's own UI owns every
|
|
1368
|
+
* screen: login, registration, verification, invitation acceptance, password
|
|
1369
|
+
* reset, the provider buttons and the MCP consent. These methods are what those
|
|
1370
|
+
* screens call. The hosted, app-branded login and consent pages this SDK used
|
|
1371
|
+
* to drive are gone, along with `beginHostedLogin`/`completeHostedLogin`.
|
|
1434
1372
|
*
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
1373
|
+
* **The enumeration discipline is the design's, and it shapes this surface.**
|
|
1374
|
+
* `register`, `resendVerification` and `requestPasswordReset` resolve for every
|
|
1375
|
+
* policy-allowed request whether or not the address exists, and `login` answers
|
|
1376
|
+
* the identical `invalid_credentials` for a wrong password, a blocked account
|
|
1377
|
+
* and an unverified one. So: *resolving does not mean an account exists*, and
|
|
1378
|
+
* the only honest refusals are the ones about policy rather than about a
|
|
1379
|
+
* person — `registration_closed`, `domain_not_allowed`, `quota_exceeded`.
|
|
1442
1380
|
*
|
|
1443
|
-
*
|
|
1444
|
-
*
|
|
1445
|
-
*
|
|
1446
|
-
*
|
|
1381
|
+
* **A client built with a `serverKey` refuses everything that needs an app
|
|
1382
|
+
* user's own session** with `invalid_option`, before any request. `me()`,
|
|
1383
|
+
* `listProviders()`, `mcpInteraction()` and `oidcErrorFromCallback()` still
|
|
1384
|
+
* work on one: the first is what a server key is *for*, the next two are public
|
|
1385
|
+
* reads the cloud answers without any credential at all, and the last touches
|
|
1386
|
+
* no network.
|
|
1447
1387
|
*/
|
|
1448
|
-
interface
|
|
1449
|
-
/**
|
|
1450
|
-
* Whether the server-side revoke actually happened. It reports the fate
|
|
1451
|
-
* of the HTTP call, not whether a session was found — `false` means the
|
|
1452
|
-
* refresh family may still be alive even though this client has
|
|
1453
|
-
* forgotten it.
|
|
1454
|
-
*/
|
|
1455
|
-
revoked: boolean;
|
|
1388
|
+
interface AuthApi {
|
|
1456
1389
|
/**
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
1460
|
-
*
|
|
1461
|
-
*
|
|
1462
|
-
*
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
* the
|
|
1469
|
-
*
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
* either one like `not_federated` and you report a session as fully ended
|
|
1475
|
-
* when it is not.
|
|
1390
|
+
* Self-registration. Writes the account as `pending_verification` and mails
|
|
1391
|
+
* the app's verification link; **the account cannot log in until that link is
|
|
1392
|
+
* spent** (`verifyEmail`).
|
|
1393
|
+
*
|
|
1394
|
+
* Resolves on the route's `202` — which the cloud answers for every
|
|
1395
|
+
* policy-allowed request, whether the address was new or already known. It is
|
|
1396
|
+
* not a claim that an account was created, and an app that renders it as one
|
|
1397
|
+
* ("welcome, Ada!") is showing a stranger the enumeration oracle this whole
|
|
1398
|
+
* family is built to avoid. Render "check your mail" instead.
|
|
1399
|
+
*
|
|
1400
|
+
* Throws a `FleetlessError` carrying the cloud's own code for a refusal, and
|
|
1401
|
+
* that is the distinction this method exists to preserve: `registration_closed`
|
|
1402
|
+
* (the app has self-registration off), `domain_not_allowed` (the address is
|
|
1403
|
+
* outside the app's allowed domains), `quota_exceeded` (the org has as many
|
|
1404
|
+
* app users as its quota allows) and `not_found` (no app carries this
|
|
1405
|
+
* client's `appIdentifier`) are all things the app can say out loud, because
|
|
1406
|
+
* none of them is about whether a person exists.
|
|
1476
1407
|
*/
|
|
1477
|
-
|
|
1478
|
-
}
|
|
1479
|
-
/**
|
|
1480
|
-
* Who the caller is, reachable as `client.auth`. A client built with a
|
|
1481
|
-
* `tokenStore` uses the full surface. One built with a `serverKey` already
|
|
1482
|
-
* has an identity and no user session, so **`me()` is the only method it
|
|
1483
|
-
* can call** — `login`, `beginHostedLogin`, `completeHostedLogin`,
|
|
1484
|
-
* `logout`, `changePassword` and `passwordResetUrl` all throw on one.
|
|
1485
|
-
*/
|
|
1486
|
-
interface AuthApi {
|
|
1487
|
-
/** Exchanges email + password, and the client's configured app identifier, for a session. */
|
|
1488
|
-
login(email: string, password: string): Promise<void>;
|
|
1408
|
+
register(input: RegisterOptions): Promise<void>;
|
|
1489
1409
|
/**
|
|
1490
|
-
*
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1493
|
-
*
|
|
1494
|
-
* `
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
* reaches `/oauth/authorize`.
|
|
1498
|
-
*
|
|
1499
|
-
* Async only because computing the S256 `code_challenge` needs
|
|
1500
|
-
* `crypto.subtle.digest`, which the Web Crypto API only ever offers as a
|
|
1501
|
-
* promise — there is no synchronous digest to call instead.
|
|
1410
|
+
* Spends a verification token and **stores the session it answers with**, so
|
|
1411
|
+
* the person is not asked to log in immediately after proving they can read
|
|
1412
|
+
* the mail.
|
|
1413
|
+
*
|
|
1414
|
+
* `token_spent` covers unknown, expired and already-used alike — one code,
|
|
1415
|
+
* because the remedy is one thing: ask for a fresh link with
|
|
1416
|
+
* `resendVerification`. An app rendering this refusal should offer that.
|
|
1502
1417
|
*/
|
|
1503
|
-
|
|
1418
|
+
verifyEmail(token: string): Promise<void>;
|
|
1419
|
+
/** Asks for the verification mail again. Resolves on `202` for every policy-allowed request, existing address or not — same reason as `register`. */
|
|
1420
|
+
resendVerification(email: string): Promise<void>;
|
|
1504
1421
|
/**
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1508
|
-
*
|
|
1509
|
-
*
|
|
1510
|
-
*
|
|
1511
|
-
* `expectedState` throws `no_hosted_login_attempt` — nothing was
|
|
1512
|
-
* persisted for this attempt (a different tab, a restored session,
|
|
1513
|
-
* cleared storage), not necessarily an attack. Only once `expectedState`
|
|
1514
|
-
* is actually present does a mismatch (or a missing `state` on the
|
|
1515
|
-
* callback itself) throw `state_mismatch`. The two are told apart on
|
|
1516
|
-
* purpose: they call for different remedies, and collapsing them would
|
|
1517
|
-
* tell a developer debugging an ordinary storage gap that their app is
|
|
1518
|
-
* under attack.
|
|
1519
|
-
*
|
|
1520
|
-
* Once past that check and the exchange completes, `me()`, `logout()`,
|
|
1521
|
-
* `changePassword()` and silent refresh all behave identically afterwards,
|
|
1522
|
-
* regardless of which flow the session started from. Both routes into a
|
|
1523
|
-
* session end at the same Fleetless token: not merely that the bytes
|
|
1524
|
-
* match, but that every existing code path treats the result the same
|
|
1525
|
-
* way.
|
|
1526
|
-
*
|
|
1527
|
-
* The wire response is the envelope of RFC 6749 section 5.1 (`token_type`, optional
|
|
1528
|
-
* `scope`), not `sessionTokens` — this method normalizes one into the
|
|
1529
|
-
* other before storing. **Refresh needs no separate handling**: the cloud
|
|
1530
|
-
* mints these tokens through the same session mechanism `/api/client/login`
|
|
1531
|
-
* uses (same `refresh_tokens` row, same rotation), so the existing silent
|
|
1532
|
-
* refresh (`/api/client/refresh`) already works for a hosted-login
|
|
1533
|
-
* session — nothing about the origin of a session is tracked or needs to
|
|
1534
|
-
* be.
|
|
1535
|
-
*
|
|
1536
|
-
* Throws with the OAuth error code as `.code` (e.g. `invalid_grant` for an
|
|
1537
|
-
* expired or already-used `code`) if the exchange itself fails — a
|
|
1538
|
-
* different vocabulary from every other method on this interface, because
|
|
1539
|
-
* `/oauth/token` answers in the shape of RFC 6749 section 5.2, not `apiError`.
|
|
1540
|
-
*
|
|
1541
|
-
* **Makes exactly one request to `/oauth/token` — never retried, no
|
|
1542
|
-
* timeout-and-resend, no internal concurrency of its own.** Stated
|
|
1543
|
-
* because the constraint that matters here is not this method's, it is
|
|
1544
|
-
* the caller's: **never call this a second time for the same `code`
|
|
1545
|
-
* while a first call is still in flight** (a plain "the first attempt
|
|
1546
|
-
* looked like it timed out, so retry" is exactly the shape this warns
|
|
1547
|
-
* against — it is not a defect in this method, since this method itself
|
|
1548
|
-
* has nothing that could ever cause that). The platform treats a second
|
|
1549
|
-
* presentation of an authorization code as theft and revokes the whole
|
|
1550
|
-
* token family it belongs to, deliberately, even though a plain
|
|
1551
|
-
* double-submission looks identical on the wire —
|
|
1552
|
-
* because the blast radius is bounded (only a caller already holding the
|
|
1553
|
-
* correct `code_verifier` and `client_id` can trigger it, so a merely
|
|
1554
|
-
* *sniffed* code cannot lock anyone out) and the alternative is a
|
|
1555
|
-
* narrower defence against a real theft.
|
|
1556
|
-
*
|
|
1557
|
-
* **What actually happens if two requests race, measured:**
|
|
1558
|
-
* one of the two receives `200` with a refresh token that the server has
|
|
1559
|
-
* already revoked. The access token in that same response keeps working
|
|
1560
|
-
* normally for the rest of its short TTL — nothing about the race is
|
|
1561
|
-
* visible yet. The failure surfaces at this session's **first silent
|
|
1562
|
-
* refresh**, as `token_revoked`, potentially many minutes after the race
|
|
1563
|
-
* that actually caused it and with nothing in that later error pointing
|
|
1564
|
-
* back to a retry that "worked". If your own framework, an HTTP client
|
|
1565
|
-
* wrapper, or a user's impatient double-click can cause this method to
|
|
1566
|
-
* be invoked twice concurrently for the same redirect, guard against
|
|
1567
|
-
* that at the call site — a simple in-flight flag or disabling the
|
|
1568
|
-
* triggering control is enough, since there is only ever one legitimate
|
|
1569
|
-
* exchange per authorization code.
|
|
1422
|
+
* Exchanges email + password, and the client's configured app identifier, for
|
|
1423
|
+
* a session.
|
|
1424
|
+
*
|
|
1425
|
+
* `invalid_credentials` is answered identically for a wrong password, a
|
|
1426
|
+
* blocked account and one still waiting to verify. Do not try to tell them
|
|
1427
|
+
* apart — there is nothing in the answer that does, deliberately.
|
|
1570
1428
|
*/
|
|
1571
|
-
|
|
1429
|
+
login(email: string, password: string): Promise<void>;
|
|
1572
1430
|
/**
|
|
1573
|
-
* Ends the session: revokes the whole refresh-token family server-side
|
|
1574
|
-
*
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1580
|
-
* family may still be alive server-side even though this client has
|
|
1581
|
-
* forgotten it; an app that cares (a kiosk, a shared workstation) can
|
|
1582
|
-
* warn the user or retry, one that doesn't can ignore it.
|
|
1431
|
+
* Ends the session: revokes the whole refresh-token family server-side (a
|
|
1432
|
+
* stolen refresh token stops working immediately), closes this client's live
|
|
1433
|
+
* realtime connection if it has one, and clears the local store.
|
|
1434
|
+
*
|
|
1435
|
+
* **Never rejects, and always clears the store**, even if the server call
|
|
1436
|
+
* fails: a user who presses "log out" must end up logged out locally
|
|
1437
|
+
* regardless of the network.
|
|
1583
1438
|
*
|
|
1584
1439
|
* **What this does not do:** invalidate the access token already issued.
|
|
1585
|
-
* Access-token checks are stateless (a signed JWT, verified without a
|
|
1586
|
-
*
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1589
|
-
*
|
|
1590
|
-
*
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1597
|
-
* two different things — see `LogoutResult.idp_logout`. This method does
|
|
1598
|
-
* not act on that information itself (no redirect, no fetch to the IdP);
|
|
1599
|
-
* it only reports what the server found, the same "this SDK stays thin"
|
|
1600
|
-
* boundary as everywhere else (`cameras.live` hands back a URL and a
|
|
1601
|
-
* token and stops there too).
|
|
1602
|
-
*
|
|
1603
|
-
* **`idp_logout` is `null` when there was no server answer to report** —
|
|
1604
|
-
* and that is *not* the same as `revoked === false`.
|
|
1605
|
-
*
|
|
1606
|
-
* Calling `logout()` with **no local session** returns
|
|
1607
|
-
* `{ revoked: true, idp_logout: null }`, so `null` is not the same as
|
|
1608
|
-
* `revoked === false` and the two fields are not one bit. TypeScript
|
|
1609
|
-
* catches a caller who forgets (the type is `| null`); a JavaScript caller
|
|
1610
|
-
* does not.
|
|
1611
|
-
*
|
|
1612
|
-
* So: **`null` means this client had nothing to send or the request never
|
|
1613
|
-
* answered.** It is not `not_federated`, which is a real finding about a
|
|
1614
|
-
* real session — conflating the two is the ambiguity this shape exists to
|
|
1615
|
-
* remove, and it is the reason to read both fields rather than one.
|
|
1440
|
+
* Access-token checks are stateless (a signed JWT, verified without a lookup),
|
|
1441
|
+
* so logout has nothing to flip on that token — only on the refresh family
|
|
1442
|
+
* behind it. A token stolen before logout keeps working on REST, and can
|
|
1443
|
+
* still open a *new* realtime connection, until it expires on its own, at
|
|
1444
|
+
* most 15 minutes. That is a deliberate boundary of the stateless-JWT design,
|
|
1445
|
+
* not a bug, but a kiosk or a shared workstation needs to know the number.
|
|
1446
|
+
*
|
|
1447
|
+
* **Nor does it end a session at the identity provider.** It used to report
|
|
1448
|
+
* what was left of one; that apparatus belonged to the hosted login, where
|
|
1449
|
+
* Fleetless owned the browser. The app owns it now, and an app that wants to
|
|
1450
|
+
* end a provider session redirects there itself — knowing its own provider,
|
|
1451
|
+
* which Fleetless never did better than it.
|
|
1616
1452
|
*/
|
|
1617
|
-
logout(): Promise<
|
|
1618
|
-
/** Who the caller turned out to be, without decoding a token client-side. */
|
|
1453
|
+
logout(): Promise<void>;
|
|
1454
|
+
/** Who the caller turned out to be, without decoding a token client-side — which is how apps end up trusting claims nobody verified. */
|
|
1619
1455
|
me(): Promise<ClientIdentity>;
|
|
1620
1456
|
/**
|
|
1621
|
-
* Changes the current
|
|
1622
|
-
*
|
|
1623
|
-
* `currentPassword` is required
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1634
|
-
* caller holding tokens the server has already revoked, working only
|
|
1635
|
-
* until the access token expires and then silently logged out —
|
|
1636
|
-
* indistinguishable from the change having failed, which is the one
|
|
1637
|
-
* outcome this route exists to prevent. A user with other tabs or
|
|
1638
|
-
* devices logged in will see *those* signed out the moment this
|
|
1639
|
-
* resolves; if your app does not already make that consequence visible
|
|
1640
|
-
* before they confirm, they will find out from a support ticket instead
|
|
1641
|
-
* of from you.
|
|
1457
|
+
* Changes the current app user's password.
|
|
1458
|
+
*
|
|
1459
|
+
* `currentPassword` is required even though the session already proves
|
|
1460
|
+
* identity — it is what stops a stolen *session* from becoming a stolen
|
|
1461
|
+
* *account*.
|
|
1462
|
+
*
|
|
1463
|
+
* **Every other session of this identity is revoked, and this call's own
|
|
1464
|
+
* session is re-issued rather than spared.** The request carries nothing
|
|
1465
|
+
* identifying the caller's own refresh family, so the server revokes all of
|
|
1466
|
+
* them and hands back a fresh pair, which this method stores exactly like
|
|
1467
|
+
* `login`. A user with other tabs or devices signed in will see those signed
|
|
1468
|
+
* out the moment this resolves; if your app does not make that consequence
|
|
1469
|
+
* visible before they confirm, they will find out from a support ticket.
|
|
1642
1470
|
*/
|
|
1643
1471
|
changePassword(currentPassword: string, newPassword: string): Promise<void>;
|
|
1472
|
+
/** Asks for a reset link. Resolves on `202` for a known and an unknown address alike — the answer says nothing about which it was. */
|
|
1473
|
+
requestPasswordReset(email: string): Promise<void>;
|
|
1474
|
+
/**
|
|
1475
|
+
* Spends a reset token, sets the new password and **stores the session it
|
|
1476
|
+
* answers with**. Every refresh family of that user is revoked first — a
|
|
1477
|
+
* forgotten password is one of the two states where somebody else may be
|
|
1478
|
+
* holding a session.
|
|
1479
|
+
*/
|
|
1480
|
+
confirmPasswordReset(token: string, newPassword: string): Promise<void>;
|
|
1481
|
+
/**
|
|
1482
|
+
* Accepts an app invitation: creates the account (or activates one invited
|
|
1483
|
+
* before it existed) with the role the invitation fixed, and **stores the
|
|
1484
|
+
* session**.
|
|
1485
|
+
*
|
|
1486
|
+
* An invitation always bypasses the app's domain whitelist — a developer
|
|
1487
|
+
* inviting somebody by hand has already made the decision the whitelist
|
|
1488
|
+
* automates.
|
|
1489
|
+
*/
|
|
1490
|
+
acceptInvitation(input: AcceptInvitationOptions): Promise<void>;
|
|
1491
|
+
/**
|
|
1492
|
+
* The app's **enabled** sign-in providers, for drawing the buttons on your
|
|
1493
|
+
* own login screen. A disabled provider is not a button that refuses; it is a
|
|
1494
|
+
* button that is not there.
|
|
1495
|
+
*
|
|
1496
|
+
* Public and unauthenticated, and it carries nothing but `slug` and `name` on
|
|
1497
|
+
* purpose: the issuer, the client id, the scopes and the linking policy are
|
|
1498
|
+
* management-side facts that would tell a stranger how the app's federation
|
|
1499
|
+
* is configured.
|
|
1500
|
+
*/
|
|
1501
|
+
listProviders(): Promise<ProviderButton[]>;
|
|
1502
|
+
/**
|
|
1503
|
+
* Builds the URL that starts a federated sign-in, with a fresh `state` and a
|
|
1504
|
+
* fresh PKCE verifier. **Makes no network call and does not navigate** —
|
|
1505
|
+
* persist `state` and `codeVerifier`, then send the browser to `url`.
|
|
1506
|
+
*
|
|
1507
|
+
* Nothing about the app, the provider or the redirect URI is validated here;
|
|
1508
|
+
* it is all checked when the browser actually reaches the route, in that
|
|
1509
|
+
* order, with the redirect target checked before the provider so that a
|
|
1510
|
+
* caller who got the target wrong learns nothing about which providers the
|
|
1511
|
+
* app has.
|
|
1512
|
+
*
|
|
1513
|
+
* Async only because the S256 `code_challenge` needs `crypto.subtle.digest`,
|
|
1514
|
+
* which the Web Crypto API only ever offers as a promise.
|
|
1515
|
+
*/
|
|
1516
|
+
beginOidcLogin(input: BeginOidcLoginOptions): Promise<OidcLoginRequest>;
|
|
1517
|
+
/**
|
|
1518
|
+
* Completes a federated sign-in: checks `state` against `expectedState`,
|
|
1519
|
+
* trades the one-time `code` for a session, and stores it.
|
|
1520
|
+
*
|
|
1521
|
+
* **The state check runs before any request is sent.** RFC 6749 §10.12's
|
|
1522
|
+
* whole point is that a client must not complete an authorization response it
|
|
1523
|
+
* did not itself request — a check made after the exchange would already have
|
|
1524
|
+
* spent a code for a flow this client never started. Both an outright
|
|
1525
|
+
* mismatch and an *empty* `expectedState` throw `state_mismatch`; the message
|
|
1526
|
+
* says which, because the remedies differ ("check how your app persisted the
|
|
1527
|
+
* value" versus "this response belongs to a sign-in you did not start") even
|
|
1528
|
+
* though the next step is the same either way — start the sign-in again.
|
|
1529
|
+
*
|
|
1530
|
+
* The code lives 60 seconds and is single-use. Unknown, expired, replayed and
|
|
1531
|
+
* "the account was blocked in between" all arrive as one `token_spent`,
|
|
1532
|
+
* because the app has nothing different to do about any of them.
|
|
1533
|
+
*/
|
|
1534
|
+
completeOidcLogin(input: CompleteOidcLoginOptions): Promise<void>;
|
|
1535
|
+
/**
|
|
1536
|
+
* Reads a **failed** federated sign-in off the redirect back, as a
|
|
1537
|
+
* `FleetlessError` you can branch on, or `null` when the callback carries no
|
|
1538
|
+
* `error` at all.
|
|
1539
|
+
*
|
|
1540
|
+
* Fleetless renders no page for these: the reason is carried to your own
|
|
1541
|
+
* `redirectUri` as `?error=<code>`, and this turns that string into the same
|
|
1542
|
+
* error type every other method throws. A code the contracts define (see
|
|
1543
|
+
* `ClientOidcErrorCode`) becomes that code verbatim; anything else becomes
|
|
1544
|
+
* `unexpected_response` with the raw value in the message, rather than being
|
|
1545
|
+
* passed through as a code neither side defines.
|
|
1546
|
+
*
|
|
1547
|
+
* Purely local — it parses a query string and asks nothing.
|
|
1548
|
+
*/
|
|
1549
|
+
oidcErrorFromCallback(params: URLSearchParams): FleetlessError | null;
|
|
1550
|
+
/**
|
|
1551
|
+
* Reads a pending MCP authorization by the interaction id the browser
|
|
1552
|
+
* arrived with, so the app can render its own consent screen.
|
|
1553
|
+
*
|
|
1554
|
+
* **`client_name` is a string the client typed about itself** during an
|
|
1555
|
+
* unauthenticated dynamic registration — nobody checked it, which is why
|
|
1556
|
+
* `client_name_verified` is the literal `false` rather than a boolean with a
|
|
1557
|
+
* `true` branch that could never happen. Do not render it as an identity.
|
|
1558
|
+
*
|
|
1559
|
+
* `interaction_expired` means exactly that: ten minutes ran out, or the id
|
|
1560
|
+
* was never real. Both answer the same way, so the screen to show is "that
|
|
1561
|
+
* took too long, start again" rather than an error.
|
|
1562
|
+
*
|
|
1563
|
+
* **Call this with the app user already signed in.** The route needs no
|
|
1564
|
+
* credential, but it reads one if present, and `already_granted` is `false`
|
|
1565
|
+
* for an anonymous read whatever the truth is — so a consent screen rendered
|
|
1566
|
+
* from an unauthenticated call asks a person to agree to something they
|
|
1567
|
+
* agreed to already. An **expired** token counts as anonymous to this route,
|
|
1568
|
+
* which answers `200` rather than refusing, so this method probes the
|
|
1569
|
+
* session's liveness first and refreshes if it can; a session that cannot be
|
|
1570
|
+
* refreshed is not an error here, it is genuinely anonymous.
|
|
1571
|
+
*/
|
|
1572
|
+
mcpInteraction(id: string): Promise<ClientMcpInteraction>;
|
|
1573
|
+
/** Approves a pending MCP authorization on behalf of the signed-in app user, and returns where to send the browser. */
|
|
1574
|
+
approveMcpInteraction(id: string): Promise<McpInteractionDecision>;
|
|
1575
|
+
/** Denies one. Also returns a redirect — with `error=access_denied` on it, so the client learns from its own callback. */
|
|
1576
|
+
denyMcpInteraction(id: string): Promise<McpInteractionDecision>;
|
|
1577
|
+
/**
|
|
1578
|
+
* Every MCP client this app user has standing consent for — the "connected
|
|
1579
|
+
* apps" list, and the door out of a decision a person could otherwise make
|
|
1580
|
+
* once and never unmake. A withdrawn grant is never listed.
|
|
1581
|
+
*
|
|
1582
|
+
* `client_name_verified` is `false` here for the reason it is on
|
|
1583
|
+
* `mcpInteraction`, and it matters more rather than less: a list like this is
|
|
1584
|
+
* read long after the moment of approval, when nobody remembers what they
|
|
1585
|
+
* clicked.
|
|
1586
|
+
*/
|
|
1587
|
+
listMcpGrants(): Promise<McpConsentGrant[]>;
|
|
1644
1588
|
/**
|
|
1645
|
-
*
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1649
|
-
*
|
|
1650
|
-
*
|
|
1651
|
-
*
|
|
1652
|
-
* is nothing for an app to sequence and nothing here that could reveal
|
|
1653
|
-
* whether an address belongs to an account.
|
|
1589
|
+
* Withdraws one standing consent by the client's id.
|
|
1590
|
+
*
|
|
1591
|
+
* Resolves whether or not there was anything to withdraw — a client id this
|
|
1592
|
+
* account never approved and one it withdrew a minute ago both land on the
|
|
1593
|
+
* end state the caller asked for. A refusal there would tell a caller which
|
|
1594
|
+
* clients an account has connected, and would turn a double-clicked button
|
|
1595
|
+
* into a failure.
|
|
1654
1596
|
*/
|
|
1655
|
-
|
|
1597
|
+
revokeMcpGrant(clientId: string): Promise<void>;
|
|
1656
1598
|
}
|
|
1657
1599
|
|
|
1658
1600
|
/**
|
|
@@ -1937,58 +1879,6 @@ interface DatapointsApi {
|
|
|
1937
1879
|
}): Promise<HistorySamplesResponse>;
|
|
1938
1880
|
}
|
|
1939
1881
|
|
|
1940
|
-
/**
|
|
1941
|
-
* An end user's own consent grants, reachable as `client.grants` — every
|
|
1942
|
-
* client this identity has ever authorized, and the means to take one back
|
|
1943
|
-
* without touching any of the others.
|
|
1944
|
-
*
|
|
1945
|
-
* **End-user only.** A server key acts with the app's own full rights and
|
|
1946
|
-
* never went through a consent screen itself — there is no "self" here for
|
|
1947
|
-
* it to list or revoke, same reasoning as `auth.login` on a `serverKey`
|
|
1948
|
-
* client. An end user reaches a group by invitation and signs in through
|
|
1949
|
-
* `auth.login` or the hosted login; a server key never does either.
|
|
1950
|
-
*/
|
|
1951
|
-
interface GrantsApi {
|
|
1952
|
-
/**
|
|
1953
|
-
* Every client this end user has consented to, most-recently-granted
|
|
1954
|
-
* first. Each entry carries names, not only ids (`client_name`, `app_name`,
|
|
1955
|
-
* `role_name`) — the id is what `revoke()` addresses, but a person
|
|
1956
|
-
* deciding whether to revoke something needs to *recognise* it first, and
|
|
1957
|
-
* an id is not recognisable. **`client_name` is not trusted** — a
|
|
1958
|
-
* self-registered client chooses its own display name, and one has
|
|
1959
|
-
* already been measured calling itself "Fleetless Official Helper" — do
|
|
1960
|
-
* not render it as if Fleetless vouched for it.
|
|
1961
|
-
*/
|
|
1962
|
-
list(): Promise<ConsentGrantSummary[]>;
|
|
1963
|
-
/**
|
|
1964
|
-
* Revokes one grant by the client's id — the `client_id` from a
|
|
1965
|
-
* `list()` entry — without touching any other client's access.
|
|
1966
|
-
*
|
|
1967
|
-
* `revoked` tells "nothing matched" (`false`) from "matched and ended"
|
|
1968
|
-
* (`true`) — revoking a grant that is already gone (a stale id, a double
|
|
1969
|
-
* click) is not an error, just a no-op the caller can still tell apart
|
|
1970
|
-
* from a real revocation. `tokens_revoked` counts refresh **families**
|
|
1971
|
-
* ended, not access tokens — that count is deliberately never a claim
|
|
1972
|
-
* about access tokens, which are stateless and short-lived by design.
|
|
1973
|
-
*
|
|
1974
|
-
* **What actually happens to an already-issued access token is stronger
|
|
1975
|
-
* than that count implies.** It does not simply expire on its own: the
|
|
1976
|
-
* cloud re-checks
|
|
1977
|
-
* every request for a revoked grant, so **any** currently-valid access
|
|
1978
|
-
* token minted through this same client's OAuth flow for this end user
|
|
1979
|
-
* — not only the one used to call `revoke()` — answers `401
|
|
1980
|
-
* token_revoked` on its very next request, no wait for its TTL. This is
|
|
1981
|
-
* scoped to that one client: a session that never went through this
|
|
1982
|
-
* client's consent (a plain `auth.login()` session, or one bound to a
|
|
1983
|
-
* different client) carries no trace of this client's id and is
|
|
1984
|
-
* unaffected. Do not build on the weaker "it will expire eventually" —
|
|
1985
|
-
* if this platform ever moves to stateless verification without that
|
|
1986
|
-
* re-check, this immediacy would go away silently, which is exactly why
|
|
1987
|
-
* it is written down here instead of left implied.
|
|
1988
|
-
*/
|
|
1989
|
-
revoke(clientId: string): Promise<ConsentRevokeResponse>;
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
1882
|
/**
|
|
1993
1883
|
* Robot-wide job reads, reachable as `client.jobs`. Everything here is
|
|
1994
1884
|
* addressed by robot rather than by slug, which is what `actions` and
|
|
@@ -2142,10 +2032,12 @@ interface FleetlessClientConfig {
|
|
|
2142
2032
|
interface FleetlessClient {
|
|
2143
2033
|
/** The settled configuration, including the defaults `createClient` filled in. */
|
|
2144
2034
|
readonly config: FleetlessClientConfig;
|
|
2145
|
-
/**
|
|
2035
|
+
/**
|
|
2036
|
+
* The whole client auth API: registration, verification, login, logout,
|
|
2037
|
+
* password reset, invitations, the app's federated sign-in providers, the
|
|
2038
|
+
* MCP consent screen, and the app user's own standing MCP grants.
|
|
2039
|
+
*/
|
|
2146
2040
|
readonly auth: AuthApi;
|
|
2147
|
-
/** An end user's own consent grants — not available on a `serverKey` client, same reasoning as `auth`'s session-only methods. */
|
|
2148
|
-
readonly grants: GrantsApi;
|
|
2149
2041
|
/** A topic's latest value, a live subscription to it, and its recorded history. */
|
|
2150
2042
|
readonly datapoints: DatapointsApi;
|
|
2151
2043
|
/** Long-running work on the robot: invoke, cancel, and watch a job as it runs. */
|
|
@@ -2176,8 +2068,8 @@ interface FleetlessClient {
|
|
|
2176
2068
|
}
|
|
2177
2069
|
/**
|
|
2178
2070
|
* Builds a client for one app. Pass `tokenStore` (or nothing — the default
|
|
2179
|
-
* keeps the session in memory) for an
|
|
2180
|
-
* `auth.login` or
|
|
2071
|
+
* keeps the session in memory) for an app-user client that signs in with
|
|
2072
|
+
* `auth.login` or a federated provider; pass `serverKey` for a server-side
|
|
2181
2073
|
* caller that never holds a user session. Passing both throws, because the
|
|
2182
2074
|
* two are different identities and a client acts as exactly one.
|
|
2183
2075
|
*
|
|
@@ -2186,4 +2078,4 @@ interface FleetlessClient {
|
|
|
2186
2078
|
*/
|
|
2187
2079
|
declare function createClient(options: FleetlessClientOptions): FleetlessClient;
|
|
2188
2080
|
|
|
2189
|
-
export { type ActionsApi, type Asset, type AssetBytes, type AssetListResponse, type AssetsApi, type AuthApi, type
|
|
2081
|
+
export { type AcceptInvitationOptions, type ActionsApi, type Asset, type AssetBytes, type AssetListResponse, type AssetsApi, type AuthApi, type BeginOidcLoginOptions, type BusyDetails, type CameraDescriptor, type CameraLiveSession, type CameraSnapshot, type CameraSnapshotMeta, type CamerasApi, type ClientIdentity, type ClientMcpInteraction, type ClientOidcErrorCode, type CompleteOidcLoginOptions, type CreateMeshLoaderOptions, type DatapointEvent, type DatapointSubscription, type DatapointSubscriptionHandlers, type DatapointValue, type DatapointsApi, type FleetlessClient, type FleetlessClientConfig, type FleetlessClientOptions, FleetlessError, type FleetlessErrorCode, type FleetlessErrorOptions, type HistoryAggregation, type HistoryBucketsResponse, type HistoryOptions, type HistorySamplesResponse, InMemoryTokenStore, type InvokeOptions, type Job, type JobEvent, type JobState, type JobSubscription, type JobSubscriptionHandlers, type JobsApi, type McpConsentGrant, type McpInteractionDecision, type MeshLoaderDelegate, type OidcLoginRequest, type ParameterInvalidDetails, type ParameterViolation, type PrepareUrdfSceneOptions, type ProviderButton, type PublishersApi, type RateLimitDetails, type RegisterOptions, SDK_ERROR_CODES, type SdkErrorCode, type SendCommandOptions, type ServicesApi, type StoredSession, type TokenStore, type UrdfCompleteness, type UrdfSceneManager, type UrdfSceneResources, createClient, parameterInvalidDetails };
|