@mdbase-dev/connect 0.1.0-beta.110 → 0.1.0-beta.111

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.
@@ -1 +1 @@
1
- sha384-87GfQ8HHeR0B68yJLi01ycH5DHMGUf0jbXDsLhhpaYD8jpF/qvol4yQtP7hdPExh
1
+ sha384-/62JQssTDGRQGVQwcO5KLWO7BOqrDVxB0ne307QlEFVO3/MzYMBJfjmbz/OdCRWP
@@ -5,7 +5,7 @@ import { ConnectionFileTransport } from "./connection-file-transport.js";
5
5
  import { authorityProofHeaders } from "./authority-proof.js";
6
6
  import { performAuthorizationRefresh } from "./authorization-refresh.js";
7
7
  import { PendingMutationStore } from "./pending-mutation-store.js";
8
- import { directFallbackStatus, encryptedOperationError, fetchOperationRequest, isMutation, localNetworkPermission, loopbackRequest, operationFingerprint, operationTransportError, sameAuthorization, throwIfCancelled, unknownMutationOutcome, withOperationDeadline } from "./operation-helpers.js";
8
+ import { directFallbackResponse, encryptedOperationError, fetchOperationRequest, isMutation, localNetworkPermission, loopbackRequest, operationFingerprint, operationTransportError, sameAuthorization, throwIfCancelled, unknownMutationOutcome, withOperationDeadline } from "./operation-helpers.js";
9
9
  import { apiError, decodeJsonResponse, parseStored, } from "./runtime-utils.js";
10
10
  import { readStoredToken } from "./stored-token.js";
11
11
  import { requestOptionsWithinBudget, withCooperativeRequestBudget, withRequestBudget } from "./request-budget.js";
@@ -467,7 +467,7 @@ export class ConnectionTransport {
467
467
  body: JSON.stringify(encryptedRequest),
468
468
  signal: options.signal
469
469
  }));
