@mdbase-dev/connect 0.1.0-beta.61 → 0.1.0-beta.62

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-8q/ad0vEZFISbJVmcgiwghE8Zi5/u4kBtZPskPy+cHhXMEwZoHGTiGdujLx57o8/
1
+ sha384-bNuQ6oZAI3yOsHt34NNTiVWa2gbZsyM2B4Iy1Gy4WQdXBSxEtUIgtaPLepNJYZjW
@@ -7,7 +7,7 @@ import { PendingMutationStore } from "./pending-mutation-store.js";
7
7
  import { directFallbackStatus, isMutation, localNetworkPermission, loopbackRequest, operationFingerprint, operationTransportError, sameAuthorization, throwIfCancelled, unknownMutationOutcome } from "./operation-helpers.js";
8
8
  import { apiError, decodeJsonResponse, oauthErrorCode, parseStored, } from "./runtime-utils.js";
9
9
  import { readStoredToken } from "./stored-token.js";
10
- import { withCooperativeRequestBudget, withRequestBudget } from "./request-budget.js";
10
+ import { requestOptionsWithinBudget, withCooperativeRequestBudget, withRequestBudget } from "./request-budget.js";
11
11
  import { sendAuthoritySyncRequest } from "./authority-sync-request.js";
12
12
  import { freshEncryptedRequest } from "./fresh-encrypted-request.js";
13
13
  import { isExplicitConnectorBusyResponse, retryExplicitConnectorBusy } from "./transient-retry.js";
@@ -127,10 +127,7 @@ export class ConnectionTransport {
127
127
  return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.performOperationWithinBudget(pending.operation, pending.request?.input ?? {}, { ...options, signal: budget.signal }, pending));
128
128
  }
129
129
  async performOperation(operation, input, options = {}) {
130
- return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.performOperationWithinBudget(operation, input, {
131
- ...options,
132
- signal: budget.signal
133
- }));
130
+ return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.performOperationWithinBudget(operation, input, requestOptionsWithinBudget(options, budget)));
134
131
  }
