Skip to content

Commit d377951

Browse files
committed
Did migration, move routing to query param
1 parent a9fd597 commit d377951

43 files changed

Lines changed: 1285 additions & 193 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 226 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser/data-browser/src/components/forms/NewForm/SubjectField.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ export function SubjectField({
3737
// as plain read-only text.
3838
const isDID = value.startsWith('did:') || value.startsWith('_');
3939

40-
const [origin, path] =
41-
isDID || readOnly ? ['unknown', 'subject'] : getPath(value);
42-
43-
const [inputValue, setInputValue] = useState(path);
44-
4540
if (isDID || readOnly) {
4641
return (
4742
<Field
@@ -56,6 +51,9 @@ export function SubjectField({
5651
);
5752
}
5853

54+
const [origin, path] = getPath(value);
55+
const [inputValue, setInputValue] = useState(path);
56+
5957
const handleChange = (v: string) => {
6058
const subject = new URL(normalizePath(v), value);
6159
setInputValue(subject.pathname.slice(1));

browser/data-browser/src/helpers/agentStorage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Agent, SubtleCryptoProvider } from '@tomic/react';
1+
import { Agent, SubtleCryptoProvider, JSCryptoProvider } from '@tomic/react';
22
import { del, get, set } from 'idb-keyval';
33

44
const AGENT_IDB_KEY = 'atomic.agent';

browser/data-browser/src/views/InvitePage.tsx

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -90,27 +90,22 @@ function InvitePage({ resource }: ResourcePageProps): JSX.Element {
9090
name?: string,
9191
) => {
9292
const resourceToSave = store.getResourceLoading(subject);
93-
9493
try {
9594
if (name?.trim()) {
9695
await resourceToSave.set(core.properties.name, name);
9796
}
98-
9997
const currentIsA =
10098
(await resourceToSave.get(core.properties.isA)) ?? ([] as string[]);
101-
10299
if (!currentIsA.includes(core.classes.agent)) {
103100
await resourceToSave.set(core.properties.isA, [
104101
...currentIsA,
105102
core.classes.agent,
106103
]);
107104
}
108-
109105
if (destination) {
110106
try {
111107
const target = await store.fetchResourceFromServer(destination);
112108
const driveSubject = await getResourcesDrive(target, store);
113-
114109
if (driveSubject) {
115110
resourceToSave.push(server.properties.drives, [driveSubject]);
116111
}
@@ -120,7 +115,6 @@ function InvitePage({ resource }: ResourcePageProps): JSX.Element {
120115
);
121116
}
122117
}
123-
124118
await resourceToSave.save();
125119
} catch (e) {
126120
store.notifyError(
@@ -133,13 +127,10 @@ function InvitePage({ resource }: ResourcePageProps): JSX.Element {
133127
onSuccess: async () => {
134128
setAgentSecret(undefined);
135129
const agentSubject = agent?.subject;
136-
137130
if (!agentSubject) {
138131
goToRedirect();
139-
140132
return;
141133
}
142-
143134
goToRedirect();
144135
void persistAgentAfterInvite(agentSubject, redirectURL, agentName);
145136
},
@@ -194,15 +185,13 @@ function InvitePage({ resource }: ResourcePageProps): JSX.Element {
194185

195186
if (redirect.error) {
196187
store.notifyError(redirect.error);
197-
198188
return;
199189
}
200190

201191
const destination = await getRedirectDestination(redirect);
202192

203193
if (!destination) {
204194
store.notifyError(new Error('Invite accepted, but no destination was returned.'));
205-
206195
return;
207196
}
208197

@@ -229,7 +218,6 @@ function InvitePage({ resource }: ResourcePageProps): JSX.Element {
229218
setRedirectURL(destination);
230219
goToRedirect(destination);
231220
void persistAgentAfterInvite(agentSubject!, destination, undefined);
232-
233221
return;
234222
}
235223

browser/lib/src/agent.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ export class Agent implements AgentInterface {
6464
// Fallback to JS crypto if SubtleCrypto doesn't support Ed25519
6565
// (e.g. in some headless browser environments)
6666
try {
67-
const [provider, subject] = JSCryptoProvider.fromSecret(secretB64);
67+
const [provider, subject] =
68+
JSCryptoProvider.fromSecret(secretB64);
6869
resolve(new Agent(provider, subject));
6970
} catch (e) {
7071
reject(e);

browser/lib/src/resource.ts

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export class Resource<C extends OptionalClass = any> {
117117

118118
throw new Error(
119119
'Invalid subject given to resource, must be a string, found ' +
120-
typeof subject,
120+
typeof subject,
121121
);
122122
}
123123

@@ -225,11 +225,6 @@ export class Resource<C extends OptionalClass = any> {
225225
});
226226
}
227227

228-
/** Returns `true` when there are locally-signed commits waiting to be pushed. */
229-
public get hasPendingCommits(): boolean {
230-
return this._pendingCommits.length > 0;
231-
}
232-
233228
private get store(): Store {
234229
if (!this._store) {
235230
console.error(`Resource ${this.subject} has no store`);
@@ -552,10 +547,9 @@ export class Resource<C extends OptionalClass = any> {
552547
public getCommitsCollectionSubject(): string {
553548
// For DID subjects (or other non-HTTP URIs) we can't derive the server
554549
// origin from the subject itself — use the store's server URL instead.
555-
const base =
556-
this.subject.startsWith('did:') || this.subject.startsWith('_')
557-
? this.store.getServerUrl()
558-
: this.subject;
550+
const base = this.subject.startsWith('did:') || this.subject.startsWith('_')
551+
? this.store.getServerUrl()
552+
: this.subject;
559553
const url = new URL('/commits', base);
560554
url.searchParams.append('property', commits.properties.subject);
561555
url.searchParams.append('value', this.subject);
@@ -605,8 +599,9 @@ export class Resource<C extends OptionalClass = any> {
605599
const commitsCollection = await this.store.fetchResourceFromServer(
606600
this.getCommitsCollectionSubject(),
607601
);
608-
const commitList = (commitsCollection.get(collections.properties.members) ??
609-
[]) as string[];
602+
const commitList = (commitsCollection.get(
603+
collections.properties.members,
604+
) ?? []) as string[];
610605

611606
const builtVersions: Version[] = [];
612607

@@ -883,6 +878,11 @@ export class Resource<C extends OptionalClass = any> {
883878
return commit;
884879
}
885880

881+
/** Returns `true` when there are locally-signed commits waiting to be pushed. */
882+
public get hasPendingCommits(): boolean {
883+
return this._pendingCommits.length > 0;
884+
}
885+
886886
/**
887887
* Push all locally-queued commits to the server, in order.
888888
*
@@ -895,9 +895,7 @@ export class Resource<C extends OptionalClass = any> {
895895
}
896896

897897
const endpoint = this.getCommitEndpoint();
898-
const wasNew =
899-
this._pendingCommits.length > 0 &&
900-
this._pendingCommits[0].previousCommit === undefined;
898+
const wasNew = this._pendingCommits.length > 0 && this._pendingCommits[0].previousCommit === undefined;
901899

902900
let lastCommitId: string | undefined;
903901

@@ -937,6 +935,19 @@ export class Resource<C extends OptionalClass = any> {
937935
}
938936
}
939937

938+
/** Resolves the `/commit` endpoint for this resource. */
939+
private getCommitEndpoint(): string {
940+
if (this.subject.startsWith('did:')) {
941+
return new URL('/commit', this.store.getServerUrl()).toString();
942+
}
943+
944+
try {
945+
return new URL(this.subject).origin + `/commit`;
946+
} catch {
947+
return new URL('/commit', this.store.getServerUrl()).toString();
948+
}
949+
}
950+
940951
/**
941952
* Commits the changes and sends the Commit to the resource's `/commit`
942953
* endpoint. Returns the Url of the created Commit. If you don't pass an Agent
@@ -989,9 +1000,7 @@ export class Resource<C extends OptionalClass = any> {
9891000
});
9901001

9911002
// Keep a backup of the commit builder in case push fails.
992-
const oldCommitBuilder = hasChanges
993-
? this.commitBuilder.clone()
994-
: undefined;
1003+
const oldCommitBuilder = hasChanges ? this.commitBuilder.clone() : undefined;
9951004
const wasNew = this.new;
9961005

9971006
try {
@@ -1141,19 +1150,6 @@ export class Resource<C extends OptionalClass = any> {
11411150
});
11421151
}
11431152

1144-
/** Resolves the `/commit` endpoint for this resource. */
1145-
private getCommitEndpoint(): string {
1146-
if (this.subject.startsWith('did:')) {
1147-
return new URL('/commit', this.store.getServerUrl()).toString();
1148-
}
1149-
1150-
try {
1151-
return new URL(this.subject).origin + `/commit`;
1152-
} catch {
1153-
return new URL('/commit', this.store.getServerUrl()).toString();
1154-
}
1155-
}
1156-
11571153
private isParentNew() {
11581154
const parentSubject = this.propvals.get(core.properties.parent) as string;
11591155

browser/lib/src/store.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ export class Store {
294294
!storeResource.loading &&
295295
!storeResource.new &&
296296
storeResource.get(commits.properties.lastCommit) ===
297-
resource.get(commits.properties.lastCommit)
297+
resource.get(commits.properties.lastCommit)
298298
) {
299299
return;
300300
}
@@ -431,6 +431,11 @@ export class Store {
431431
return this.findAvailableSubject(path, parentUrl);
432432
}
433433

434+
/** Creates a random HTTP subject under the given parent URL. */
435+
private createHTTPSubject(parentSubject: string): string {
436+
return `${parentSubject}/${this.randomPart()}`;
437+
}
438+
434439
/**
435440
* Always fetches the resource from the server then adds it to the store.
436441
*/
@@ -465,14 +470,12 @@ export class Store {
465470

466471
if (existing) {
467472
existing.loading = false;
468-
469473
return existing as Resource<C>;
470474
}
471475

472476
const local = new Resource<C>(normalizedSubject, true);
473477
local.loading = false;
474478
this.addResources(local, { skipCommitCompare: true });
475-
476479
return local;
477480
}
478481

@@ -506,13 +509,15 @@ export class Store {
506509
? { agent: this.agent, serverURL: this.getServerUrl() }
507510
: undefined;
508511

509-
const { resource, createdResources } =
510-
await this.client.fetchResourceHTTP(fetchSubject, {
512+
const { resource, createdResources } = await this.client.fetchResourceHTTP(
513+
fetchSubject,
514+
{
511515
from: opts.fromProxy ? this.getServerUrl() : undefined,
512516
method: opts.method,
513517
body: opts.body,
514518
signInfo,
515-
});
519+
},
520+
);
516521

517522
// The client already returns the requested top-level resource as `resource`.
518523
this.addResources(resource, {
@@ -524,10 +529,7 @@ export class Store {
524529

525530
// Any other resources that were returned (e.g. linked resources)
526531
createdResources.forEach(r => {
527-
if (
528-
this.normalizeSubject(r.subject) !==
529-
this.normalizeSubject(resource.subject)
530-
) {
532+
if (this.normalizeSubject(r.subject) !== this.normalizeSubject(resource.subject)) {
531533
this.addResources(r);
532534
}
533535
});
@@ -617,10 +619,7 @@ export class Store {
617619
let resource = this.resources.get(resolved);
618620

619621
if (!resource) {
620-
resource = new Resource<C>(
621-
normalized,
622-
opts.newResource || isTemporarySubject,
623-
);
622+
resource = new Resource<C>(normalized, opts.newResource || isTemporarySubject);
624623

625624
// New resources don't have to load, they are just created.
626625
if (!opts.newResource && !isTemporarySubject) {
@@ -1312,11 +1311,6 @@ export class Store {
13121311
await loadResourceTreeInner(resource, treeTemplate);
13131312
}
13141313

1315-
/** Creates a random HTTP subject under the given parent URL. */
1316-
private createHTTPSubject(parentSubject: string): string {
1317-
return `${parentSubject}/${this.randomPart()}`;
1318-
}
1319-
13201314
private randomPart(): string {
13211315
return ulid().toLowerCase();
13221316
}

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171

7272
- [Atomic Data Extended](extended.md)
7373
- [Agents](agents.md)
74+
- [Decentralized Identifiers (DIDs)](did.md)
7475
- [Hierarchy and authorization](hierarchy.md)
7576
- [Authentication](authentication.md)
7677
- [Invitations and sharing](invitations.md)

docs/src/agents.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ The `publicKey` is used to verify commit signatures by that Agent, to check if t
2929

3030
## Creating an Agent
3131

32-
An Agent is identified by a DID (Decentralized Identifier) derived from its public key: `did:ad:{publicKey}`.
32+
An Agent is identified by a DID (Decentralized Identifier) derived from its public key: `did:ad:agent:{publicKey}`.
3333
When a client generates a keypair, the public key immediately determines the Agent's subject, without needing to register it on a server first.
34+
See the [DID specification](did.md) for details on how agent DIDs work and are resolved.
3435

3536
One way to start using your Agent is by accepting an [Invite](invitations.md) with your public key.
36-
The server will derive the `did:ad:` identifier and grant the requested rights.
37+
The server will derive the `did:ad:agent:` identifier and grant the requested rights.
3738
Alternatively, you can host an [Atomic Server](https://crates.io/crates/atomic-server) and use the `/setup` invite to configure the root Agent.

0 commit comments

Comments
 (0)