@mdbase-dev/connect 0.1.0-beta.108 → 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-wpc0R7K2M2u/v8bIQcBgnveqMOK02ergpDhAolUQ2yEXNgyXH7U7pLkhlzyvmKZs
1
+ sha384-/62JQssTDGRQGVQwcO5KLWO7BOqrDVxB0ne307QlEFVO3/MzYMBJfjmbz/OdCRWP
package/dist/connect.js CHANGED
@@ -5,7 +5,6 @@ import { authorizationCallbackState } from "./authorization-url.js";
5
5
  import { randomBase64Url } from "./base64.js";
6
6
  import { ApplicationIdentityStoreError, IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, applicationIdentity, applicationInstallationId, signApplicationAuthorization } from "./application-identity.js";
7
7
  import { MdbaseConnection } from "./connection.js";
8
- import { addConnectionId, connectionIds, removeConnectionId } from "./connection-index.js";
9
8
  import { assertFreshV2AuthorizationSupport, authorizationAbort, declarationIdFromFamilyIdentity } from "./connect-authorization-helpers.js";
10
9
  import { IndexedDbGrantKeyStore, MemoryGrantKeyStore } from "./crypto.js";
11
10
  import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
@@ -13,7 +12,7 @@ import { prepareAuthorizationWindow, publishPopupAuthorizationCompletion, waitFo
13
12
  import { pendingRecoveryTransports, removePendingMutations } from "./pending-recovery-transports.js";
14
13
  import { GrantKeyLeaseRegistry } from "./grant-key-leases.js";
15
14
  import { resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
16
- import { MemoryStorage, apiError, applicationStorageOrigin, canonicalLoopbackUrl, collectionIdFromTokenKey, connectFetch, createPkce, defaultStorage, decodeJsonResponse, isOpaquePortableManifest, resolveManifestSource, oauthErrorCode, parseDeviceAuthorization, parseStored, stripTrailingSlash, validAuthorityTokenResponse } from "./runtime-utils.js";
15
+ import { MemoryStorage, addConnectionId, connectionIds, removeConnectionId, apiError, applicationStorageOrigin, canonicalLoopbackUrl, collectionIdFromTokenKey, connectFetch, createPkce, defaultStorage, decodeJsonResponse, isOpaquePortableManifest, resolveManifestSource, oauthErrorCode, parseDeviceAuthorization, parseStored, stripTrailingSlash, validAuthorityTokenResponse } from "./runtime-utils.js";
17
16
  import { storedTokenFromResponse } from "./token-response.js";
18
17
  import { deleteUnusedGrantKey, planRetiredGrantKeys, storedTokenKeyHandles } from "./retired-grant-keys.js";
19
18
  import { SharedOperationMap } from "./shared-operation.js";
@@ -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,12 @@
1
1
  import type { CollectionOperation, FileCapability, GrantScope, JsonObject, SyncChangesPage, SyncMutation, SyncMutationReceipt, SyncSession, SyncSnapshotPage } from "@mdbase-dev/connect-protocol";
2
2
  import type { ConnectRequestOptions } from "./operation-types.js";
3
+ export interface MdbaseDeviceAuthorization {
4
+ userCode: string;
5
+ verificationUri: string;
6
+ verificationUriComplete: string;
7
+ expiresAt: number;
8
+ intervalSeconds: number;
9
+ }
3
10
  export type MdbaseConnectionRoute = "remote" | "direct" | "relay";
4
11
  export type DirectAccessStatus = "disabled" | "permission_required" | "checking" | "available" | "unavailable" | "denied";
5
12
  export interface MdbaseConnectionInfo {
@@ -8,7 +8,7 @@ import { type Application } from "./internal-types.js";
8
8
  import type { MdbaseNativeNotificationRegistration, MdbaseNativeNotificationRegistrationOptions, MdbaseNotificationRegistration, MdbaseNotificationRegistrationOptions } from "./notifications.js";
9
9
  import type { ChangesInput, CollectionChangesPage, CollectionDescription, CollectionSetupApplyResult, CollectionSetupAssessment, ApplyCollectionSetupInput, ApplyTypePackInput, AssessCollectionSetupInput, AssessTypePackInput, CreateInput, CreateTypeInput, CreateViewSourceInput, DeleteInput, DeletePreflightResult, DeleteProgressOptions, DeleteResult, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ConnectRequestOptions, PendingMutation, QueryAllOptions, QueryInput, QueryPage, QueryPagesOptions, QueryResult, ReadInput, ReadTypeInput, ReadViewSourceInput, RenameInput, RenamePreflightResult, RenameProgressOptions, RenameResult, RecordDocument, SavedViewExecution, SavedViewPage, SavedViewPagesOptions, SavedViewList, SavedViewSourceDocument, TypePackApplyResult, TypePackAssessment, UpdateInput, UpdateTypeInput, UpdateViewSourceInput, MdbaseWatchSubscription, WatchInput } from "./operation-types.js";
10
10
  import { type AuthorizationProblemCode, type CollectionChangesProblemCode, type CollectionDescriptionProblemCode, type CollectionMutationProblemCode, type CollectionQueryProblemCode, type CollectionReadProblemCode, type CollectionTypeProblemCode, type ConnectOutcome, type DirectAccessProblemCode, type NotificationProblemCode, type RegistrationProblemCode } from "./outcomes.js";
11
- import type { MdbaseDeviceAuthorization } from "./authorization-types.js";
11
+ import type { MdbaseDeviceAuthorization } from "./connection-types.js";
12
12
  import { type ResolvedConnectTimeouts } from "./request-budget.js";
13
13
  export type MdbaseAuthorizationTarget = {
14
14
  kind: "choose";
@@ -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
  }
@@ -174,7 +174,11 @@ export function requirePreparedPart(part, transferId, partIndex, offset, content
174
174
  || part.part_index !== partIndex
175
175
  || part.offset !== offset
176
176
  || part.content_length !== contentLength
177
+ || typeof part.method !== "string"
177
178
  || part.method.toUpperCase() !== method
179
+ || !part.headers || typeof part.headers !== "object" || Array.isArray(part.headers)
180
+ || Object.values(part.headers).some((value) => typeof value !== "string")
181
+ || typeof part.expires_at !== "string" || !Number.isFinite(Date.parse(part.expires_at))
178
182
  || url.protocol !== "https:"
179
183
  && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]", "::1"].includes(url.hostname))
180
184
  || url.username
package/dist/files.js CHANGED
@@ -144,21 +144,36 @@ export class MdbaseFileClient {
144
144
  const partCount = session.strategy.kind === "object_put"
145
145
  ? 1
146
146
  : Math.ceil(size / partSize);
147
- const status = await this.request("GET", `transfers/${encodeURIComponent(transferId)}`, undefined, options.signal);
148
- requireTransferStatus(status, session);
149
- if (status.state !== "open" && status.state !== "committed") {
150
- throw connectError("invalid_operation_response", `The authority cannot resume a ${status.state} file transfer.`);
147
+ // Open replays its ORIGINAL receipt, not fresh progress. A still-valid
148
+ // create-only capability can nevertheless be reused safely: PUT either
149
+ // creates the candidate or returns 412, then commit verifies/replays it.
150
+ let bootstrap = session.prepared_upload_part;
151
+ if (bootstrap !== undefined) {
152
+ requirePreparedPart(bootstrap, transferId, 0, 0, size, "PUT");
153
+ if (session.strategy.kind !== "object_put"
154
+ || browserObjectHeaders(bootstrap.headers).get("if-none-match") !== "*") {
155
+ throw connectError("invalid_operation_response", "Upload bootstrap must be a create-only single PUT.");
156
+ }
157
+ if (Date.parse(bootstrap.expires_at) <= Date.now() + 60_000)
158
+ bootstrap = undefined;
159
+ }
160
+ const status = bootstrap ? undefined : await this.request("GET", `transfers/${encodeURIComponent(transferId)}`, undefined, options.signal);
161
+ if (status) {
162
+ requireTransferStatus(status, session);
163
+ if (status.state !== "open" && status.state !== "committed") {
164
+ throw connectError("invalid_operation_response", `The authority cannot resume a ${status.state} file transfer.`);
165
+ }
151
166
  }
152
167
  prepareSource?.(partSize);
153
- if (!framed && status.received.length === partCount) {
168
+ if (!framed && status?.received.length === partCount) {
154
169
  const replay = await this.tryReplayUploadCommit(transferId, session.strategy.kind === "object_multipart" ? status.uploaded_parts : undefined, options.signal);
155
170
  if (replay) {
156
171
  committed = true;
157
172
  return clientFileDescriptor(replay.file);
158
173
  }
159
174
  }
160
- const received = new Set(status.received);
161
- const uploadedParts = new Map(status.uploaded_parts.map((part) => [part.part_number - 1, part]));
175
+ const received = new Set(status?.received ?? []);
176
+ const uploadedParts = new Map((status?.uploaded_parts ?? []).map((part) => [part.part_number - 1, part]));
162
177
  let transferredBytes = [...received].reduce((total, index) => total + chunkLength(size, partSize, index), 0);
163
178
  const parts = await mapConcurrent(partCount, concurrency, async (partIndex) => {
164
179
  const offset = partIndex * partSize;
@@ -171,7 +186,7 @@ export class MdbaseFileClient {
171
186
  return uploadedParts.get(partIndex) ?? null;
172
187
  const uploaded = framed
173
188
  ? await retryChunk(async () => this.framed.uploadChunk(session, partIndex, await uploadBodyBytes(part), options.signal), options.signal).then(() => null)
174
- : await this.uploadPart(transferId, partIndex, offset, part, length, session.strategy.kind === "object_multipart", options.signal);
189
+ : await this.uploadPart(transferId, partIndex, offset, part, length, session.strategy.kind === "object_multipart", options.signal, bootstrap ? { session, part: bootstrap } : undefined);
175
190
  transferredBytes += length;
176
191
  options.onProgress?.({
177
192
  phase: "uploading",
@@ -472,11 +487,24 @@ export class MdbaseFileClient {
472
487
  revision: receipt.revision
473
488
  };
474
489
  }
475
- async uploadPart(transferId, partIndex, offset, body, contentLength, requireEtag, signal) {
490
+ async uploadPart(transferId, partIndex, offset, body, contentLength, requireEtag, signal, bootstrap) {
476
491
  for (let attempt = 1; attempt <= MAX_OBJECT_ATTEMPTS; attempt += 1) {
477
492
  throwIfAborted(signal);
478
493
  try {
479
- const prepared = await this.request("POST", `uploads/${encodeURIComponent(transferId)}/parts`, {
494
+ const reuseBootstrap = attempt === 1 && bootstrap
495
+ && Date.parse(bootstrap.part.expires_at) > Date.now() + 60_000;
496
+ if (bootstrap && !reuseBootstrap) {
497
+ // A slow byte source, clock change, or ambiguous PUT can outlive the
498
+ // capability. Recover through fresh progress, never a stale receipt.
499
+ const status = await this.request("GET", `transfers/${encodeURIComponent(transferId)}`, undefined, signal);
500
+ requireTransferStatus(status, bootstrap.session);
501
+ if (status.state !== "open" && status.state !== "committed") {
502
+ throw connectError("invalid_operation_response", `The authority cannot resume a ${status.state} file transfer.`);
503
+ }
504
+ if (status.received.includes(partIndex))
505
+ return { part_number: partIndex + 1, etag: "" };
506
+ }
507
+ const prepared = reuseBootstrap ? bootstrap.part : await this.request("POST", `uploads/${encodeURIComponent(transferId)}/parts`, {
480
508
  protocol_version: FILE_PROTOCOL_VERSION,
481
509
  type: "prepare_file_upload_part",
482
510
  transfer_id: transferId,
@@ -484,13 +512,21 @@ export class MdbaseFileClient {
484
512
  content_length: contentLength
485
513
  }, signal);
486
514
  requirePreparedPart(prepared, transferId, partIndex, offset, contentLength, "PUT");
515
+ const requestHeaders = browserObjectHeaders(prepared.headers);
487
516
  const response = await fetch(prepared.url, {
488
517
  method: prepared.method,
489
- headers: browserObjectHeaders(prepared.headers),
518
+ headers: requestHeaders,
490
519
  body,
491
520
  redirect: "error",
492
521
  signal
493
522
  });
523
+ // A lost successful create-only PUT can be retried after the object
524
+ // already exists. 412 is not proof of success: commit must still verify
525
+ // the authoritative object's digest/length and publish its receipt.
526
+ const existingCandidate = response.status === 412 && !requireEtag
527
+ && requestHeaders.get("if-none-match") === "*";
528
+ if (existingCandidate)
529
+ return { part_number: partIndex + 1, etag: "" };
494
530
  if (!response.ok)
495
531
  throw new Error(`Object upload failed with HTTP ${response.status}.`);
496
532
  const etag = response.headers.get("etag");
package/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
- export type { MdbaseDeviceAuthorization } from "./authorization-types.js";
2
1
  export { MdbaseApplicationSession, MdbaseMemoryVerificationStore } from "./application-session.js";
3
2
  export type { MdbaseApplicationSessionOptions, MdbaseApplicationVerificationStore, MdbaseDefinitionUpdate, MdbaseCollectionSetupUpdate, MdbaseApplicationSessionSnapshot } from "./application-session.js";
4
3
  export type { MdbaseConnectOptions, MdbaseConnectTimeouts, MdbaseFrontmatter } from "./connect-options.js";
5
4
  export { MdbaseConnect } from "./mdbase-connect.js";
6
5
  export { MdbaseConnection } from "./connection.js";
7
6
  export type { MdbaseAuthorizationTarget, MdbaseAuthorizeOptions, MdbaseConnectionAuthorizeOptions, MdbaseConnectEnvironment, MdbaseAuthorizationResult, MdbaseAuthorizationOutcome } from "./connection.js";
8
- export type { MdbaseConnectionRoute, DirectAccessStatus, MdbaseConnectionInfo, MdbaseAuthorizationCapabilities, MdbaseSyncTransport, MdbaseSyncConnection } from "./connection-types.js";
7
+ export type { MdbaseConnectionRoute, DirectAccessStatus, MdbaseConnectionInfo, MdbaseDeviceAuthorization, MdbaseAuthorizationCapabilities, MdbaseSyncTransport, MdbaseSyncConnection } from "./connection-types.js";
9
8
  export { effectiveCapabilities } from "./capabilities.js";
10
9
  export type { MdbaseStructuredReadiness } from "./structured-readiness.js";
11
10
  export type { MdbaseCapabilityState, MdbaseCapabilityEvidence, MdbaseEffectiveCapability, MdbaseEffectiveCapabilities } from "./capabilities.js";
@@ -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);
@@ -2,7 +2,10 @@ import type { FileCapability, GrantEncryption, GrantScope } from "@mdbase-dev/co
2
2
  import type { MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
3
3
  import { MdbaseConnectError } from "./errors.js";
4
4
  import type { StoredToken } from "./internal-types.js";
5
- import type { MdbaseDeviceAuthorization } from "./authorization-types.js";
5
+ import type { MdbaseDeviceAuthorization } from "./connection-types.js";
6
+ export declare function connectionIds(storage: Storage, storagePrefix: string): string[];
7
+ export declare function addConnectionId(storage: Storage, storagePrefix: string, collectionId: string): void;
8
+ export declare function removeConnectionId(storage: Storage, storagePrefix: string, collectionId: string): void;
6
9
  /** Resolve bundled declarations and their stable storage identity without loading URLs. */
7
10
  export declare function resolveManifestSource(input?: MdbaseAppManifest | string | URL, redirect?: string | URL): {
8
11
  manifest: MdbaseAppManifest | string;
@@ -2,6 +2,24 @@ import { validateGrantEncryption } from "./crypto.js";
2
2
  import { connectError, serverConnectError } from "./errors.js";
3
3
  import { canonicalJson } from "./operation-helpers.js";
4
4
  import { bytesToBase64Url, randomBase64Url } from "./base64.js";
5
+ export function connectionIds(storage, storagePrefix) {
6
+ const index = parseStored(storage.getItem(`${storagePrefix}:connections`));
7
+ return index?.version === 1 && Array.isArray(index.collectionIds)
8
+ && index.collectionIds.every((collectionId) => typeof collectionId === "string")
9
+ ? index.collectionIds
10
+ : [];
11
+ }
12
+ export function addConnectionId(storage, storagePrefix, collectionId) {
13
+ writeConnectionIds(storage, storagePrefix, [...connectionIds(storage, storagePrefix), collectionId]);
14
+ }
15
+ export function removeConnectionId(storage, storagePrefix, collectionId) {
16
+ writeConnectionIds(storage, storagePrefix, connectionIds(storage, storagePrefix).filter((id) => id !== collectionId));
17
+ }
18
+ function writeConnectionIds(storage, storagePrefix, ids) {
19
+ storage.setItem(`${storagePrefix}:connections`, JSON.stringify({
20
+ version: 1, collectionIds: [...new Set(ids)]
21
+ }));
22
+ }
5
23
  /** Resolve bundled declarations and their stable storage identity without loading URLs. */
6
24
  export function resolveManifestSource(input, redirect) {
7
25
  const manifest = input instanceof URL ? input.href : input ?? defaultManifestSource();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdbase-dev/connect",
3
- "version": "0.1.0-beta.108",
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.108"
36
+ "@mdbase-dev/connect-protocol": "0.1.0-beta.111"
37
37
  },
38
38
  "devDependencies": {
39
39
  "esbuild": "^0.28.1",
@@ -1,7 +0,0 @@
1
- export interface MdbaseDeviceAuthorization {
2
- userCode: string;
3
- verificationUri: string;
4
- verificationUriComplete: string;
5
- expiresAt: number;
6
- intervalSeconds: number;
7
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,3 +0,0 @@
1
- export declare function connectionIds(storage: Storage, storagePrefix: string): string[];
2
- export declare function addConnectionId(storage: Storage, storagePrefix: string, collectionId: string): void;
3
- export declare function removeConnectionId(storage: Storage, storagePrefix: string, collectionId: string): void;
@@ -1,20 +0,0 @@
1
- import { parseStored } from "./runtime-utils.js";
2
- export function connectionIds(storage, storagePrefix) {
3
- const index = parseStored(storage.getItem(`${storagePrefix}:connections`));
4
- return index?.version === 1 && Array.isArray(index.collectionIds)
5
- && index.collectionIds.every((collectionId) => typeof collectionId === "string")
6
- ? index.collectionIds
7
- : [];
8
- }
9
- export function addConnectionId(storage, storagePrefix, collectionId) {
10
- writeConnectionIds(storage, storagePrefix, [...connectionIds(storage, storagePrefix), collectionId]);
11
- }
12
- export function removeConnectionId(storage, storagePrefix, collectionId) {
13
- writeConnectionIds(storage, storagePrefix, connectionIds(storage, storagePrefix).filter((id) => id !== collectionId));
14
- }
15
- function writeConnectionIds(storage, storagePrefix, ids) {
16
- storage.setItem(`${storagePrefix}:connections`, JSON.stringify({
17
- version: 1,
18
- collectionIds: [...new Set(ids)]
19
- }));
20
- }