@synap-core/sdk 1.5.0 → 1.5.2

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.
@@ -94,8 +94,20 @@ interface PodEntity {
94
94
  /** ISO 8601. */
95
95
  createdAt?: string;
96
96
  updatedAt?: string;
97
+ /** The roles it wears, with `includeFacets`; absent when it wears none. */
98
+ facetSlugs?: string[];
99
+ /** Each role's overlay, with `includeFacets`; absent when it wears none. */
100
+ facets?: PodEntityFacet[];
97
101
  [key: string]: unknown;
98
102
  }
103
+ /** One role an entity wears (`FacetRowAnnotation`). */
104
+ interface PodEntityFacet {
105
+ facetId: string;
106
+ /** The role, e.g. `lead`. */
107
+ slug: string;
108
+ properties: Record<string, unknown>;
109
+ status: string | null;
110
+ }
99
111
  interface ListEntitiesOptions {
100
112
  /** One granted space. Omitted: every space the connection can read. */
101
113
  workspaceId?: string;
@@ -108,6 +120,10 @@ interface ListEntitiesOptions {
108
120
  sort?: string;
109
121
  /** With `workspaceId`: also the person's pod-wide rows (the pod's default: true). */
110
122
  includePodWide?: boolean;
123
+ /** Only the entities wearing this role, e.g. `lead`. */
124
+ facetSlug?: string;
125
+ /** Put `facetSlugs` and `facets` on each row that wears a role. */
126
+ includeFacets?: boolean;
111
127
  }
112
128
  type PodConnectionSource = "graph" | "property" | "thread" | "context_channel" | "focus_session";
113
129
  /** One edge of `GET /entities/:id/connections` (`EntityConnection`). */
@@ -300,7 +316,27 @@ interface ApproveProposalOptions {
300
316
 
301
317
  type PodErrorCode = "expired" | "unauthorized" | "forbidden" | "door_unsupported" | "not_found" | "invalid" | "conflict" | "rate_limited" | "pod_unreachable"
302
318
  /** The pod answered a redirect (a moved pod answers 308 from its old name): connect again at its new address. */
303
- | "pod_moved" | "server";
319
+ | "pod_moved"
320
+ /**
321
+ * A credential door (approving a proposal that widens who or what can reach
322
+ * the pod) wants the person to re-confirm at the POD within 15 minutes; a
323
+ * Synap Cloud sign-in never counts. Recovery: `prompt_login`.
324
+ */
325
+ | "step_up_required"
326
+ /** Synap Cloud refused to vouch a person with no verified email. Recovery: `verify_email`. */
327
+ | "email_unverified"
328
+ /** Synap Cloud vouches an app origin over HTTPS only. Recovery: `use_https`. */
329
+ | "origin_insecure" | "server";
330
+ /**
331
+ * What a typed refusal asks the app to do:
332
+ * - `prompt_login`: send the person through the connect handler with
333
+ * `prompt=login` (re-confirm at the pod; the SDK redeems it with the
334
+ * device's refresh token), then retry ONCE;
335
+ * - `verify_email`: tell the person to verify their Synap email;
336
+ * - `use_https`: serve the app over HTTPS (`http://localhost` signs in to a
337
+ * local Control Plane started with `CP_ALLOW_DEV_ORIGINS=true`, never to api.synap.live).
338
+ */
339
+ type PodRecovery = "prompt_login" | "verify_email" | "use_https";
304
340
  /**
305
341
  * Why the SDK refused before sending anything (`status: null`):
306
342
  * - `agent_cannot_approve`: an agent connection asked to approve (the pod
@@ -318,6 +354,8 @@ interface PodError {
318
354
  detail?: string;
319
355
  /** Set when the SDK refused locally (1.5). */
320
356
  reason?: PodRefusalReason;
357
+ /** What to do next, on `step_up_required`, `email_unverified` and `origin_insecure`. */
358
+ recovery?: PodRecovery;
321
359
  /**
322
360
  * On `unauthorized` (1.5): the pod refused the access token, the connection
323
361
  * still holds a refresh token, and this client could not renew it (no
@@ -898,6 +936,8 @@ interface ConnectTransaction {
898
936
  resource: string | null;
899
937
  /** A same-origin path to land on afterwards. */
900
938
  next: string | null;
939
+ /** The sign-in re-confirms a device that is already connected (`prompt=login`). */
940
+ stepUp?: boolean;
901
941
  createdAt: number;
902
942
  }
903
943
  interface FlowOptions {
@@ -922,8 +962,13 @@ interface StartConnectOptions extends FlowOptions {
922
962
  * `"consent"`: the person asked to see the consent screen again (to widen or
923
963
  * narrow what the app may touch). Sent as OIDC `prompt=consent`; the sign-in
924
964
  * is otherwise the same (fresh PKCE verifier and state).
965
+ *
966
+ * `"login"`: a step-up. A credential door refused a stale device
967
+ * (`step_up_required`): the person re-confirms at the pod (OIDC
968
+ * `prompt=login`) and the callback redeems the code WITH the connection's
969
+ * refresh token (`synap_device_refresh_token`; see `HandleCallbackOptions.current`).
925
970
  */
926
- prompt?: "consent" | null;
971
+ prompt?: "consent" | "login" | null;
927
972
  allowInsecureLoopback?: boolean;
928
973
  }
929
974
  interface StartedConnect {
@@ -948,6 +993,13 @@ interface HandleCallbackOptions extends FlowOptions {
948
993
  clientId: string;
949
994
  redirectUri: string;
950
995
  secret: AppSecret;
996
+ /**
997
+ * The connection this browser holds now. A step-up (`prompt=login`) of a
998
+ * Synap app's device is redeemed with ITS refresh token (the pod moves that
999
+ * device's clock instead of making a second device); without one, or for an
1000
+ * app that is not a person's device, the code is redeemed as any other.
1001
+ */
1002
+ current?: PodConnection | null;
951
1003
  }
952
1004
  interface CallbackResult {
953
1005
  connection: PodConnection;
@@ -1012,4 +1064,4 @@ declare function revokeConnection(connection: PodConnection, opts?: FlowOptions)
1012
1064
  /** RFC 7636 §4.2: `BASE64URL(SHA256(ASCII(code_verifier)))`. */
1013
1065
  declare function pkceChallenge(verifier: string): Promise<string>;
1014
1066
 
1015
- export { type AcceptCloudReturnOptions, AppRequest, type AppSecret, type ApproveProposalOptions, CLOUD_TRANSACTION_TTL_MS, CONNECT_ERROR_CODES, type CallbackResult, ClientMetadataDocument, type CloudConnectConfig, type CloudReturn, type CloudTransaction, ConnectError, type ConnectErrorCode, type ConnectTransaction, type ConnectionStore, type CookieNames, DEFAULT_PATHS, DEFAULT_REFRESH_IDLE_MS, type DiscoverOptions, type FlowOptions, type GetDocumentOptions, type GetEntityConnectionsOptions, type HandleCallbackOptions, type LaunchHint, type ListEntitiesOptions, type ListFocusSessionsOptions, type ListPendingProposalsOptions, MARKER_TTL_SEC, MIN_SECRET_LENGTH, type Opened, type PendingProposal, type PendingProposals, PodActsAs, type PodAppAuth, type PodAppAuthConfig, type PodAppClientOptions, type PodAppPaths, type PodApproval, type PodClient, type PodClientOptions, PodConnection, type PodConnectionSource, type PodDocument, type PodDocumentSection, type PodEntity, type PodEntityConnection, type PodEntityConnections, type PodError, type PodErrorCode, type PodFileUrl, type PodFocusSession, PodGrant, PodIssuer, type PodRefusalReason, type PodResult, type PodSessionSlot, type PodSessionStatus, PodSpace, type PodWriteResult, type QueryValue, REFRESH_WINDOW_MS, ROTATION_MEMORY_MS, type RefreshOptions, type RefreshOutcome, type RefreshStatus, type SealPurpose, type SealedCloudTransaction, type SealedCookieStoreOptions, type SealedTransaction, type StartCloudConnectOptions, type StartConnectOptions, type StartedCloudConnect, type StartedConnect, TRANSACTION_TTL_MS, acceptCloudReturn, acceptLaunch, applySetCookies, buildClientMetadata, can, canonicalPodUrl, cloudBaseUrl, cloudConnectAvailable, connectionActsAs, connectionExpiresAt, cookieNames, createPodAppAuth, createPodClient, createSealedCookieStore, discoverPod, handleCallback, humanSlots, isBrowserRuntime, isConnectError, isPrefetchOrAsset, markerCookie, openTransaction, parseConnectErrorCode, parseTokenResponse, pkceChallenge, readMarker, refreshConnection, reviewUrlFor, revokeConnection, safeNextPath, seal, spaces, startCloudConnect, startConnect, unseal };
1067
+ export { type AcceptCloudReturnOptions, AppRequest, type AppSecret, type ApproveProposalOptions, CLOUD_TRANSACTION_TTL_MS, CONNECT_ERROR_CODES, type CallbackResult, ClientMetadataDocument, type CloudConnectConfig, type CloudReturn, type CloudTransaction, ConnectError, type ConnectErrorCode, type ConnectTransaction, type ConnectionStore, type CookieNames, DEFAULT_PATHS, DEFAULT_REFRESH_IDLE_MS, type DiscoverOptions, type FlowOptions, type GetDocumentOptions, type GetEntityConnectionsOptions, type HandleCallbackOptions, type LaunchHint, type ListEntitiesOptions, type ListFocusSessionsOptions, type ListPendingProposalsOptions, MARKER_TTL_SEC, MIN_SECRET_LENGTH, type Opened, type PendingProposal, type PendingProposals, PodActsAs, type PodAppAuth, type PodAppAuthConfig, type PodAppClientOptions, type PodAppPaths, type PodApproval, type PodClient, type PodClientOptions, PodConnection, type PodConnectionSource, type PodDocument, type PodDocumentSection, type PodEntity, type PodEntityConnection, type PodEntityConnections, type PodEntityFacet, type PodError, type PodErrorCode, type PodFileUrl, type PodFocusSession, PodGrant, PodIssuer, type PodRecovery, type PodRefusalReason, type PodResult, type PodSessionSlot, type PodSessionStatus, PodSpace, type PodWriteResult, type QueryValue, REFRESH_WINDOW_MS, ROTATION_MEMORY_MS, type RefreshOptions, type RefreshOutcome, type RefreshStatus, type SealPurpose, type SealedCloudTransaction, type SealedCookieStoreOptions, type SealedTransaction, type StartCloudConnectOptions, type StartConnectOptions, type StartedCloudConnect, type StartedConnect, TRANSACTION_TTL_MS, acceptCloudReturn, acceptLaunch, applySetCookies, buildClientMetadata, can, canonicalPodUrl, cloudBaseUrl, cloudConnectAvailable, connectionActsAs, connectionExpiresAt, cookieNames, createPodAppAuth, createPodClient, createSealedCookieStore, discoverPod, handleCallback, humanSlots, isBrowserRuntime, isConnectError, isPrefetchOrAsset, markerCookie, openTransaction, parseConnectErrorCode, parseTokenResponse, pkceChallenge, readMarker, refreshConnection, reviewUrlFor, revokeConnection, safeNextPath, seal, spaces, startCloudConnect, startConnect, unseal };
package/dist/connect.d.ts CHANGED
@@ -94,8 +94,20 @@ interface PodEntity {
94
94
  /** ISO 8601. */
95
95
  createdAt?: string;
96
96
  updatedAt?: string;
97
+ /** The roles it wears, with `includeFacets`; absent when it wears none. */
98
+ facetSlugs?: string[];
99
+ /** Each role's overlay, with `includeFacets`; absent when it wears none. */
100
+ facets?: PodEntityFacet[];
97
101
  [key: string]: unknown;
98
102
  }
103
+ /** One role an entity wears (`FacetRowAnnotation`). */
104
+ interface PodEntityFacet {
105
+ facetId: string;
106
+ /** The role, e.g. `lead`. */
107
+ slug: string;
108
+ properties: Record<string, unknown>;
109
+ status: string | null;
110
+ }
99
111
  interface ListEntitiesOptions {
100
112
  /** One granted space. Omitted: every space the connection can read. */
101
113
  workspaceId?: string;
@@ -108,6 +120,10 @@ interface ListEntitiesOptions {
108
120
  sort?: string;
109
121
  /** With `workspaceId`: also the person's pod-wide rows (the pod's default: true). */
110
122
  includePodWide?: boolean;
123
+ /** Only the entities wearing this role, e.g. `lead`. */
124
+ facetSlug?: string;
125
+ /** Put `facetSlugs` and `facets` on each row that wears a role. */
126
+ includeFacets?: boolean;
111
127
  }
112
128
  type PodConnectionSource = "graph" | "property" | "thread" | "context_channel" | "focus_session";
113
129
  /** One edge of `GET /entities/:id/connections` (`EntityConnection`). */
@@ -300,7 +316,27 @@ interface ApproveProposalOptions {
300
316
 
301
317
  type PodErrorCode = "expired" | "unauthorized" | "forbidden" | "door_unsupported" | "not_found" | "invalid" | "conflict" | "rate_limited" | "pod_unreachable"
302
318
  /** The pod answered a redirect (a moved pod answers 308 from its old name): connect again at its new address. */
303
- | "pod_moved" | "server";
319
+ | "pod_moved"
320
+ /**
321
+ * A credential door (approving a proposal that widens who or what can reach
322
+ * the pod) wants the person to re-confirm at the POD within 15 minutes; a
323
+ * Synap Cloud sign-in never counts. Recovery: `prompt_login`.
324
+ */
325
+ | "step_up_required"
326
+ /** Synap Cloud refused to vouch a person with no verified email. Recovery: `verify_email`. */
327
+ | "email_unverified"
328
+ /** Synap Cloud vouches an app origin over HTTPS only. Recovery: `use_https`. */
329
+ | "origin_insecure" | "server";
330
+ /**
331
+ * What a typed refusal asks the app to do:
332
+ * - `prompt_login`: send the person through the connect handler with
333
+ * `prompt=login` (re-confirm at the pod; the SDK redeems it with the
334
+ * device's refresh token), then retry ONCE;
335
+ * - `verify_email`: tell the person to verify their Synap email;
336
+ * - `use_https`: serve the app over HTTPS (`http://localhost` signs in to a
337
+ * local Control Plane started with `CP_ALLOW_DEV_ORIGINS=true`, never to api.synap.live).
338
+ */
339
+ type PodRecovery = "prompt_login" | "verify_email" | "use_https";
304
340
  /**
305
341
  * Why the SDK refused before sending anything (`status: null`):
306
342
  * - `agent_cannot_approve`: an agent connection asked to approve (the pod
@@ -318,6 +354,8 @@ interface PodError {
318
354
  detail?: string;
319
355
  /** Set when the SDK refused locally (1.5). */
320
356
  reason?: PodRefusalReason;
357
+ /** What to do next, on `step_up_required`, `email_unverified` and `origin_insecure`. */
358
+ recovery?: PodRecovery;
321
359
  /**
322
360
  * On `unauthorized` (1.5): the pod refused the access token, the connection
323
361
  * still holds a refresh token, and this client could not renew it (no
@@ -898,6 +936,8 @@ interface ConnectTransaction {
898
936
  resource: string | null;
899
937
  /** A same-origin path to land on afterwards. */
900
938
  next: string | null;
939
+ /** The sign-in re-confirms a device that is already connected (`prompt=login`). */
940
+ stepUp?: boolean;
901
941
  createdAt: number;
902
942
  }
903
943
  interface FlowOptions {
@@ -922,8 +962,13 @@ interface StartConnectOptions extends FlowOptions {
922
962
  * `"consent"`: the person asked to see the consent screen again (to widen or
923
963
  * narrow what the app may touch). Sent as OIDC `prompt=consent`; the sign-in
924
964
  * is otherwise the same (fresh PKCE verifier and state).
965
+ *
966
+ * `"login"`: a step-up. A credential door refused a stale device
967
+ * (`step_up_required`): the person re-confirms at the pod (OIDC
968
+ * `prompt=login`) and the callback redeems the code WITH the connection's
969
+ * refresh token (`synap_device_refresh_token`; see `HandleCallbackOptions.current`).
925
970
  */
926
- prompt?: "consent" | null;
971
+ prompt?: "consent" | "login" | null;
927
972
  allowInsecureLoopback?: boolean;
928
973
  }
929
974
  interface StartedConnect {
@@ -948,6 +993,13 @@ interface HandleCallbackOptions extends FlowOptions {
948
993
  clientId: string;
949
994
  redirectUri: string;
950
995
  secret: AppSecret;
996
+ /**
997
+ * The connection this browser holds now. A step-up (`prompt=login`) of a
998
+ * Synap app's device is redeemed with ITS refresh token (the pod moves that
999
+ * device's clock instead of making a second device); without one, or for an
1000
+ * app that is not a person's device, the code is redeemed as any other.
1001
+ */
1002
+ current?: PodConnection | null;
951
1003
  }
952
1004
  interface CallbackResult {
953
1005
  connection: PodConnection;
@@ -1012,4 +1064,4 @@ declare function revokeConnection(connection: PodConnection, opts?: FlowOptions)
1012
1064
  /** RFC 7636 §4.2: `BASE64URL(SHA256(ASCII(code_verifier)))`. */
1013
1065
  declare function pkceChallenge(verifier: string): Promise<string>;
1014
1066
 
1015
- export { type AcceptCloudReturnOptions, AppRequest, type AppSecret, type ApproveProposalOptions, CLOUD_TRANSACTION_TTL_MS, CONNECT_ERROR_CODES, type CallbackResult, ClientMetadataDocument, type CloudConnectConfig, type CloudReturn, type CloudTransaction, ConnectError, type ConnectErrorCode, type ConnectTransaction, type ConnectionStore, type CookieNames, DEFAULT_PATHS, DEFAULT_REFRESH_IDLE_MS, type DiscoverOptions, type FlowOptions, type GetDocumentOptions, type GetEntityConnectionsOptions, type HandleCallbackOptions, type LaunchHint, type ListEntitiesOptions, type ListFocusSessionsOptions, type ListPendingProposalsOptions, MARKER_TTL_SEC, MIN_SECRET_LENGTH, type Opened, type PendingProposal, type PendingProposals, PodActsAs, type PodAppAuth, type PodAppAuthConfig, type PodAppClientOptions, type PodAppPaths, type PodApproval, type PodClient, type PodClientOptions, PodConnection, type PodConnectionSource, type PodDocument, type PodDocumentSection, type PodEntity, type PodEntityConnection, type PodEntityConnections, type PodError, type PodErrorCode, type PodFileUrl, type PodFocusSession, PodGrant, PodIssuer, type PodRefusalReason, type PodResult, type PodSessionSlot, type PodSessionStatus, PodSpace, type PodWriteResult, type QueryValue, REFRESH_WINDOW_MS, ROTATION_MEMORY_MS, type RefreshOptions, type RefreshOutcome, type RefreshStatus, type SealPurpose, type SealedCloudTransaction, type SealedCookieStoreOptions, type SealedTransaction, type StartCloudConnectOptions, type StartConnectOptions, type StartedCloudConnect, type StartedConnect, TRANSACTION_TTL_MS, acceptCloudReturn, acceptLaunch, applySetCookies, buildClientMetadata, can, canonicalPodUrl, cloudBaseUrl, cloudConnectAvailable, connectionActsAs, connectionExpiresAt, cookieNames, createPodAppAuth, createPodClient, createSealedCookieStore, discoverPod, handleCallback, humanSlots, isBrowserRuntime, isConnectError, isPrefetchOrAsset, markerCookie, openTransaction, parseConnectErrorCode, parseTokenResponse, pkceChallenge, readMarker, refreshConnection, reviewUrlFor, revokeConnection, safeNextPath, seal, spaces, startCloudConnect, startConnect, unseal };
1067
+ export { type AcceptCloudReturnOptions, AppRequest, type AppSecret, type ApproveProposalOptions, CLOUD_TRANSACTION_TTL_MS, CONNECT_ERROR_CODES, type CallbackResult, ClientMetadataDocument, type CloudConnectConfig, type CloudReturn, type CloudTransaction, ConnectError, type ConnectErrorCode, type ConnectTransaction, type ConnectionStore, type CookieNames, DEFAULT_PATHS, DEFAULT_REFRESH_IDLE_MS, type DiscoverOptions, type FlowOptions, type GetDocumentOptions, type GetEntityConnectionsOptions, type HandleCallbackOptions, type LaunchHint, type ListEntitiesOptions, type ListFocusSessionsOptions, type ListPendingProposalsOptions, MARKER_TTL_SEC, MIN_SECRET_LENGTH, type Opened, type PendingProposal, type PendingProposals, PodActsAs, type PodAppAuth, type PodAppAuthConfig, type PodAppClientOptions, type PodAppPaths, type PodApproval, type PodClient, type PodClientOptions, PodConnection, type PodConnectionSource, type PodDocument, type PodDocumentSection, type PodEntity, type PodEntityConnection, type PodEntityConnections, type PodEntityFacet, type PodError, type PodErrorCode, type PodFileUrl, type PodFocusSession, PodGrant, PodIssuer, type PodRecovery, type PodRefusalReason, type PodResult, type PodSessionSlot, type PodSessionStatus, PodSpace, type PodWriteResult, type QueryValue, REFRESH_WINDOW_MS, ROTATION_MEMORY_MS, type RefreshOptions, type RefreshOutcome, type RefreshStatus, type SealPurpose, type SealedCloudTransaction, type SealedCookieStoreOptions, type SealedTransaction, type StartCloudConnectOptions, type StartConnectOptions, type StartedCloudConnect, type StartedConnect, TRANSACTION_TTL_MS, acceptCloudReturn, acceptLaunch, applySetCookies, buildClientMetadata, can, canonicalPodUrl, cloudBaseUrl, cloudConnectAvailable, connectionActsAs, connectionExpiresAt, cookieNames, createPodAppAuth, createPodClient, createSealedCookieStore, discoverPod, handleCallback, humanSlots, isBrowserRuntime, isConnectError, isPrefetchOrAsset, markerCookie, openTransaction, parseConnectErrorCode, parseTokenResponse, pkceChallenge, readMarker, refreshConnection, reviewUrlFor, revokeConnection, safeNextPath, seal, spaces, startCloudConnect, startConnect, unseal };
package/dist/connect.js CHANGED
@@ -555,14 +555,32 @@ function renewalError(err) {
555
555
  return { code: "server", status: null, detail };
556
556
  }
557
557
  }
558
+ var STEP_UP_MESSAGE = "Confirm it's you before changing who or what can reach this pod.";
559
+ var CP_PROBLEMS = {
560
+ CP_ACCOUNT_EMAIL_REQUIRED: { code: "email_unverified", recovery: "verify_email" },
561
+ FEDERATED_APPLICATION_ORIGIN_INSECURE: { code: "origin_insecure", recovery: "use_https" }
562
+ };
563
+ function isStepUp(obj) {
564
+ const data = (v) => v && typeof v === "object" ? v : {};
565
+ const nested = data(data(obj.error).data).reasonCode ?? data(obj.data).reasonCode ?? obj.reasonCode;
566
+ return nested === "STEP_UP_REQUIRED" || obj.error === STEP_UP_MESSAGE || obj.message === STEP_UP_MESSAGE;
567
+ }
568
+ function classifyRefusal(status, body) {
569
+ const obj = body && typeof body === "object" ? body : {};
570
+ if (status === 403) {
571
+ if (isStepUp(obj)) return { code: "step_up_required", recovery: "prompt_login" };
572
+ return { code: obj.code === "GRANT_DOOR_UNSUPPORTED" ? "door_unsupported" : "forbidden" };
573
+ }
574
+ const problem = typeof obj.code === "string" ? CP_PROBLEMS[obj.code] : void 0;
575
+ return problem ?? { code: "conflict" };
576
+ }
558
577
  function errorFor(status, body, retryAfter, now) {
559
578
  const obj = body && typeof body === "object" ? body : {};
560
579
  const detail = typeof obj.error === "string" ? obj.error : typeof obj.message === "string" ? obj.message : void 0;
561
580
  const base = { status, ...detail !== void 0 ? { detail } : {} };
562
581
  if (status === 401) return { code: "unauthorized", ...base };
563
- if (status === 403) return { code: obj.code === "GRANT_DOOR_UNSUPPORTED" ? "door_unsupported" : "forbidden", ...base };
582
+ if (status === 403 || status === 409) return { ...classifyRefusal(status, body), ...base };
564
583
  if (status === 404) return { code: "not_found", ...base };
565
- if (status === 409) return { code: "conflict", ...base };
566
584
  if (status === 400 || status === 422) return { code: "invalid", ...base };
567
585
  if (status === 429) {
568
586
  const retryAfterSec = retryAfterSeconds(retryAfter, now);
@@ -733,7 +751,10 @@ function createPodClient(connection, opts = {}) {
733
751
  limit: clamp(opts2.limit, 1, 1e3),
734
752
  offset: clamp(opts2.offset, 0, Number.MAX_SAFE_INTEGER),
735
753
  sort: opts2.sort,
736
- includePodWide: opts2.includePodWide
754
+ includePodWide: opts2.includePodWide,
755
+ facetSlug: opts2.facetSlug,
756
+ // Sent only when asked: the default request stays the 1.5.0 one.
757
+ includeFacets: opts2.includeFacets ? true : void 0
737
758
  }, (data) => Array.isArray(data) ? data.filter(hasId) : null);
738
759
  }
739
760
  async function getEntity(id) {
@@ -895,7 +916,7 @@ async function startConnect(opts) {
895
916
  url.searchParams.set("code_challenge_method", "S256");
896
917
  if (resource) url.searchParams.set("resource", resource);
897
918
  if (opts.spaceHint) url.searchParams.set("synap_space_hint", opts.spaceHint);
898
- if (opts.prompt === "consent") url.searchParams.set("prompt", "consent");
919
+ if (opts.prompt === "consent" || opts.prompt === "login") url.searchParams.set("prompt", opts.prompt);
899
920
  const tx = {
900
921
  v: 1,
901
922
  issuer: issuer.issuer,
@@ -907,6 +928,7 @@ async function startConnect(opts) {
907
928
  verifier,
908
929
  resource,
909
930
  next: opts.next ?? null,
931
+ ...opts.prompt === "login" ? { stepUp: true } : {},
910
932
  createdAt: now
911
933
  };
912
934
  const expiresAt = now + TRANSACTION_TTL_MS;
@@ -964,13 +986,16 @@ async function handleCallback(opts) {
964
986
  code_verifier: tx.verifier
965
987
  });
966
988
  if (tx.resource) body.set("resource", tx.resource);
989
+ const device = tx.stepUp && opts.current && opts.current.actsAs === "person" && opts.current.podUrl === tx.issuer && opts.current.clientId === tx.clientId ? opts.current : null;
990
+ if (device?.refreshToken) body.set("synap_device_refresh_token", device.refreshToken);
967
991
  const json = await tokenRequest(tx.tokenEndpoint, body, "token", opts);
968
992
  const connection = parseTokenResponse(json, {
969
993
  podUrl: tx.issuer,
970
994
  clientId: tx.clientId,
971
995
  tokenEndpoint: tx.tokenEndpoint,
972
996
  revocationEndpoint: tx.revocationEndpoint,
973
- now: (opts.now ?? Date.now)()
997
+ now: (opts.now ?? Date.now)(),
998
+ ...device ? { previous: device } : {}
974
999
  });
975
1000
  return { connection, next: tx.next };
976
1001
  }
@@ -1297,6 +1322,10 @@ var PERMISSION_SEGMENT_RE = /^[a-z0-9][a-z0-9_-]*$/;
1297
1322
  function isSynapAppOrigin(origin) {
1298
1323
  return /^https:\/\/[a-z0-9]([a-z0-9-]*[a-z0-9])?\.synap\.live$/.test(origin);
1299
1324
  }
1325
+ function isMockModeOrigin(origin) {
1326
+ const url = new URL(origin);
1327
+ return url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
1328
+ }
1300
1329
  function assertAppPermission(permission, origin) {
1301
1330
  const segs = permission.split(".");
1302
1331
  const fail = (why) => {
@@ -1306,7 +1335,7 @@ function assertAppPermission(permission, origin) {
1306
1335
  fail("is not <subject>[.<kind>].<action> in lowercase (no *)");
1307
1336
  }
1308
1337
  if (segs.length === 3 && segs[0] !== "entity") fail(`names a kind, but "${segs[0]}" has none (only entity does)`);
1309
- if (SYNAP_APP_REVIEW_PERMISSIONS.includes(permission) && isSynapAppOrigin(origin)) return;
1338
+ if (SYNAP_APP_REVIEW_PERMISSIONS.includes(permission) && (isSynapAppOrigin(origin) || isMockModeOrigin(origin))) return;
1310
1339
  if (!GRANT_ACTIONS.includes(segs[segs.length - 1])) {
1311
1340
  fail(`asks for "${segs[segs.length - 1]}", which an app cannot be granted (read, create, update, delete)`);
1312
1341
  }
@@ -1472,7 +1501,8 @@ function createPodAppAuth(config) {
1472
1501
  const pod = formField(form, "pod")?.slice(0, 512) ?? "";
1473
1502
  const next = safeNextPath(formField(form, "next"), origin) ?? paths.afterConnect;
1474
1503
  const space = formField(form, "space");
1475
- const prompt = formField(form, "prompt") === "consent" ? "consent" : null;
1504
+ const asked = formField(form, "prompt");
1505
+ const prompt = asked === "consent" || asked === "login" ? asked : null;
1476
1506
  try {
1477
1507
  if (!prompt && await liveConnectionTo(request, pod)) return redirect(new URL(next, origin).href);
1478
1508
  return await toPod({ pod, next, space, prompt });
@@ -1489,6 +1519,7 @@ function createPodAppAuth(config) {
1489
1519
  ...flow,
1490
1520
  callbackUrl: request.url,
1491
1521
  transaction,
1522
+ current: await store.read(request).catch(() => null),
1492
1523
  clientId,
1493
1524
  redirectUri,
1494
1525
  secret: config.secret