From a1fd1bb1d7df810f31759e7f368dba8e5ed6e2e3 Mon Sep 17 00:00:00 2001 From: callumalpass Date: Sun, 26 Jul 2026 08:48:35 +1000 Subject: [PATCH] Update Connect portable app documentation --- .github/workflows/ci.yml | 2 +- .github/workflows/deploy.yml | 2 +- .../schemas/connect-protocol.v1.schema.json | 9 +- public/connect/schemas/mdbase-app.schema.json | 26 ++- public/connect/schemas/mdbase-app.v1.json | 26 ++- site-sources.json | 2 +- src/components/DocsLayout.astro | 1 + src/pages/connect/index.astro | 10 +- src/pages/sdk/api/index.astro | 35 +++- src/pages/sdk/authorization/index.astro | 31 +++ src/pages/sdk/contracts/index.astro | 63 ++---- src/pages/sdk/index.astro | 23 ++- src/pages/sdk/manifest/index.astro | 81 +++++--- src/pages/sdk/notifications/index.astro | 2 +- src/pages/sdk/offline-sync/index.astro | 21 +- src/pages/sdk/portable-apps/index.astro | 184 ++++++++++++++++++ src/pages/sdk/quickstart/index.astro | 14 +- src/pages/sdk/routing/index.astro | 19 +- src/pages/sdk/security/index.astro | 28 ++- src/pages/sdk/testing/index.astro | 7 + 20 files changed, 482 insertions(+), 104 deletions(-) create mode 100644 src/pages/sdk/portable-apps/index.astro diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2645779..403340f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v7 with: repository: mdbase-dev/mdbase-connect - ref: fec3e222cebb8f1711d3e6eb111ab8f225680006 + ref: e6b8c7d552c74ad6c4163733ae3337302c97e434 path: .sources/mdbase-connect - name: Check out Rust implementation diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 45558b8..5e03015 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@v7 with: repository: mdbase-dev/mdbase-connect - ref: fec3e222cebb8f1711d3e6eb111ab8f225680006 + ref: e6b8c7d552c74ad6c4163733ae3337302c97e434 path: .sources/mdbase-connect - name: Check out Rust implementation diff --git a/public/connect/schemas/connect-protocol.v1.schema.json b/public/connect/schemas/connect-protocol.v1.schema.json index 9db34bd..49560f2 100644 --- a/public/connect/schemas/connect-protocol.v1.schema.json +++ b/public/connect/schemas/connect-protocol.v1.schema.json @@ -125,8 +125,15 @@ "operations": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/operation" } }, "scope": { "$ref": "#/$defs/grantScope" }, "application_name": { "type": "string", "minLength": 1 }, + "application_distribution": { "enum": ["web", "portable"] }, "application_homepage": { "type": "string" }, - "application_origin": { "type": "string", "format": "uri" }, + "application_project_url": { "type": "string", "format": "uri" }, + "application_origin": { + "anyOf": [ + { "const": "null" }, + { "type": "string", "format": "uri" } + ] + }, "application_icon": { "type": "string" }, "collection_name": { "type": "string", "minLength": 1 }, "notification_criteria": { diff --git a/public/connect/schemas/mdbase-app.schema.json b/public/connect/schemas/mdbase-app.schema.json index 359fd78..ae7df73 100644 --- a/public/connect/schemas/mdbase-app.schema.json +++ b/public/connect/schemas/mdbase-app.schema.json @@ -4,9 +4,13 @@ "title": "mdbase connect bundled application manifest v1", "type": "object", "additionalProperties": false, - "required": ["manifest_version", "id", "name", "homepage", "redirect_uris"], + "required": ["manifest_version", "id", "name"], "properties": { "manifest_version": { "const": 1 }, + "distribution": { + "enum": ["web", "portable"], + "description": "Portable declarations use device authorization and do not claim a browser origin." + }, "id": { "type": "string", "minLength": 5, @@ -16,6 +20,7 @@ }, "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "homepage": { "type": "string", "format": "uri", "pattern": "^https://" }, + "project_url": { "type": "string", "format": "uri", "pattern": "^https://" }, "icon": { "type": "string", "format": "uri", "pattern": "^https://" }, "redirect_uris": { "type": "array", @@ -55,6 +60,25 @@ } } }, + "oneOf": [ + { + "required": ["homepage", "redirect_uris"], + "properties": { + "distribution": { "const": "web" }, + "homepage": true, + "redirect_uris": true, + "project_url": false + } + }, + { + "required": ["distribution"], + "properties": { + "distribution": { "const": "portable" }, + "homepage": false, + "redirect_uris": false + } + } + ], "$defs": { "identifier": { "type": "string", diff --git a/public/connect/schemas/mdbase-app.v1.json b/public/connect/schemas/mdbase-app.v1.json index 359fd78..ae7df73 100644 --- a/public/connect/schemas/mdbase-app.v1.json +++ b/public/connect/schemas/mdbase-app.v1.json @@ -4,9 +4,13 @@ "title": "mdbase connect bundled application manifest v1", "type": "object", "additionalProperties": false, - "required": ["manifest_version", "id", "name", "homepage", "redirect_uris"], + "required": ["manifest_version", "id", "name"], "properties": { "manifest_version": { "const": 1 }, + "distribution": { + "enum": ["web", "portable"], + "description": "Portable declarations use device authorization and do not claim a browser origin." + }, "id": { "type": "string", "minLength": 5, @@ -16,6 +20,7 @@ }, "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "homepage": { "type": "string", "format": "uri", "pattern": "^https://" }, + "project_url": { "type": "string", "format": "uri", "pattern": "^https://" }, "icon": { "type": "string", "format": "uri", "pattern": "^https://" }, "redirect_uris": { "type": "array", @@ -55,6 +60,25 @@ } } }, + "oneOf": [ + { + "required": ["homepage", "redirect_uris"], + "properties": { + "distribution": { "const": "web" }, + "homepage": true, + "redirect_uris": true, + "project_url": false + } + }, + { + "required": ["distribution"], + "properties": { + "distribution": { "const": "portable" }, + "homepage": false, + "redirect_uris": false + } + } + ], "$defs": { "identifier": { "type": "string", diff --git a/site-sources.json b/site-sources.json index 77134fc..796480c 100644 --- a/site-sources.json +++ b/site-sources.json @@ -5,7 +5,7 @@ }, "connect": { "repository": "mdbase-dev/mdbase-connect", - "ref": "fec3e222cebb8f1711d3e6eb111ab8f225680006" + "ref": "e6b8c7d552c74ad6c4163733ae3337302c97e434" }, "implementations": [ { diff --git a/src/components/DocsLayout.astro b/src/components/DocsLayout.astro index a692658..d7e0256 100644 --- a/src/components/DocsLayout.astro +++ b/src/components/DocsLayout.astro @@ -12,6 +12,7 @@ const { title, description, active, headings = [] } = Astro.props; const docs = [ { href: "/sdk/", label: "Overview", key: "overview" }, { href: "/sdk/quickstart/", label: "Quickstart", key: "quickstart" }, + { href: "/sdk/portable-apps/", label: "Portable HTML apps", key: "portable-apps" }, { href: "/sdk/manifest/", label: "Application manifest", key: "manifest" }, { href: "/sdk/contracts/", label: "Contracts and adapters", key: "contracts" }, { href: "/sdk/authorization/", label: "Authorization", key: "authorization" }, diff --git a/src/pages/connect/index.astro b/src/pages/connect/index.astro index cdd5d14..07e2240 100644 --- a/src/pages/connect/index.astro +++ b/src/pages/connect/index.astro @@ -4,7 +4,7 @@ import BaseLayout from "../../layouts/BaseLayout.astro"; const steps = [ { title: "The application declares what it understands", - copy: "Its bundled manifest names callback URLs, required domain contracts, collection kind, and any type definitions that can be installed safely." + copy: "Its bundled v1 manifest declares web callbacks or portable distribution, required domain contracts, collection kind, and any type definitions that can be installed safely." }, { title: "Connect checks collection compatibility", @@ -40,6 +40,7 @@ const steps = [