470
- if (!directFallbackStatus(response.status)
470
+ if (!await directFallbackResponse(response)
471
471
  || await isExplicitConnectorBusyResponse(response)) {
472
472
  if (response.ok) {
473
473
  this.markDirectAvailable();
@@ -603,9 +603,15 @@ export class ConnectionTransport {
603
603
  }
604
604
  async probeDirectAccess(signal) {
605
605
  this.setDirectStatus("checking");
606
- if (await probeLoopbackAccess(this.loopbackUrl, OPERATION_TRANSPORT_PROTOCOL_VERSION, signal)) {
607
- this.markDirectAvailable();
608
- return "available";
606
+ try {
607
+ if (await probeLoopbackAccess(this.loopbackUrl, OPERATION_TRANSPORT_PROTOCOL_VERSION, signal)) {
608
+ this.markDirectAvailable();
609
+ return "available";
610
+ }
611
+ }
612
+ catch (error) {
613
+ this.markDirectUnavailable();
614
+ throw error;
609
615
  }
610
616
  if ((await localNetworkPermission()) === "denied")
611
617
  return this.setDirectStatus("denied");
@@ -1,5 +1,6 @@
1
1
  import { loopbackRequest } from "./operation-helpers.js";
2
- import { decodeJsonResponse } from "./runtime-utils.js";
2
+ import { decodeJsonResponse, portableApplicationOrigin } from "./runtime-utils.js";
3
+ import { serverConnectError } from "./errors.js";
3
4
  export function tokenSupportsDirectAccess(token, mode) {
4
5
  if (!token || token.authority || !token.encryption || !token.grantId || !token.keyHandle) {
5
6
  return false;
@@ -8,22 +9,26 @@ export function tokenSupportsDirectAccess(token, mode) {
8
9
  return false;
9
10
  return typeof location === "undefined"
10
11
  || !token.applicationOrigin
11
- || token.applicationOrigin === location.origin;
12
+ || token.applicationOrigin === portableApplicationOrigin();
12
13
  }
13
14
  export async function probeLoopbackAccess(loopbackUrl, expectedOperationProtocol, signal) {
15
+ let response;
14
16
  try {
15
- const response = await fetch(`${loopbackUrl}/v1/ready`, loopbackRequest({
17
+ response = await fetch(`${loopbackUrl}/v1/ready`, loopbackRequest({
16
18
  method: "GET",
17
19
  cache: "no-store",
18
20
  signal
19
21
  }));
20
- const body = await decodeJsonResponse(response, "invalid_operation_response", "The connector returned an invalid readiness response.").catch(() => null);
21
- return response.ok
22
- && body?.service === "mdbase-connect"
23
- && body?.loopback_protocol_version === 1
24
- && body?.operation_transport_protocol_version === expectedOperationProtocol;
25
22
  }
26
23
  catch {
27
24
  return false;
28
25
  }
26
+ if (response.status === 403) {
27
+ throw serverConnectError("operation_failed", "The connector rejected direct access from this application. Relay access remains available.", { status: response.status });
28
+ }
29
+ const body = await decodeJsonResponse(response, "invalid_operation_response", "The connector returned an invalid readiness response.").catch(() => null);
30
+ return response.ok
31
+ && body?.service === "mdbase-connect"
32
+ && body?.loopback_protocol_version === 1
33
+ && body?.operation_transport_protocol_version === expectedOperationProtocol;
29
34
  }
@@ -3,7 +3,7 @@ import { decryptRelayResponse, encryptRelayRequest } from "./crypto.js";
3
3
  import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
4
4
  import { GrantFileTransferCipher } from "./file-crypto.js";
5
5
  import { GrantKeyLeaseSet, retainCurrentGrantToken } from "./grant-key-leases.js";
6
- import { directFallbackStatus, loopbackRequest } from "./operation-helpers.js";
6
+ import { directFallbackResponse, loopbackRequest } from "./operation-helpers.js";
7
7
  import { apiError, decodeJsonResponse } from "./runtime-utils.js";
8
8
  import { isConnectorBusy, isExplicitConnectorBusyResponse, MAX_CONNECTOR_BUSY_RETRIES, waitForConnectorAvailability } from "./transient-retry.js";
9
9
  /** Keeps grant-bound file crypto and loopback framing out of the operation transport. */
@@ -42,7 +42,7 @@ export class LocalFileTransport {
42
42
  throw error;
43
43
  this.options.onDirectUnavailable();
44
44
  }
45
- if (response && (!directFallbackStatus(response.status)
45
+ if (response && (!await directFallbackResponse(response)
46
46
  || await isExplicitConnectorBusyResponse(response))) {
47
47
  if (response.ok)
48
48
  this.options.onDirectAvailable();
@@ -134,7 +134,7 @@ export class LocalFileTransport {
134
134
  throw error;
135
135
  this.options.onDirectUnavailable();
136
136
  }
137
- if (response && (!directFallbackStatus(response.status)
137
+ if (response && (!await directFallbackResponse(response)
138
138
  || await isExplicitConnectorBusyResponse(response))) {
139
139
  if (response.ok)
140
140
  this.options.onDirectAvailable();
@@ -180,7 +180,7 @@ export class LocalFileTransport {
180
180
  throw error;
181
181
  this.options.onDirectUnavailable();
182
182
  }
183
- if (response && (!directFallbackStatus(response.status)
183
+ if (response && (!await directFallbackResponse(response)
184
184
  || await isExplicitConnectorBusyResponse(response))) {
185
185
  if (response.ok)
186
186
  this.options.onDirectAvailable();
@@ -7,7 +7,7 @@ export type LoopbackRequestInit = RequestInit & {
7
7
  };
8
8
  export declare function loopbackRequest(init: RequestInit): LoopbackRequestInit;
9
9
  export declare function localNetworkPermission(): Promise<PermissionState | null>;
10
- export declare function directFallbackStatus(status: number): boolean;
10
+ export declare function directFallbackResponse(response: Response): Promise<boolean>;
11
11
  export declare function isMutation(operation: CollectionOperation, input?: unknown): boolean;
12
12
  export declare function withOperationDeadline(request: EncryptedRelayOperationRequest, deadlineUnixMs?: number): EncryptedRelayOperationRequest;
13
13
  export declare function uniqueOperations(operations: CollectionOperation[]): CollectionOperation[];
@@ -16,8 +16,13 @@ export async function localNetworkPermission() {
16
16
  return null;
17
17
  }
18
18
  }
19
- export function directFallbackStatus(status) {
20
- return status === 404 || status === 405 || status === 426 || status >= 500;
19
+ export async function directFallbackResponse(response) {
20
+ const { status } = response;
21
+ if (status === 404 || status === 405 || status === 426 || status >= 500)
22
+ return true;
23
+ // Loopback rejects browser requests before execution with an empty 403.
24
+ // Structured grant/policy rejections must remain terminal, not route failures.
25
+ return status === 403 && (await response.clone().text()).trim() === "";
21
26
  }
22
27
  export function isMutation(operation, input) {
23
28
  return isMutatingOperation(operation, input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdbase-dev/connect",
3
- "version": "0.1.0-beta.110",
3
+ "version": "0.1.0-beta.111",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "README.md"
34
34
  ],
35
35
  "dependencies": {
36
- "@mdbase-dev/connect-protocol": "0.1.0-beta.110"
36
+ "@mdbase-dev/connect-protocol": "0.1.0-beta.111"
37
37
  },
38
38
  "devDependencies": {
39
39
  "esbuild": "^0.28.1",