135
132
  async performOperationWithinBudget(operation, input, options, storedPending, freshReadRetried = false, connectorBusyRetries = 0, knownRejectedMutationRetry = false) {
136
133
  throwIfCancelled(options.signal);
@@ -383,7 +380,7 @@ export class ConnectionTransport {
383
380
  encryptedRequest = pending.envelope;
384
381
  }
385
382
  else {
386
- encryptedRequest = await encryptRelayRequest(this.keyStore, token.keyHandle, { grantId: token.grantId, applicationId: token.clientId, encryption: token.encryption }, operation, input, requestId);
383
+ encryptedRequest = await encryptRelayRequest(this.keyStore, token.keyHandle, { grantId: token.grantId, applicationId: token.clientId, encryption: token.encryption }, operation, input, requestId, options.deadlineUnixMs);
387
384
  this.pendingMutationStore.store({
388
385
  collectionId: token.collectionId,
389
386
  ...(token.grantId ? { grantId: token.grantId } : {}),
@@ -401,7 +398,7 @@ export class ConnectionTransport {
401
398
  }
402
399
  }
403
400
  else {
404
- encryptedRequest = await encryptRelayRequest(this.keyStore, token.keyHandle, { grantId: token.grantId, applicationId: token.clientId, encryption: token.encryption }, operation, input);
401
+ encryptedRequest = await encryptRelayRequest(this.keyStore, token.keyHandle, { grantId: token.grantId, applicationId: token.clientId, encryption: token.encryption }, operation, input, undefined, options.deadlineUnixMs);
405
402
  }
406
403
  }
407
404
  catch (error) {
@@ -481,7 +478,7 @@ export class ConnectionTransport {
481
478
  }
482
479
  let response;
483
480
  if (!pendingMutation) {
484
- encryptedRequest = await freshEncryptedRequest(this.keyStore, relayToken, operation, input);
481
+ encryptedRequest = await freshEncryptedRequest(this.keyStore, relayToken, operation, input, options.deadlineUnixMs);
485
482
  requestId = encryptedRequest.request_id;
486
483
  }
487
484
  try {
package/dist/crypto.d.ts CHANGED
@@ -46,7 +46,7 @@ export interface AuthorityProofInput {
46
46
  /** Sign one remote-authority or token-refresh request with the approved grant key. */
47
47
  export declare function signAuthorityRequest(store: GrantKeyStore, handle: string, expectedPublicKey: string, input: AuthorityProofInput): Promise<Record<string, string>>;
48
48
  export declare function authorityProofMessage(input: Required<Pick<AuthorityProofInput, "method" | "target" | "credential" | "timestamp" | "nonce">> & Pick<AuthorityProofInput, "body">): Promise<string>;
49
- export declare function encryptRelayRequest(store: GrantKeyStore, handle: string, binding: RelayBinding, operation: EncryptedRelayOperation, input: unknown, requestId?: string): Promise<EncryptedRelayOperationRequest>;
49
+ export declare function encryptRelayRequest(store: GrantKeyStore, handle: string, binding: RelayBinding, operation: EncryptedRelayOperation, input: unknown, requestId?: string, deadlineUnixMs?: number): Promise<EncryptedRelayOperationRequest>;
50
50
  export declare function decryptRelayResponse<Result>(store: GrantKeyStore, handle: string, binding: RelayBinding, request: EncryptedRelayOperationRequest, response: EncryptedRelayOperationResponse): Promise<{
51
51
  ok: true;
52
52
  result: Result;
package/dist/crypto.js CHANGED
@@ -157,8 +157,12 @@ export async function authorityProofMessage(input) {
157
157
  input.nonce
158
158
  ].join("\n");
159
159
  }
160
- export async function encryptRelayRequest(store, handle, binding, operation, input, requestId = crypto.randomUUID()) {
160
+ export async function encryptRelayRequest(store, handle, binding, operation, input, requestId = crypto.randomUUID(), deadlineUnixMs) {
161
161
  validateGrantEncryption(binding.encryption);
162
+ if (deadlineUnixMs !== undefined
163
+ && (!Number.isSafeInteger(deadlineUnixMs) || deadlineUnixMs <= 0)) {
164
+ throw new RelayCryptoError("invalid_deadline", "The operation deadline is invalid.");
165
+ }
162
166
  const record = await requireAgreementKey(store, handle, binding.encryption.application_agreement_public_key);
163
167
  const counter = await store.nextCounter(handle);
164
168
  const metadata = {
@@ -179,6 +183,7 @@ export async function encryptRelayRequest(store, handle, binding, operation, inp
179
183
  return {
180
184
  type: "encrypted_operation_request",
181
185
  ...envelopeMetadata(metadata),
186
+ ...(deadlineUnixMs === undefined ? {} : { deadline_unix_ms: deadlineUnixMs }),
182
187
  ciphertext: bytesToBase64Url(new Uint8Array(ciphertext))
183
188
  };
184
189
  }
@@ -1,4 +1,4 @@
1
1
  import type { CollectionOperation, EncryptedRelayOperationRequest } from "@mdbase-dev/connect-protocol";
2
2
  import { type GrantKeyStore } from "./crypto.js";
3
3
  import type { StoredToken } from "./internal-types.js";
4
- export declare function freshEncryptedRequest(keyStore: GrantKeyStore, token: StoredToken, operation: CollectionOperation, input: unknown): Promise<EncryptedRelayOperationRequest>;
4
+ export declare function freshEncryptedRequest(keyStore: GrantKeyStore, token: StoredToken, operation: CollectionOperation, input: unknown, deadlineUnixMs?: number): Promise<EncryptedRelayOperationRequest>;
@@ -1,6 +1,6 @@
1
1
  import { encryptRelayRequest, RelayCryptoError } from "./crypto.js";
2
2
  import { connectError, serverConnectError } from "./errors.js";
3
- export async function freshEncryptedRequest(keyStore, token, operation, input) {
3
+ export async function freshEncryptedRequest(keyStore, token, operation, input, deadlineUnixMs) {
4
4
  if (!token.encryption || !token.grantId || !token.keyHandle) {
5
5
  throw connectError("authorization_binding_incompatible", "The refreshed authorization no longer supports encrypted connector operations.", {
6
6
  details: {
@@ -17,7 +17,7 @@ export async function freshEncryptedRequest(keyStore, token, operation, input) {
17
17
  grantId: token.grantId,
18
18
  applicationId: token.clientId,
19
19
  encryption: token.encryption
20
- }, operation, input);
20
+ }, operation, input, undefined, deadlineUnixMs);
21
21
  }
22
22
  catch (error) {
23
23
  if (error instanceof RelayCryptoError) {
@@ -19,6 +19,11 @@ export interface RequestBudget {
19
19
  remainingMs(): number | null;
20
20
  dispose(): void;
21
21
  }
22
+ export type OperationRequestOptions = ConnectRequestOptions & {
23
+ /** Absolute budget shared with the connector; never supplied directly by callers. */
24
+ deadlineUnixMs?: number;
25
+ };
26
+ export declare function requestOptionsWithinBudget(options: ConnectRequestOptions, budget: RequestBudget): OperationRequestOptions;
22
27
  export declare function createRequestBudget(options?: ConnectRequestOptions, defaultTimeoutMs?: number | null, now?: () => number): RequestBudget;
23
28
  export declare function withRequestBudget<Result>(options: ConnectRequestOptions | undefined, defaultTimeoutMs: number | null, operation: (budget: RequestBudget) => Promise<Result>): Promise<Result>;
24
29
  /**
@@ -13,6 +13,13 @@ export function resolveConnectTimeouts(timeouts = {}) {
13
13
  syncMs: configuredTimeout(timeouts.syncMs, DEFAULT_SYNC_TIMEOUT_MS, "syncMs")
14
14
  };
15
15
  }
16
+ export function requestOptionsWithinBudget(options, budget) {
17
+ return {
18
+ ...options,
19
+ signal: budget.signal,
20
+ ...(budget.deadline === null ? {} : { deadlineUnixMs: budget.deadline })
21
+ };
22
+ }
16
23
  export function createRequestBudget(options = {}, defaultTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, now = Date.now) {
17
24
  const configured = options.timeoutMs === undefined ? defaultTimeoutMs : options.timeoutMs;
18
25
  const timeoutMs = configured === null ? null : validTimeout(configured);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdbase-dev/connect",
3
- "version": "0.1.0-beta.61",
3
+ "version": "0.1.0-beta.62",
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.61"
36
+ "@mdbase-dev/connect-protocol": "0.1.0-beta.62"
37
37
  },
38
38
  "devDependencies": {
39
39
  "esbuild": "^0.28.1",