SDK quickstart + Portable HTML apps View source
@@ -151,9 +152,10 @@ const steps = [

The private-beta stack includes local and hosted authorities, encrypted relay operations, grant narrowing and revocation, sync, notifications, - and the browser SDK. Public registration, a production restore drill, - signed desktop releases, and abuse-response operations are still required - before public registration. + the browser SDK, and key-bound authorization for downloaded HTML files. + The npm packages have not yet received their first public release. Public + registration, a production restore drill, signed desktop releases, and + abuse-response operations are still required before public registration.

Read the SDK documentation diff --git a/src/pages/sdk/api/index.astro b/src/pages/sdk/api/index.astro index d0cf7b3..6bb42ef 100644 --- a/src/pages/sdk/api/index.astro +++ b/src/pages/sdk/api/index.astro @@ -31,6 +31,7 @@ const error = `try { active="api" headings={[ { href: "#constructor", label: "Constructor" }, + { href: "#environment", label: "Environment" }, { href: "#authorization", label: "Authorization methods" }, { href: "#operations", label: "Operation methods" }, { href: "#notifications", label: "Notifications and timers" }, @@ -62,13 +63,13 @@ const error = `try { redirectUri - Current page where supported - Exact callback listed in the manifest + Current page for web; unused for portable files + Exact callback listed in a web or native manifest storage - localStorage - Authorization persistence boundary + localStorage for web; memory for opaque portable files + Authorization credential boundary relayEncryption @@ -77,7 +78,7 @@ const error = `try { keyStore - IndexedDB non-extractable key store + IndexedDB for web; memory for opaque portable files Override encrypted grant key persistence @@ -98,6 +99,15 @@ const error = `try { +

Runtime environment

+

+ environment() returns the effective + {" "}distribution, applicationOrigin, and + {" "}credentialStorage. Portable files report origin + {" "}null and default to memory. A caller-supplied + storage or key adapter reports custom. +

+

Registration and authorization

@@ -115,13 +125,13 @@ const error = `try { - - + + - + @@ -145,6 +155,15 @@ const error = `try {
authorize(options?)Does not returnStart PKCE; options include operations, collectionId hint, and returnToPromise<MdbaseAuthorizationResult>Navigate for web/native, or resolve after short-code approval for portable files
completeAuthorization(callbackUrl?) MdbaseAuthorizationResultReturn the bound connection and app-local returnTo valueComplete a web or native callback and return its bound connection
connections()
+

Portable authorization options

+

+ authorize() accepts onDeviceCode to report + {" "}userCode, verificationUri, + {" "}verificationUriComplete, expiresAt, and + {" "}intervalSeconds. Supply openVerification to own + the approval window, or an AbortSignal to cancel polling and + discard the unapproved key. +

Collection operations

diff --git a/src/pages/sdk/authorization/index.astro b/src/pages/sdk/authorization/index.astro index f3f7cdf..485fb35 100644 --- a/src/pages/sdk/authorization/index.astro +++ b/src/pages/sdk/authorization/index.astro @@ -44,6 +44,16 @@ const recover = `try { } throw error; }`; + +const portableAuthorize = `const controller = new AbortController(); + +const { connection } = await connect.authorize({ + operations: ["describe", "read", "query"], + onDeviceCode: ({ userCode, verificationUriComplete }) => { + renderApprovalCode(userCode, verificationUriComplete); + }, + signal: controller.signal +});`; --- MdbaseConnection bound to one collection ID.

+

Use the flow selected by the manifest

+

+ Web and native-shell manifests use an authorization-code redirect with + PKCE. authorize() navigates away, and the callback route calls + {" "}completeAuthorization(). +

+

+ A distribution: "portable" manifest uses short-code device + authorization with PKCE and a P-256 application key. + authorize() opens the approval page, polls at the server + interval, and resolves with the collection-bound connection after approval. +

+ +

+ The abort signal stops polling and discards the unapproved in-memory key. + Use openVerification when a shell owns the approval window. + See the portable HTML application guide + for the complete downloaded-file boundary. +

+

Choose operations by feature

diff --git a/src/pages/sdk/contracts/index.astro b/src/pages/sdk/contracts/index.astro index 3c9ff2a..9061639 100644 --- a/src/pages/sdk/contracts/index.astro +++ b/src/pages/sdk/contracts/index.astro @@ -4,41 +4,25 @@ import CodeBlock from "../../../components/CodeBlock.astro"; const generic = `const description = await connection.describe(); -const taskContract = description.contracts.find( +const workItemContract = description.contracts.find( (contract) => - contract.id === "tasknotes.task" && + contract.id === "example.work-item" && contract.version === 1 ); -if (!taskContract) { - throw new Error("The approved collection lacks tasknotes.task@1"); +if (!workItemContract) { + throw new Error("The approved collection lacks example.work-item@1"); } -const taskType = taskContract.type_name; -const fieldRoles = taskContract.configuration.field_roles;`; - -const adapter = `import { - TasknotesCollection, - type TaskFrontmatter -} from "@mdbase/tasknotes"; -import { MdbaseConnect } from "@mdbase/connect"; - -const mdbase = new MdbaseConnect(options); -const connection = mdbase.connection(collectionId); -if (!connection) throw new Error("Choose a collection."); -const tasks = new TasknotesCollection(connection); - -await tasks.describe(); -const open = await tasks.list(); -const created = await tasks.create({ title: "Prepare release" }); -await tasks.setCompleted(created.path, true);`; +const workItemType = workItemContract.type_name; +const fieldRoles = workItemContract.configuration.field_roles;`; const domainFunction = `import type { MdbaseCollectionClient } from "@mdbase/connect"; -export class WorkoutCollection { +export class WorklogCollection { constructor( - private readonly client: MdbaseCollectionClient, - private readonly contract: WorkoutContract + private readonly client: MdbaseCollectionClient, + private readonly contract: WorkItemContract ) {} list() { @@ -55,8 +39,8 @@ export class WorkoutCollection { active="contracts" headings={[ { href: "#contract", label: "Read a contract" }, - { href: "#tasknotes", label: "TaskNotes adapter" }, { href: "#own-adapter", label: "Your own adapter" }, + { href: "#ownership", label: "Adapter ownership" }, { href: "#scope", label: "Contract scope" } ]} > @@ -76,24 +60,8 @@ export class WorkoutCollection { Connect only offers a ready or safely provisionable collection.

-

Use the TaskNotes adapter

-

- @mdbase/tasknotes resolves{" "} - tasknotes.task@1, follows its field-role mapping, and performs - revision-safe generic operations. The field-role mapping supplies the title - property and completed-state representation for each collection. -

- -
- TaskNotes supplies the tasknotes.task application contract. -

- Hosted and local collections remain generic mdbase collections. TaskNotes - is one optional provider and consumer of a declared contract. -

-
-

Keep your adapter on the transport-neutral client

- +

Domain adapters that use collection operations should accept{" "} MdbaseCollectionClient. This makes the adapter usable with @@ -101,6 +69,15 @@ export class WorkoutCollection { providers.

+

Keep domain behavior with its application

+

+ Connect publishes application-neutral authorization, transport, schemas, + and operation clients. A domain application owns its contract vocabulary, + model, and adapter package. This keeps application conventions out of the + Connect protocol and lets more than one app interpret the same generic + collection independently. +

+

Contract scope and full collection access

Contract-scoped grants confine record paths and operations to types that diff --git a/src/pages/sdk/index.astro b/src/pages/sdk/index.astro index 9ade993..47f51a4 100644 --- a/src/pages/sdk/index.astro +++ b/src/pages/sdk/index.astro @@ -33,6 +33,15 @@ export async function listOpenTasks(client: MdbaseCollectionClient) {

+
+ npm publication is pending. +

+ This is the public-beta install command. During private development, build + the packages from the + {" "}Connect repository. + The production service and v1 protocol are already deployed. +

+

SDK surfaces

@@ -46,7 +55,7 @@ export async function listOpenTasks(client: MdbaseCollectionClient) { - + @@ -82,6 +91,12 @@ export async function listOpenTasks(client: MdbaseCollectionClient) { against MdbaseCollectionClient can also run against the developer sandbox or another conforming transport.

+

+ A downloaded HTML file supplies + {" "}distribution: "portable" in its inline manifest. The same + manager then uses short-code approval and session-only credentials. See the + {" "}portable HTML application guide. +

The operation envelope is part of the contract. @@ -122,15 +137,11 @@ export async function listOpenTasks(client: MdbaseCollectionClient) {
- - - -
MdbaseConnectApplication-level manager for saved connections and authorizationApplication-level manager for web, native-shell, and portable-file authorization Browser APIs or adapters supplied by a native shell
@mdbase/connect-webhooks Server-side verification for signed notification webhooks
@mdbase/tasknotesContract-aware TaskNotes operations over generic collection clients
diff --git a/src/pages/sdk/manifest/index.astro b/src/pages/sdk/manifest/index.astro index 51a3fb2..31142af 100644 --- a/src/pages/sdk/manifest/index.astro +++ b/src/pages/sdk/manifest/index.astro @@ -4,24 +4,24 @@ import CodeBlock from "../../../components/CodeBlock.astro"; const contractManifest = `{ "manifest_version": 1, - "id": "dev.mdbase.tasknotes", - "name": "TaskNotes", - "homepage": "https://tasks.example", + "id": "dev.example.worklog", + "name": "Worklog", + "homepage": "https://worklog.example", "redirect_uris": [ - "https://tasks.example/auth/mdbase/callback" + "https://worklog.example/auth/mdbase/callback" ], "requirements": { "contracts": [ - { "id": "tasknotes.task", "version": 1 } + { "id": "example.work-item", "version": 1 } ] }, "provisions": { "types": [ { - "name": "Task", - "document": "---\\nkind: mdbase.type\\nname: task\\nversion: 1\\nschema:\\n dialect: json-schema-2020-12\\n value:\\n type: object\\nx-tasknotes:\\n contract: tasknotes.task\\n version: 1\\n---\\n", + "name": "Work item", + "document": "---\\nkind: mdbase.type\\nname: work-item\\nversion: 1\\nschema:\\n dialect: json-schema-2020-12\\n value:\\n type: object\\nx-worklog:\\n contract: example.work-item\\n version: 1\\n---\\n", "provides": [ - { "id": "tasknotes.task", "version": 1 } + { "id": "example.work-item", "version": 1 } ] } ] @@ -39,6 +39,18 @@ const nativeManifest = `{ ], "requirements": { "contracts": [] } }`; + +const portableManifest = `{ + "manifest_version": 1, + "distribution": "portable", + "id": "dev.example.portable-worklog", + "name": "Portable worklog", + "project_url": "https://worklog.example/source", + "requirements": { + "access": "full_collection", + "contracts": [] + } +}`; --- mdbase-app.v1.json

-

Identity and callbacks

+

Shared identity fields

@@ -73,28 +87,47 @@ const nativeManifest = `{ - - - - - - + + - - + + - - + + - - + +
idStable lowercase reverse-domain identifier with at least three labels
nameHuman-readable name shown during authorizationmanifest_version1 throughout the current Connect pre-release
homepageHTTPS application origindistributionweb when omitted, or explicit portable
redirect_urisHTTPS URIs on the homepage origin, plus matching native schemes when neededidStable lowercase reverse-domain identifier with at least three labels
iconOptional HTTPS icon on the homepage originnameHuman-readable name shown during authorization
+

Web and native-shell manifests

+

+ Existing v1 manifests may omit distribution; that means + {" "}web. They declare an HTTPS homepage and at least + one redirect_uris entry on the same origin. An optional icon stays on + the homepage origin. +

+ +

Downloaded HTML manifests

+

+ A downloaded file declares distribution: "portable" and omits + {" "}homepage and redirect_uris. Its optional + {" "}project_url is HTTPS presentation metadata, not publisher + verification. An optional icon must share that project URL's origin. +

+ +

+ Portable manifests use key-bound short-code authorization, accept local + collections only, and keep their credentials in memory when opened from + {" "}file://. See the + {" "}portable HTML application guide. +

+

Collection requirements

Requirements identify compatible collections. The user grants access in a @@ -122,7 +155,7 @@ const nativeManifest = `{ collection_kind: "hosted" - Offer only provider-backed collections and enable the hosted sync handoff + Offer only provider-backed collections; unavailable to portable files @@ -142,7 +175,7 @@ const nativeManifest = `{ General type administration requires a separate{" "} create_type grant.

- +

A provision may only claim contracts listed in{" "} requirements.contracts. Auxiliary types installed with the diff --git a/src/pages/sdk/notifications/index.astro b/src/pages/sdk/notifications/index.astro index 7d87892..95389f6 100644 --- a/src/pages/sdk/notifications/index.astro +++ b/src/pages/sdk/notifications/index.astro @@ -17,7 +17,7 @@ const criterion = `{ "minimum_interval": "1m", "presentation": { "title": "Tasks changed", - "body": "Open TaskNotes to see the latest changes.", + "body": "Open Worklog to see the latest changes.", "tag": "task-changes" } }] diff --git a/src/pages/sdk/offline-sync/index.astro b/src/pages/sdk/offline-sync/index.astro index 68c6772..0dde310 100644 --- a/src/pages/sdk/offline-sync/index.astro +++ b/src/pages/sdk/offline-sync/index.astro @@ -49,6 +49,8 @@ const conflict = `for (const { recordId, receipt } of await replica.conflictEntr } await replica.sync();`; + +const promote = `mdbase-mirror promote ./worklog-mirror`; ---

Require a hosted collection

@@ -107,4 +110,20 @@ await replica.sync();`; directory it compares the complete remote snapshot before writing or uploading. Differing paths stop for explicit review.

+ +

Move hosted authority to a complete local mirror

+ +

+ Promotion starts from a converged full writable mirror and opens a + short-lived approval in Connect. Approval freezes hosted writes at a final + sequence. The CLI pulls through that sequence, proves the exact record and + resource manifest, registers the folder with the local connector, and then + advances the collection authority epoch. +

+

+ Old hosted grants, provider capabilities, and replicas are revoked at + cutover. Applications authorize the new local authority explicitly. + Cancellation or expiry before completion restores hosted writes. The + command is resumable after local materialization. +

diff --git a/src/pages/sdk/portable-apps/index.astro b/src/pages/sdk/portable-apps/index.astro new file mode 100644 index 0000000..dd73d9d --- /dev/null +++ b/src/pages/sdk/portable-apps/index.astro @@ -0,0 +1,184 @@ +--- +import DocsLayout from "../../../components/DocsLayout.astro"; +import CodeBlock from "../../../components/CodeBlock.astro"; + +const manifest = `{ + "manifest_version": 1, + "distribution": "portable", + "id": "dev.example.portable-notes", + "name": "Portable notes", + "project_url": "https://example.dev/portable-notes", + "requirements": { + "access": "full_collection", + "contracts": [] + } +}`; + +const browserBundle = ``; + +const application = ` + + +`; +--- + + +

Release status

+
+ Implemented in Connect v1; npm publication is pending. +

+ The production service accepts the portable manifest and device flow. + The version-pinned CDN URL below becomes available when + {" "}@mdbase/connect and + {" "}@mdbase/connect-protocol are published. Until then, build + the browser artifact from the Connect repository for development. +

+
+

+ Connect remains pre-release, so the manifest and encrypted wire protocol + stay labelled v1. A later public release can establish a new compatibility + boundary when one is needed. +

+ +

Declare a portable application inline

+ +

+ distribution: "portable" replaces homepage and + {" "}redirect_uris. The optional project_url gives the + user somewhere to inspect the project or source. It must use HTTPS, but it + does not verify the publisher or prove where the downloaded file came from. +

+

+ Connect identifies the normalized manifest by its SHA-256 digest. Changing + any declared field creates a different application identity and requires a + new approval. Portable applications currently authorize computer-owned + collections only. A manifest that requests + {" "}collection_kind: "hosted" is rejected. +

+ +

Authorize with a short code and installation key

+
    +
  1. The file registers its inline manifest and generates a P-256 relay key.
  2. +
  3. Connect returns a short-lived device code and an eight-character user code.
  4. +
  5. The SDK opens the Connect approval page in a popup and reports the code through onDeviceCode.
  6. +
  7. The signed-in user confirms the code, reviews the downloaded-file warning, chooses a local collection, and narrows the operations.
  8. +
  9. The SDK polls at the server-provided interval and accepts only the grant bound to its application key.
  10. +
+

+ Device codes expire after ten minutes, are stored as hashes by the control + plane, and can be consumed once. PKCE prevents another installation from + exchanging a copied code. Polling faster than the returned interval + produces slow_down. +

+

+ Start authorize() from a user action so the browser permits + the popup. Supply openVerification when an embedding shell + owns the approval window. If the default popup is blocked, the SDK throws + {" "}approval_window_blocked with the verification URL and code. +

+ +

Treat file:// as an opaque, session-only origin

+

+ Browsers serialize local-file origins as null. For a portable + manifest opened from file://, the SDK stores the authorization + token and non-extractable private key in process memory. Another downloaded + file cannot inherit them through localStorage or IndexedDB. + Reloading or reopening the file requires authorization again. +

+

+ An embedding shell can inject custom storage and + {" "}keyStore adapters as an explicit trust decision. + {" "}connect.environment() reports the active distribution, + application origin, and whether credential storage is + {" "}memory, persistent, or custom. +

+ +

Pin the browser bundle and its integrity hash

+

+ The npm package contains one dependency-free IIFE bundle plus its generated + SHA-384 metadata: +

+ +

+ Use an exact package version and copy the integrity value from that same + version's integrity.json. The browser global is + {" "}MdbaseConnect. +

+ +

+ Do not use an unversioned CDN URL, a moving tag, or a script without + Subresource Integrity in a downloaded application. +

+ +

Connect from one HTML file

+ + +

Keep the application page inside the grant boundary

+

+ The local connector is the final authorization boundary. It accepts + {" "}Origin: null only for an active portable grant with the exact + encrypted relay binding. Every operation authenticates the grant, + application, connector, collection, key ID, epoch, counter, request ID, and + ciphertext. CORS permission alone does not authorize an operation. +

+

+ Code already running inside an approved page can use that page's + authorization. Keep untrusted scripts out of the file, pin every external + resource, and request only the operations used by the current feature. +

+ + +
diff --git a/src/pages/sdk/quickstart/index.astro b/src/pages/sdk/quickstart/index.astro index 9403c34..1e6ac2b 100644 --- a/src/pages/sdk/quickstart/index.astro +++ b/src/pages/sdk/quickstart/index.astro @@ -119,6 +119,16 @@ const validate = `mdbase-connect-dev validate-manifest \\

2. Install the SDK and validator

+
+ The packages are awaiting their first npm publication. +

+ The commands above are the public-beta installation path. Current + development builds @mdbase/connect, + {" "}@mdbase/connect-protocol, and + {" "}@mdbase/connect-dev from the + {" "}Connect repository. +

+

Validate the bundled file during development and CI:

@@ -174,12 +184,12 @@ const validate = `mdbase-connect-dev validate-manifest \\

Generic apps can use declared mdbase types directly. Domain applications should read the contract descriptors returned by describe(){" "} - or use a contract adapter such as @mdbase/tasknotes. + or use an adapter maintained by the owning application.

Design the manifest - Add tests + Build one downloaded file
diff --git a/src/pages/sdk/routing/index.astro b/src/pages/sdk/routing/index.astro index a1479f5..7484b58 100644 --- a/src/pages/sdk/routing/index.astro +++ b/src/pages/sdk/routing/index.astro @@ -33,6 +33,7 @@ const options = `const mdbase = new MdbaseConnect({ { href: "#direct", label: "Direct access" }, { href: "#relay", label: "Encrypted relay" }, { href: "#hosted", label: "Hosted provider" }, + { href: "#portable", label: "Portable files" }, { href: "#native", label: "Native shells" } ]} > @@ -57,8 +58,9 @@ const options = `const mdbase = new MdbaseConnect({ Browser local-network permission must be requested from a user action. Once available, grantable operations prefer{" "} http://127.0.0.1:28485/v1/operations. The connector checks the - exact origin, Host header, protocol version, grant, and application-key - proof. + exact approved web origin, or the exact opaque + {" "}null origin for a portable grant. It also checks the Host + header, protocol version, grant, and application-key proof.

Set directAccess: "disabled" for embedding environments that @@ -68,8 +70,9 @@ const options = `const mdbase = new MdbaseConnect({

Encrypted relay fallback

Protocol 1 uses a non-extractable P-256 key and atomic message counter in - IndexedDB. The SDK encrypts operation inputs for the local connector and - decrypts results in the application. The control plane receives opaque + IndexedDB for a web application. An opaque portable file keeps both in + process memory. The SDK encrypts operation inputs for the local connector + and decrypts results in the application. The control plane receives opaque bodies plus operation and delivery metadata.

@@ -92,6 +95,14 @@ const options = `const mdbase = new MdbaseConnect({ a trusted data processor.

+

Portable files use local authority only

+

+ A downloaded file uses the same direct and encrypted-relay routes after its + short-code approval. Portable manifests cannot request a hosted collection. + This restriction keeps hosted renewal capabilities out of an opaque browser + origin until they have an equivalent sender constraint. +

+

Native shells

Supply navigate to open authorization in the system browser, diff --git a/src/pages/sdk/security/index.astro b/src/pages/sdk/security/index.astro index d002f58..40ab4e6 100644 --- a/src/pages/sdk/security/index.astro +++ b/src/pages/sdk/security/index.astro @@ -10,6 +10,7 @@ import DocsLayout from "../../../components/DocsLayout.astro"; { href: "#least-privilege", label: "Least privilege" }, { href: "#credentials", label: "Credentials" }, { href: "#origins", label: "Origins and callbacks" }, + { href: "#portable", label: "Downloaded files" }, { href: "#payloads", label: "Payload boundaries" }, { href: "#checklist", label: "Release checklist" } ]} @@ -24,11 +25,12 @@ import DocsLayout from "../../../components/DocsLayout.astro";

Choose a storage boundary deliberately

- Browser defaults retain authorization in localStorage and - relay keys as non-extractable IndexedDB keys. Applications with a stronger - isolation boundary can supply a custom Storage and{" "} - GrantKeyStore. Native shells should use platform-protected - application storage where possible. + Web-browser defaults retain authorization in localStorage and + relay keys as non-extractable IndexedDB keys. A portable manifest opened + from file:// uses process-memory token and key storage instead. + Applications with another isolation boundary can supply a custom + {" "}Storage and GrantKeyStore. Native shells should + use platform-protected application storage where possible.

Store Connect refresh credentials, hosted provider capabilities, and mirror @@ -42,8 +44,22 @@ import DocsLayout from "../../../components/DocsLayout.astro";

  • Native callback schemes match or extend the reverse-domain application ID.
  • PKCE remains mandatory for browser and native authorization.
  • The loopback connector validates the exact approved browser origin.
  • +
  • Portable files declare no web origin and use a key-bound short-code flow.
  • +

    Treat downloaded application code as authorized code

    +

    + Every local file has the opaque browser origin null. Connect + does not trust that origin by itself. The connector accepts it only when + the request matches an active portable grant, the P-256 application key, + and the encrypted relay envelope. +

    +

    + Code already running in an approved page has that page's access. Keep + untrusted scripts out of the file, pin every CDN dependency to an exact + version with Subresource Integrity, and expect reauthorization after reload. +

    +

    Understand each payload boundary

    @@ -75,6 +91,8 @@ import DocsLayout from "../../../components/DocsLayout.astro";

    Application release checklist