Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@v7
with:
repository: mdbase-dev/mdbase-connect
ref: e6b8c7d552c74ad6c4163733ae3337302c97e434
ref: 8e960317f0886f8c504023dad873cf8cec82fd99
path: .sources/mdbase-connect

- name: Check out Rust implementation
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
uses: actions/checkout@v7
with:
repository: mdbase-dev/mdbase-connect
ref: e6b8c7d552c74ad6c4163733ae3337302c97e434
ref: 8e960317f0886f8c504023dad873cf8cec82fd99
path: .sources/mdbase-connect

- name: Check out Rust implementation
Expand Down
2 changes: 1 addition & 1 deletion site-sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},
"connect": {
"repository": "mdbase-dev/mdbase-connect",
"ref": "e6b8c7d552c74ad6c4163733ae3337302c97e434"
"ref": "8e960317f0886f8c504023dad873cf8cec82fd99"
},
"implementations": [
{
Expand Down
39 changes: 39 additions & 0 deletions src/pages/sdk/api/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ const constructor = `new MdbaseConnect<Frontmatter>({
navigate?: (url: string) => void | Promise<void>
})`;

const browserLocation = `const collectionLocation =
new MdbaseBrowserLocation(manager, {
collectionParameter: "collection",
fallbackPath: "/"
});`;

const error = `try {
const result = unwrapOperation(await connection.update(input));
} catch (error) {
Expand All @@ -33,6 +39,7 @@ const error = `try {
{ href: "#constructor", label: "Constructor" },
{ href: "#environment", label: "Environment" },
{ href: "#authorization", label: "Authorization methods" },
{ href: "#browser-location", label: "Browser location" },
{ href: "#operations", label: "Operation methods" },
{ href: "#notifications", label: "Notifications and timers" },
{ href: "#connection", label: "Connection and sync" },
Expand Down Expand Up @@ -165,6 +172,38 @@ const error = `try {
discard the unapproved key.
</p>

<h2 id="browser-location">MdbaseBrowserLocation</h2>
<CodeBlock code={browserLocation} language="ts" label="Browser application boundary" />
<p>
Keep one location helper beside the application's stable
{" "}<code>MdbaseConnect</code> manager. It treats the explicit URL
identity as authoritative and selects a saved fallback only when exactly
one connection exists.
</p>
<table class="api-table">
<thead>
<tr><th>Method</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td><code>selectedCollectionId()</code></td><td>Read the explicit bookmark identity, including one that is not currently authorized</td></tr>
<tr><td><code>activeConnection()</code></td><td>Resolve the bookmarked connection or the sole saved fallback</td></tr>
<tr><td><code>selectConnection(id, options?)</code></td><td>Push or replace a cleaned bookmark URL</td></tr>
<tr><td><code>authorizationReturnTo()</code></td><td>Return the current app-local path after removing temporary OAuth parameters</td></tr>
<tr><td><code>completeAuthorization(callbackUrl?)</code></td><td>Complete PKCE and restore a safe same-origin URL for the approved collection</td></tr>
<tr><td><code>isAuthorizationCallback(url)</code></td><td>Recognize browser and native callback URLs with an authorization result</td></tr>
<tr><td><code>clearAuthorizationCallback(returnTo?)</code></td><td>Remove callback parameters after denial or failure</td></tr>
<tr><td><code>onChange(listener)</code></td><td>Observe saved-connection changes plus browser back and forward navigation</td></tr>
</tbody>
</table>
<div class="callout">
<strong>Collection IDs are bookmark-safe locators.</strong>
<p>
They may appear in history, logs, and shared URLs. They do not authorize
an operation. Connect still requires the application's stored grant and
enforces it at the collection authority.
</p>
</div>

<h2 id="operations">Collection operations</h2>
<p>
The methods below belong to <code>MdbaseConnection</code>, not the manager.
Expand Down
12 changes: 7 additions & 5 deletions src/pages/sdk/authorization/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ const capability = connection.authorizationCapabilities([...required]);

if (!capability.sufficient) {
await connection.requestOperations([...required], {
returnTo: location.pathname + location.search
returnTo: collectionLocation.authorizationReturnTo()
});
}`;

const listen = `const stop = connection.onConnectionChange((info) => {
if (!info) {
const listen = `const stop = collectionLocation.onChange(({ connection }) => {
if (!connection) {
renderDisconnected();
return;
}

const info = connection.info();
renderConnection({
collectionId: info.collectionId,
route: info.route,
Expand Down Expand Up @@ -161,8 +162,9 @@ const { connection } = await connect.authorize({
<p>
The route is <code>direct</code>, <code>relay</code>, or
{" "}<code>hosted</code>. Display it as status information. The SDK selects
the route. A <code>null</code> connection means the SDK has no usable
authorization.
the route. The browser location subscription also reports back and forward
selection changes. A <code>null</code> connection means the bookmarked
collection has no usable authorization.
</p>

<h2 id="recovery">Use structured recovery actions</h2>
Expand Down
19 changes: 13 additions & 6 deletions src/pages/sdk/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ export async function listOpenTasks(client: MdbaseCollectionClient<Task>) {
<td>One collection's operations, renewal, routing, notifications, and hosted sync</td>
<td>Browser APIs or adapters supplied by a native shell</td>
</tr>
<tr>
<td><code>MdbaseBrowserLocation</code></td>
<td>Bookmarkable collection selection, authorization return URLs, and browser navigation</td>
<td>A stable <code>MdbaseConnect</code> manager and browser history APIs</td>
</tr>
<tr>
<td><code>MdbaseCollectionClient</code></td>
<td>Domain and feature logic that should be independent of OAuth and hosting</td>
Expand All @@ -84,12 +89,14 @@ export async function listOpenTasks(client: MdbaseCollectionClient<Task>) {
<h2 id="application-shape">Application structure</h2>
<p>
Create one <code>MdbaseConnect</code> manager at the application boundary.
Resolve the ID in the URL with <code>manager.connection(id)</code>, then
pass that <code>MdbaseConnection</code> into feature code while keeping
OAuth and saved-connection state at the boundary. A bound connection never
changes collections underneath a repository, and feature code written
against <code>MdbaseCollectionClient</code> can also run against the
developer sandbox or another conforming transport.
Pair it with one <code>MdbaseBrowserLocation</code> at the browser
application boundary. Resolve its active connection, then pass that
{" "}<code>MdbaseConnection</code> into feature code. The location helper owns
bookmark selection and OAuth return cleanup; the manager owns saved
authorizations. A bound connection never changes collections underneath a
repository, and feature code written against
{" "}<code>MdbaseCollectionClient</code> can also run against the developer
sandbox or another conforming transport.
</p>
<p>
A downloaded HTML file supplies
Expand Down
40 changes: 21 additions & 19 deletions src/pages/sdk/quickstart/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const install = `pnpm add @mdbase/connect
pnpm add -D @mdbase/connect-dev`;

const client = `import {
MdbaseBrowserLocation,
MdbaseConnect,
type JsonObject
} from "@mdbase/connect";
Expand All @@ -33,34 +34,31 @@ export const mdbase = new MdbaseConnect<Workout>({
serverUrl: "https://connect.mdbase.dev",
manifest: new URL("/.well-known/mdbase-app.json", location.origin).href,
redirectUri: new URL("/auth/mdbase/callback", location.origin).href
});`;
});

const connectButton = `import { mdbase } from "./connect";
export const collectionLocation = new MdbaseBrowserLocation(mdbase);`;

const connectButton = `import { collectionLocation, mdbase } from "./connect";

export async function connectCollection() {
await mdbase.authorize({
operations: ["describe", "read", "query", "update"],
returnTo: location.pathname + location.search
collectionId: collectionLocation.selectedCollectionId() ?? undefined,
returnTo: collectionLocation.authorizationReturnTo()
});
// authorize() navigates to Connect and does not return.
}`;

const callback = `import { mdbase } from "./connect";
const callback = `import { collectionLocation } from "./connect";

export async function completeMdbaseCallback() {
const { connection, returnTo = "/" } =
await mdbase.completeAuthorization(location.href);
const destination = new URL(returnTo, location.origin);
destination.searchParams.set("collection", connection.collectionId);
history.replaceState({}, "", destination);
return connection; // Permanently bound to this collection.
return collectionLocation.completeAuthorization();
}`;

const query = `import { mdbase } from "./connect";
const query = `import { collectionLocation } from "./connect";
import { unwrapOperation } from "@mdbase/connect";

const collectionId = new URL(location.href).searchParams.get("collection");
const collection = collectionId ? mdbase.connection(collectionId) : null;
const collection = collectionLocation.activeConnection();
if (!collection) throw new Error("Choose a collection.");

const page = unwrapOperation(await collection.query({
Expand Down Expand Up @@ -159,7 +157,9 @@ const validate = `mdbase-connect-dev validate-manifest \\
<p>
Call this once on the exact callback route listed in the manifest. PKCE is
mandatory. On success, the client stores the grant-bound authorization and
returns the newly approved collection-bound connection.
returns the newly approved collection-bound connection. The location
helper restores the app-local return location, adds its collection ID, and
removes temporary OAuth parameters.
</p>

<h2 id="operate">6. Query and update with a revision</h2>
Expand All @@ -172,11 +172,13 @@ const validate = `mdbase-connect-dev validate-manifest \\
<div class="callout">
<strong>Use a stable collection ID in app URLs.</strong>
<p>
A query parameter such as <code>?collection=&lt;id&gt;</code> works on
static hosts, survives refreshes, and makes a view bookmarkable. Treat an
explicit ID as authoritative: if it is not saved, show the chooser or
reconnect that ID instead of silently opening another collection. Names
are display text and can change.
<code>MdbaseBrowserLocation</code> writes
{" "}<code>?collection=&lt;id&gt;</code>, follows browser back and
forward navigation through <code>onChange()</code>, and auto-selects only
when exactly one connection is saved. An explicit unavailable ID stays
authoritative, so show the chooser or reconnect that ID. Collection IDs
can appear in browser history and logs. They are opaque locators, not
credentials. Names are display text and can change.
</p>
</div>
<div class="callout">
Expand Down