Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DWN Registration #769

Merged
merged 5 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
25 changes: 17 additions & 8 deletions packages/api/src/web5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,13 @@ export type Web5ConnectOptions = {
/**
* If the `registration` option is provided, the agent DID and the connected DID will be registered with the DWN endpoints provided by `techPreview` or `didCreateOptions`.
*
* If registration fails, the `onFailure` callback will be called with the error.
* If registration fails, the `onFailure` callback will be called with the error.
* If registration is successful, the `onSuccess` callback will be called.
*/
registration? : {
/** Called when all of the DWN registrations are successful */
onSuccess: () => void;
/** Called when any of the DWN registrations fail */
onFailure: (error: any) => void;
}
}
Expand Down Expand Up @@ -326,30 +328,37 @@ export class Web5 {

if (registration !== undefined) {
// If a registration object is passed, we attempt to register the AgentDID and the ConnectedDID with the DWN endpoints provided

const serviceEndpointNodes = techPreview?.dwnEndpoints ?? didCreateOptions?.dwnEndpoints;
for (const dwnEndpoint of serviceEndpointNodes) {
try {

// We only want to return the success callback if we successfully register with all DWN endpoints
// So we keep track of whether we attempted registration at all
let registrationAttempt = false;
LiranCohen marked this conversation as resolved.
Show resolved Hide resolved
try {
for (const dwnEndpoint of serviceEndpointNodes) {
// check if endpoint needs registration
const serverInfo = await userAgent.rpc.getServerInfo(dwnEndpoint);
if (serverInfo.registrationRequirements.length === 0) {
// no registration required
continue;
}

registrationAttempt = true;

// register the agent DID
await DwnRegistrar.registerTenant(dwnEndpoint, agent.agentDid.uri);

// register the connected Identity DID
await DwnRegistrar.registerTenant(dwnEndpoint, connectedDid);
}

// if no failure occurs, call the onSuccess callback
if (registrationAttempt) {
LiranCohen marked this conversation as resolved.
Show resolved Hide resolved
// If there was a registration attempt and no errors were thrown, call the onSuccess callback
registration.onSuccess();
} catch(error) {
// for any failure, call the onFailure callback with the error
registration.onFailure(error);
}

} catch(error) {
// for any failure, call the onFailure callback with the error
registration.onFailure(error);
}
}

Expand Down
145 changes: 144 additions & 1 deletion packages/api/tests/web5.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import sinon from 'sinon';

import { MemoryStore } from '@web5/common';
import { Web5UserAgent } from '@web5/user-agent';
import { AgentIdentityApi, HdIdentityVault, PlatformAgentTestHarness } from '@web5/agent';
import { AgentIdentityApi, DwnRegistrar, HdIdentityVault, PlatformAgentTestHarness } from '@web5/agent';

import { Web5 } from '../src/web5.js';

Expand Down Expand Up @@ -204,5 +204,148 @@ describe('Web5', () => {
const serviceEndpoints = (identityApiSpy.firstCall.args[0].didOptions as any).services[0].serviceEndpoint;
expect(serviceEndpoints).to.deep.equal(['https://dwn.tbddev.org/beta']);
});

describe('registration', () => {
it('should call onSuccess if registration is successful', async () => {
sinon.stub(Web5UserAgent, 'create').resolves(testHarness.agent as Web5UserAgent);
const serverInfoStub = sinon.stub(testHarness.agent.rpc, 'getServerInfo').resolves({
registrationRequirements : ['terms-of-service'],
maxFileSize : 10000,
webSocketSupport : true,
});

// stub a successful registration
const registerStub = sinon.stub(DwnRegistrar, 'registerTenant').resolves();

const registration = {
onSuccess : () => {},
onFailure : () => {}
};

const registerSuccessSpy = sinon.spy(registration, 'onSuccess');
const registerFailureSpy = sinon.spy(registration, 'onFailure');

const { web5, did } = await Web5.connect({ registration, didCreateOptions: { dwnEndpoints: [
'https://dwn.example.com',
'https://dwn.production.com/'
] } });
expect(web5).to.exist;
expect(did).to.exist;

// Success should be called, and failure should not
expect(registerFailureSpy.notCalled, 'onFailure not called').to.be.true;
expect(registerSuccessSpy.calledOnce, 'onSuccess called').to.be.true;

// Expect getServerInfo and registerTenant to be called.
expect(serverInfoStub.calledTwice, 'getServerInfo called').to.be.true; // once per dwnEndpoint
expect(registerStub.callCount, 'registerTenant called').to.equal(4); // called twice for each dwnEndpoint
});

it('should call onFailure if the registration attempts fail', async () => {
sinon.stub(Web5UserAgent, 'create').resolves(testHarness.agent as Web5UserAgent);
const serverInfoStub = sinon.stub(testHarness.agent.rpc, 'getServerInfo').resolves({
registrationRequirements : ['terms-of-service'],
maxFileSize : 10000,
webSocketSupport : true,
});

// stub a successful registration
const registerStub = sinon.stub(DwnRegistrar, 'registerTenant').rejects();

const registration = {
onSuccess : () => {},
onFailure : () => {}
};

const registerSuccessSpy = sinon.spy(registration, 'onSuccess');
const registerFailureSpy = sinon.spy(registration, 'onFailure');

const { web5, did } = await Web5.connect({ registration, didCreateOptions: { dwnEndpoints: [
'https://dwn.example.com',
'https://dwn.production.com/'
] } });
expect(web5).to.exist;
expect(did).to.exist;

// failure should be called, and success should not
expect(registerSuccessSpy.notCalled, 'onSuccess not called').to.be.true;
expect(registerFailureSpy.calledOnce, 'onFailure called').to.be.true;

// Expect getServerInfo and registerTenant to be called.
expect(serverInfoStub.calledOnce, 'getServerInfo called').to.be.true; // only called once before registration fails
expect(registerStub.callCount, 'registerTenant called').to.equal(1); // called once and fails
});

it('should not attempt registration if the server does not require it', async () => {
sinon.stub(Web5UserAgent, 'create').resolves(testHarness.agent as Web5UserAgent);
const serverInfoStub = sinon.stub(testHarness.agent.rpc, 'getServerInfo').resolves({
registrationRequirements : [], // no registration requirements
maxFileSize : 10000,
webSocketSupport : true,
});

// stub a successful registration
const registerStub = sinon.stub(DwnRegistrar, 'registerTenant').resolves();

const registration = {
onSuccess : () => {},
onFailure : () => {}
};

const registerSuccessSpy = sinon.spy(registration, 'onSuccess');
const registerFailureSpy = sinon.spy(registration, 'onFailure');

const { web5, did } = await Web5.connect({ registration, didCreateOptions: { dwnEndpoints: [
'https://dwn.example.com',
'https://dwn.production.com/'
] } });
expect(web5).to.exist;
expect(did).to.exist;

// should ot call either success or failure
expect(registerFailureSpy.notCalled, 'onFailure not called').to.be.true;
expect(registerSuccessSpy.notCalled, 'onSuccess called').to.be.true;

// Expect getServerInfo to be called but not registerTenant
expect(serverInfoStub.calledTwice, 'getServerInfo called').to.be.true; // once per dwnEndpoint
expect(registerStub.notCalled, 'registerTenant not called').to.be.true; // not called
});

it('techPreview.dwnEndpoints should take precedence over didCreateOptions.dwnEndpoints', async () => {
sinon.stub(Web5UserAgent, 'create').resolves(testHarness.agent as Web5UserAgent);
const serverInfoStub = sinon.stub(testHarness.agent.rpc, 'getServerInfo').resolves({
registrationRequirements : ['terms-of-service'],
maxFileSize : 10000,
webSocketSupport : true,
});

// stub a successful registration
const registerStub = sinon.stub(DwnRegistrar, 'registerTenant').resolves();

const registration = {
onSuccess : () => {},
onFailure : () => {}
};

const registerSuccessSpy = sinon.spy(registration, 'onSuccess');
const registerFailureSpy = sinon.spy(registration, 'onFailure');

const { web5, did } = await Web5.connect({ registration,
didCreateOptions : { dwnEndpoints: [ 'https://dwn.example.com', 'https://dwn.production.com/' ] }, // two endpoints,
techPreview : { dwnEndpoints: [ 'https://dwn.production.com/' ] }, // one endpoint
});
expect(web5).to.exist;
expect(did).to.exist;

// Success should be called, and failure should not
expect(registerFailureSpy.notCalled, 'onFailure not called').to.be.true;
expect(registerSuccessSpy.calledOnce, 'onSuccess called').to.be.true;

// Expect getServerInfo and registerTenant to be called.
expect(serverInfoStub.calledOnce, 'getServerInfo called').to.be.true; // Should only be called once for `techPreview` endpoint
expect(registerStub.callCount, 'registerTenant called').to.equal(2); // called twice, once for Agent DID once for Identity DID
});
});

});
});
Loading