diff --git a/shnippet.config.json b/shnippet.config.json index e51218942..0f4c9dbea 100644 --- a/shnippet.config.json +++ b/shnippet.config.json @@ -1,5 +1,9 @@ { - "rootDirectory": "./site/testsuites", + "rootDirectories": [ + "./site/testsuites", + "./site/docs/tbdex/test", + "./site/docs/web5/test" + ], "outputDirectory": "./site/snippets", "fileExtensions": [".js", ".ts", ".kt", ".gradle", ".xml", ".bash", ".swift"], "exclude": ["pfiOverviewReadOfferingsJs", "pfiOverviewWriteJs", "pfiOverviewWriteOfferingsJs"], diff --git a/shnippet/src/cli.js b/shnippet/src/cli.js index 676040c7b..0da7de20b 100644 --- a/shnippet/src/cli.js +++ b/shnippet/src/cli.js @@ -25,13 +25,36 @@ function clearOutputDirectory(outputDirectory) { } } +function findTestDirectories(baseDir) { + let testDirectories = []; + + function traverseDirectory(currentDir) { + const files = fs.readdirSync(currentDir); + for (const file of files) { + const filePath = path.join(currentDir, file); + const stats = fs.statSync(filePath); + + if (stats.isDirectory()) { + if (file === "test") { + testDirectories.push(filePath); + } else { + traverseDirectory(filePath); + } + } + } + } + + traverseDirectory(baseDir); + return testDirectories; +} + function main() { const args = process.argv.slice(2); const configPath = findConfigFile(); const configDir = path.dirname(configPath); let config = require(configPath); - config.rootDirectory = path.resolve(configDir, config.rootDirectory); + // Resolve paths for output directory config.outputDirectory = path.resolve(configDir, config.outputDirectory); // Check for "clear" argument @@ -62,8 +85,18 @@ function main() { process.exit(1); } - const extractor = new SnippetExtractor(config); - extractor.extractSnippets(); + // Process each root directory or traverse dynamically + let directoriesToProcess = config.rootDirectories + .map((rootDir) => path.resolve(configDir, rootDir)) + .reduce((acc, dir) => { + return acc.concat(findTestDirectories(dir)); + }, []); + + directoriesToProcess.forEach((directory) => { + config.rootDirectory = directory; // Set the current root directory + const extractor = new SnippetExtractor(config); + extractor.extractSnippets(); + }); } main(); diff --git a/site/docs/api.mdx b/site/docs/common/api.mdx similarity index 100% rename from site/docs/api.mdx rename to site/docs/common/api.mdx diff --git a/site/docs/docs-index.js b/site/docs/common/docs-index.js similarity index 93% rename from site/docs/docs-index.js rename to site/docs/common/docs-index.js index db27a4431..b898418b0 100644 --- a/site/docs/docs-index.js +++ b/site/docs/common/docs-index.js @@ -1,7 +1,7 @@ import React from 'react'; import HeroCard from '@site/src/components/HeroCard'; -import ExploreCard from '../src/components/ExploreCard'; -import Community from '../src/components/Community'; +import ExploreCard from '../../src/components/ExploreCard'; +import Community from '../../src/components/Community'; import Head from '@docusaurus/Head'; function DocsIndex() { @@ -14,8 +14,9 @@ function DocsIndex() { Give your customers control of their identity, data, and finances.

- Our toolkits bring decentralized identity, messaging, and data storage to your applications. - They let developers focus on creating delightful user experiences, while returning ownership to individuals. + Our toolkits bring decentralized identity, messaging, and data storage + to your applications. They let developers focus on creating delightful + user experiences, while returning ownership to individuals.

diff --git a/site/docs/glossary.md b/site/docs/common/glossary.md similarity index 100% rename from site/docs/glossary.md rename to site/docs/common/glossary.md diff --git a/site/docs/index.mdx b/site/docs/common/index.mdx similarity index 100% rename from site/docs/index.mdx rename to site/docs/common/index.mdx diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/credentialIssuance.bash b/site/docs/tbdex/test/issuer/credentialIssuance.bash similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/credentialIssuance.bash rename to site/docs/tbdex/test/issuer/credentialIssuance.bash diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/credentialIssuance.test.js b/site/docs/tbdex/test/issuer/credentialIssuance.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/credentialIssuance.test.js rename to site/docs/tbdex/test/issuer/credentialIssuance.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/kbc/knownBusinessCredentialIssuer.test.js b/site/docs/tbdex/test/issuer/kbc/knownBusinessCredentialIssuer.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/kbc/knownBusinessCredentialIssuer.test.js rename to site/docs/tbdex/test/issuer/kbc/knownBusinessCredentialIssuer.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/kcc/knownCustomerCredential.bash b/site/docs/tbdex/test/issuer/kcc/knownCustomerCredential.bash similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/kcc/knownCustomerCredential.bash rename to site/docs/tbdex/test/issuer/kcc/knownCustomerCredential.bash diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/kcc/knownCustomerCredentialIssuer.test.js b/site/docs/tbdex/test/issuer/kcc/knownCustomerCredentialIssuer.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/kcc/knownCustomerCredentialIssuer.test.js rename to site/docs/tbdex/test/issuer/kcc/knownCustomerCredentialIssuer.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/kcc/knownCustomerCredentialWallet.test.js b/site/docs/tbdex/test/issuer/kcc/knownCustomerCredentialWallet.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/issuer/kcc/knownCustomerCredentialWallet.test.js rename to site/docs/tbdex/test/issuer/kcc/knownCustomerCredentialWallet.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/creatingOfferings.test.js b/site/docs/tbdex/test/pfi/creatingOfferings.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/creatingOfferings.test.js rename to site/docs/tbdex/test/pfi/creatingOfferings.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/creatingQuotes.test.js b/site/docs/tbdex/test/pfi/creatingQuotes.test.js similarity index 74% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/creatingQuotes.test.js rename to site/docs/tbdex/test/pfi/creatingQuotes.test.js index a6137997f..e3e70c45b 100644 --- a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/creatingQuotes.test.js +++ b/site/docs/tbdex/test/pfi/creatingQuotes.test.js @@ -1,9 +1,9 @@ import { Rfq, Quote, Parser } from '@tbdex/http-server'; import { DevTools } from '@tbdex/http-client'; import { DidDht } from '@web5/dids'; -import { OfferingsApiProvider } from './offeringsApiProvider' -import { ExchangesApiProvider } from './exchangesApiProvider' -import { MockDataProvider } from '../../utils/mockDataProvider' +import { OfferingsApiProvider } from './offeringsApiProvider'; +import { ExchangesApiProvider } from './exchangesApiProvider'; +import { MockDataProvider } from '../../../test-utils/mockDataProvider'; import { vi, test, expect, describe, beforeAll } from 'vitest'; let pfiDid; @@ -18,20 +18,22 @@ describe('PFI: Quotes', () => { beforeAll(async () => { // Set up providers and DID pfiDid = await DidDht.create({ - options:{ + options: { publish: true, - services: [{ + services: [ + { id: 'pfi', type: 'PFI', - serviceEndpoint: 'https://example.com/' - }] - } + serviceEndpoint: 'https://example.com/', + }, + ], + }, }); senderDid = await DidDht.create({ - options: { publish: true } - }) - + options: { publish: true }, + }); + offeringsApiProvider = new OfferingsApiProvider(pfiDid); exchangesApiProvider = new ExchangesApiProvider(); @@ -39,24 +41,24 @@ describe('PFI: Quotes', () => { message = await DevTools.createRfq({ sender: senderDid, - receiver: pfiDid + receiver: pfiDid, }); await message.sign(senderDid); - + mockOffering = DevTools.createOffering({ from: pfiDid.uri, - offeringData: DevTools.createOfferingData() - }) + offeringData: DevTools.createOfferingData(), + }); await mockOffering.sign(pfiDid); - message.offeringId = mockOffering.id; offeringsApiProvider.setOffering(mockOffering); - - dataProvider.setupInsert("exchange", "", () => { return }); + dataProvider.setupInsert('exchange', '', () => { + return; + }); }); - + test('PFI creates offering', async () => { // :snippet-start: pfiWriteOfferingJs // Write the message to your exchanges database @@ -65,17 +67,17 @@ describe('PFI: Quotes', () => { messagekind: message.kind, messageid: message.id, subject: message.subject, - message: await Parser.parseMessage(message) + message: await Parser.parseMessage(message), }); - + //highlight-start const offering = await offeringsApiProvider.getOffering(message.offeringId); //highlight-end // :snippet-end: - }) + }); test('PFI creates and signs quote', async () => { - const offering = mockOffering + const offering = mockOffering; // :snippet-start: pfiCreateQuoteJs // Set the Quote's expiration date for 10 days from now @@ -87,7 +89,7 @@ describe('PFI: Quotes', () => { from: pfiDid.uri, to: message.from, exchangeId: message.exchangeId, - protocol: '1.0' + protocol: '1.0', }, data: { expiresAt: quoteExpiration.toLocaleDateString('en-us'), @@ -95,31 +97,30 @@ describe('PFI: Quotes', () => { currencyCode: offering.data.payin.currencyCode, amount: '0.01', fee: '0.0001', - paymentInstruction : { + paymentInstruction: { link: 'https://example.com/paymentInstructions', - instruction: 'Detailed payment instructions' - } + instruction: 'Detailed payment instructions', + }, }, payout: { currencyCode: offering.data.payout.currencyCode, amount: '1000.00', - paymentInstruction : { + paymentInstruction: { link: 'https://example.com/paymentInstructions', - instruction: 'Detailed payout instructions' - } - } - } + instruction: 'Detailed payout instructions', + }, + }, + }, }); // :snippet-end: - + exchangesApiProvider.setWrite(); - + // :snippet-start: pfiSignQuoteJs await quote.sign(pfiDid); exchangesApiProvider.write(quote); // :snippet-end: const signature = await quote.verifySignature(); expect(signature).toBeDefined(); - }) - -}) \ No newline at end of file + }); +}); diff --git a/site/docs/tbdex/test/pfi/exchangesApiProvider.js b/site/docs/tbdex/test/pfi/exchangesApiProvider.js new file mode 100644 index 000000000..f02f287b8 --- /dev/null +++ b/site/docs/tbdex/test/pfi/exchangesApiProvider.js @@ -0,0 +1,22 @@ +import { MockExchangesApiProvider } from '../../../test-utils/mockExchangesApiProvider'; + +export class ExchangesApiProvider extends MockExchangesApiProvider { + // :snippet-start: pfiOverviewWriteJs + async write(message, replyTo) { + await this.dataProvider.insert('exchange', { + exchangeid: message.exchangeId, + messagekind: message.kind, + messageid: message.id, + subject: message.subject, + message: JSON.stringify(message), + }); + + if (replyTo != null) { + await this.dataProvider.insert('callbacks', { + exchangeId: message.exchangeId, + uri: replyTo, + }); + } + } + // :snippet-end: +} diff --git a/site/docs/tbdex/test/pfi/offeringsApiProvider.js b/site/docs/tbdex/test/pfi/offeringsApiProvider.js new file mode 100644 index 000000000..ddbb2655d --- /dev/null +++ b/site/docs/tbdex/test/pfi/offeringsApiProvider.js @@ -0,0 +1,41 @@ +import { MockOfferingsApiProvider } from '../../../test-utils/mockOfferingsApiProvider'; +import { Parser } from '@tbdex/http-server'; + +export class OfferingsApiProvider extends MockOfferingsApiProvider { + constructor(pfiDid) { + super(); + this.pfiDid = pfiDid; + } + + // :snippet-start: pfiOverviewReadOfferingsJs + async getOffering(id) { + return this.dataProvider.get('offering', id).then(async (result) => { + if (result) { + return await Parser.parseResource(result); + } + }); + } + + async getOfferings() { + return this.dataProvider.query('offering', '*').then((results) => { + const offerings = []; + + for (let result of results) { + const offering = Parser.rawToResourceModel(result); + offerings.push(offering); + } + + return offerings; + }); + } + + async setOffering(offering) { + await this.dataProvider.insert('offering', { + offeringid: offering.metadata.id, + payoutcurrency: offering.data.payout.currencyCode, + payincurrency: offering.data.payin.currencyCode, + offering: Parser.rawToResourceModel(offering), + }); + } + // :snippet-end: +} diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/pfiOnboarding.test.js b/site/docs/tbdex/test/pfi/pfiOnboarding.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/pfiOnboarding.test.js rename to site/docs/tbdex/test/pfi/pfiOnboarding.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/pfiStructure.test.js b/site/docs/tbdex/test/pfi/pfiStructure.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/pfiStructure.test.js rename to site/docs/tbdex/test/pfi/pfiStructure.test.js diff --git a/site/docs/tbdex/test/pfi/processingOrders.test.js b/site/docs/tbdex/test/pfi/processingOrders.test.js new file mode 100644 index 000000000..c1f325855 --- /dev/null +++ b/site/docs/tbdex/test/pfi/processingOrders.test.js @@ -0,0 +1,107 @@ +import { Order, OrderStatus, Close } from '@tbdex/http-server'; +import { DevTools } from '@tbdex/http-client'; +import { DidDht } from '@web5/dids'; +import { MockDataProvider } from '../../../test-utils/mockDataProvider'; +import { test, expect, describe, beforeAll, assert } from 'vitest'; + +let pfiDid; +let senderDid; +let dataProvider; +let orderMessage; + +describe('PFI: Orders', () => { + beforeAll(async () => { + // Set up providers and DID + pfiDid = await DidDht.create({ + options: { + publish: true, + services: [ + { + id: 'pfi', + type: 'PFI', + serviceEndpoint: 'https://example.com/', + }, + ], + }, + }); + + senderDid = await DidDht.create({ + options: { publish: true }, + }); + + dataProvider = new MockDataProvider(); + orderMessage = Order.create({ + metadata: { + from: senderDid.uri, + to: pfiDid.uri, + exchangeId: 'MyExchange', + }, + }); + }); + + test('PFI Accesses Private Data', async () => { + const rfq = await DevTools.createRfq({ + sender: senderDid, + receiver: pfiDid, + }); + + // :snippet-start: pfiAccessPrivateDataJs + const creditCardNumber = rfq.privateData.payin.paymentDetails.cardNumber; + // :snippet-end: + + expect(creditCardNumber).toBeDefined(); + }); + + test('PFI Creates OrderStatus', async () => { + // :snippet-start: pfiOrderStatusJs + const orderStatus = OrderStatus.create({ + metadata: { + from: pfiDid.uri, + to: orderMessage.metadata.from, + exchangeId: orderMessage.metadata.exchangeId, + }, + data: { orderStatus: 'PROCESSING' }, + }); + + await orderStatus.sign(pfiDid); + dataProvider.insert(orderStatus); + // :snippet-end: + + expect.soft(orderStatus.data.orderStatus).toBe('PROCESSING'); + + try { + await orderStatus.verifySignature(); + await orderStatus.verify(); + } catch (e) { + assert.fail(`Failed to verify offering requirements: : ${e.message}`); + } + }); + + test('PFI Creates Close', async () => { + // :snippet-start: pfiCloseOrderJs + const closeMessage = Close.create({ + metadata: { + from: pfiDid.uri, + to: orderMessage.metadata.from, + exchangeId: orderMessage.metadata.exchangeId, + }, + data: { + reason: 'COMPLETED', + success: true, // Indicates the transaction was successful + }, + }); + + await closeMessage.sign(pfiDid); + dataProvider.insert(closeMessage); + // :snippet-end: + + expect.soft(closeMessage.data.reason).toBe('COMPLETED'); + + try { + await closeMessage.verifySignature(); + await closeMessage.verify(); + } catch (e) { + assert.fail(`Failed to verify offering requirements: ${e.message}`); + } + }); +}); diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/required-sdks.js b/site/docs/tbdex/test/pfi/required-sdks.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/required-sdks.js rename to site/docs/tbdex/test/pfi/required-sdks.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/required.sdks.bash b/site/docs/tbdex/test/pfi/required.sdks.bash similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/required.sdks.bash rename to site/docs/tbdex/test/pfi/required.sdks.bash diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/allowListPfi.test.js b/site/docs/tbdex/test/wallet/allowListPfi.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/allowListPfi.test.js rename to site/docs/tbdex/test/wallet/allowListPfi.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/getOfferings.test.js b/site/docs/tbdex/test/wallet/getOfferings.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/getOfferings.test.js rename to site/docs/tbdex/test/wallet/getOfferings.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/manageCredentials.test.js b/site/docs/tbdex/test/wallet/manageCredentials.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/manageCredentials.test.js rename to site/docs/tbdex/test/wallet/manageCredentials.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/placeOrder.test.js b/site/docs/tbdex/test/wallet/placeOrder.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/placeOrder.test.js rename to site/docs/tbdex/test/wallet/placeOrder.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/receiveQuote.test.js b/site/docs/tbdex/test/wallet/receiveQuote.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/receiveQuote.test.js rename to site/docs/tbdex/test/wallet/receiveQuote.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/required-sdks.js b/site/docs/tbdex/test/wallet/required-sdks.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/required-sdks.js rename to site/docs/tbdex/test/wallet/required-sdks.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/required.sdks.bash b/site/docs/tbdex/test/wallet/required.sdks.bash similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/required.sdks.bash rename to site/docs/tbdex/test/wallet/required.sdks.bash diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/sendRfq.test.js b/site/docs/tbdex/test/wallet/sendRfq.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/sendRfq.test.js rename to site/docs/tbdex/test/wallet/sendRfq.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/walletQuickstart.test.js b/site/docs/tbdex/test/wallet/walletQuickstart.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/walletQuickstart.test.js rename to site/docs/tbdex/test/wallet/walletQuickstart.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/utils/mockDataProvider.js b/site/docs/test-utils/mockDataProvider.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/utils/mockDataProvider.js rename to site/docs/test-utils/mockDataProvider.js diff --git a/site/testsuites/testsuite-javascript/__tests__/utils/mockExchangesApiProvider.js b/site/docs/test-utils/mockExchangesApiProvider.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/utils/mockExchangesApiProvider.js rename to site/docs/test-utils/mockExchangesApiProvider.js diff --git a/site/testsuites/testsuite-javascript/__tests__/utils/mockOfferingsApiProvider.js b/site/docs/test-utils/mockOfferingsApiProvider.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/utils/mockOfferingsApiProvider.js rename to site/docs/test-utils/mockOfferingsApiProvider.js diff --git a/site/testsuites/testsuite-javascript/__tests__/setup-web5.js b/site/docs/test-utils/setup-web5.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/setup-web5.js rename to site/docs/test-utils/setup-web5.js diff --git a/site/docs/web5/decentralized-identifiers/how-to-create-did.mdx b/site/docs/web5/decentralized-identifiers/how-to-create-did.mdx index df0a8587f..27e338b3b 100644 --- a/site/docs/web5/decentralized-identifiers/how-to-create-did.mdx +++ b/site/docs/web5/decentralized-identifiers/how-to-create-did.mdx @@ -4,7 +4,8 @@ hide_title: true sidebar_position: 2 --- -import requiredDidImportsKt from '!!raw-loader!@site/snippets/testsuite-kotlin/src/test/kotlin/docs/web5/build/decentralizedidentifiers/requiredDidImportsKt.snippet.kt'; +import requiredDidImportsKt from '!!raw-loader!@site/snippets/kotlin/docs/web5/build/decentralizedidentifiers/requiredDidImportsKt.snippet.kt'; + diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/apps/upgrade-to-web5.test.js b/site/docs/web5/test/apps/upgrade-to-web5.test.js similarity index 80% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/apps/upgrade-to-web5.test.js rename to site/docs/web5/test/apps/upgrade-to-web5.test.js index 77b550c54..102730559 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/apps/upgrade-to-web5.test.js +++ b/site/docs/web5/test/apps/upgrade-to-web5.test.js @@ -1,6 +1,6 @@ import { test, expect, beforeAll, describe, vi } from 'vitest'; -import { createAliceDid } from '../../../../../../code-snippets/web5/build/apps/upgrade-to-web5'; -import { setUpWeb5 } from '../../../setup-web5'; +import { createAliceDid } from '../../../../code-snippets/web5/build/apps/upgrade-to-web5'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did-dependency.gradle b/site/docs/web5/test/decentralized-identifiers/how-to-create-did-dependency.gradle similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did-dependency.gradle rename to site/docs/web5/test/decentralized-identifiers/how-to-create-did-dependency.gradle diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did-dependency.xml b/site/docs/web5/test/decentralized-identifiers/how-to-create-did-dependency.xml similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did-dependency.xml rename to site/docs/web5/test/decentralized-identifiers/how-to-create-did-dependency.xml diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did-import.bash b/site/docs/web5/test/decentralized-identifiers/how-to-create-did-import.bash similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did-import.bash rename to site/docs/web5/test/decentralized-identifiers/how-to-create-did-import.bash diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did.test.js b/site/docs/web5/test/decentralized-identifiers/how-to-create-did.test.js similarity index 88% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did.test.js rename to site/docs/web5/test/decentralized-identifiers/how-to-create-did.test.js index c68deb6dd..2b84c3fd7 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-create-did.test.js +++ b/site/docs/web5/test/decentralized-identifiers/how-to-create-did.test.js @@ -1,7 +1,7 @@ import { test, expect, vi, describe, beforeAll } from 'vitest'; import { DidDht, DidJwk } from '@web5/dids'; -import { createDidAutomatically } from '../../../../../../code-snippets/web5/build/decentralized-identifiers/how-to-create-did'; -import { setUpWeb5 } from '../../../setup-web5'; +import { createDidAutomatically } from '../../../../code-snippets/web5/build/decentralized-identifiers/how-to-create-did'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; @@ -25,7 +25,6 @@ describe('how-to-create-did', () => { }); }); - test('show required imports to create did', async () => { const requiredImports = ` // :snippet-start: requiredDidImports @@ -51,14 +50,14 @@ import { DidJwk } from '@web5/dids' const didDht = await DidDht.create({ publish: true }); // DID and its associated data which can be exported and used in different contexts/apps - const portableDid = didDht.export() + const portableDid = didDht.export(); // DID string const did = didDht.uri; // DID Document const didDocument = JSON.stringify(didDht.document); - + // :snippet-end: expect(did).toMatch(/^did:dht:/); @@ -70,7 +69,7 @@ import { DidJwk } from '@web5/dids' const didJwk = await DidJwk.create(); //DID and its associated data which can be exported and used in different contexts/apps - const portableDid = didJwk.export() + const portableDid = didJwk.export(); //DID string const did = didJwk.uri; diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-resolve-did.test.js b/site/docs/web5/test/decentralized-identifiers/how-to-resolve-did.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-resolve-did.test.js rename to site/docs/web5/test/decentralized-identifiers/how-to-resolve-did.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-update-did.test.js b/site/docs/web5/test/decentralized-identifiers/how-to-update-did.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/how-to-update-did.test.js rename to site/docs/web5/test/decentralized-identifiers/how-to-update-did.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/key-management.test.js b/site/docs/web5/test/decentralized-identifiers/key-management.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-identifiers/key-management.test.js rename to site/docs/web5/test/decentralized-identifiers/key-management.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/access-data-with-drls.test.js b/site/docs/web5/test/decentralized-web-nodes/access-data-with-drls.test.js similarity index 73% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/access-data-with-drls.test.js rename to site/docs/web5/test/decentralized-web-nodes/access-data-with-drls.test.js index e3cb01903..795e5b39f 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/access-data-with-drls.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/access-data-with-drls.test.js @@ -1,8 +1,14 @@ -import { beforeAll, beforeEach, afterEach, describe, test, expect, vi } from 'vitest'; -import { setUpWeb5 } from '../../../setup-web5'; import { - uploadImage, -} from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/write-to-dwn'; + beforeAll, + beforeEach, + afterEach, + describe, + test, + expect, + vi, +} from 'vitest'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; +import { uploadImage } from '../../../../code-snippets/web5/build/decentralized-web-nodes/write-to-dwn'; describe('Testing upgrade to PWA', () => { let web5, did; @@ -16,12 +22,12 @@ describe('Testing upgrade to PWA', () => { }); beforeEach(() => { - originalFetch = global.fetch; + originalFetch = global.fetch; vi.mock('@web5/api', () => ({ Web5: { - connect: vi.fn(() => Promise.resolve({ web5, did })) - } + connect: vi.fn(() => Promise.resolve({ web5, did })), + }, })); global.fetch = vi.fn((url) => { @@ -30,7 +36,10 @@ describe('Testing upgrade to PWA', () => { return Promise.resolve({ ok: true, status: 200, - blob: () => Promise.resolve(new Blob(['fake image data'], { type: 'image/png' })) + blob: () => + Promise.resolve( + new Blob(['fake image data'], { type: 'image/png' }), + ), }); } return Promise.reject(new Error('URL not found')); @@ -38,7 +47,7 @@ describe('Testing upgrade to PWA', () => { }); afterEach(() => { - vi.resetAllMocks(); + vi.resetAllMocks(); global.fetch = originalFetch; }); @@ -48,7 +57,7 @@ describe('Testing upgrade to PWA', () => { files: [new Blob(['fake image data'], { type: 'image/png' })], }, }; - const record = await uploadImage(mockEvent) + const record = await uploadImage(mockEvent); recordId = record.id; // :snippet-start: drlFetchReadRecord const drl = `https://dweb/${did}/read/records/${recordId}`; @@ -66,17 +75,15 @@ describe('Testing upgrade to PWA', () => { files: [new Blob(['fake image data'], { type: 'image/png' })], }, }; - const record = await uploadImage(mockEvent) + const record = await uploadImage(mockEvent); recordId = record.id; const drl = `https://dweb/${did}/read/records/${recordId}`; const response = await fetch(drl); const imageUrl = URL.createObjectURL(await response.blob()); - return ( - ` + return ` // :snippet-start: renderImageUrlTag uploaded image // :snippet-end: - ` - ) + `; }); }); diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/connect.test.js b/site/docs/web5/test/decentralized-web-nodes/connect.test.js similarity index 95% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/connect.test.js rename to site/docs/web5/test/decentralized-web-nodes/connect.test.js index 0db52fb87..005be6673 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/connect.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/connect.test.js @@ -4,8 +4,8 @@ import { connectWithAgentAndConnectedDid, connectWithSyncConfig, connectToCommunityNode, -} from '../../../../../../code-snippets/api/web5-js'; -import { setUpWeb5 } from '../../../setup-web5'; +} from '../../../../code-snippets/api/web5-js'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; // Mock needed to not conflict with globalThis.web5 vi.mock('@web5/api', () => { diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/delete-from-dwn.test.js b/site/docs/web5/test/decentralized-web-nodes/delete-from-dwn.test.js similarity index 66% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/delete-from-dwn.test.js rename to site/docs/web5/test/decentralized-web-nodes/delete-from-dwn.test.js index c78030c0a..2c89f9c1b 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/delete-from-dwn.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/delete-from-dwn.test.js @@ -1,6 +1,9 @@ import { test, beforeAll, expect, describe } from 'vitest'; -import { createLocalRecord, sendLocalRecordToTarget } from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/send'; -import { setUpWeb5 } from '../../../setup-web5'; +import { + createLocalRecord, + sendLocalRecordToTarget, +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/send'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; let did; @@ -21,9 +24,9 @@ describe('delete-from-dwn', () => { const readResult = await web5.dwn.records.read({ message: { filter: { - recordId: record.id - } - } + recordId: record.id, + }, + }, }); expect(deleteStatus.code).toBe(202); expect(readResult.status.code).toBe(404); @@ -42,27 +45,28 @@ describe('delete-from-dwn', () => { }); test('pruneRecords deletes parents record and its children', async () => { - const { status: protocolStatus, protocol } = await web5.dwn.protocols.configure({ - message: { - definition: { - protocol: 'http://example.com/parent-child', - published: true, - types: { - post: { - schema: 'http://example.com/post', + const { status: protocolStatus, protocol } = + await web5.dwn.protocols.configure({ + message: { + definition: { + protocol: 'http://example.com/parent-child', + published: true, + types: { + post: { + schema: 'http://example.com/post', + }, + comment: { + schema: 'http://example.com/comment', + }, + }, + structure: { + post: { + comment: {}, + }, }, - comment: { - schema: 'http://example.com/comment' - } }, - structure: { - post: { - comment: {} - } - } - } - } - }); + }, + }); const { record: parentRecord } = await web5.dwn.records.create({ data: 'Hello, world!', @@ -70,8 +74,8 @@ describe('delete-from-dwn', () => { protocol: protocol.definition.protocol, protocolPath: 'post', schema: 'http://example.com/post', - dataFormat: 'text/plain' - } + dataFormat: 'text/plain', + }, }); const { record: childRecord } = await web5.dwn.records.create({ @@ -81,33 +85,34 @@ describe('delete-from-dwn', () => { protocolPath: 'post/comment', schema: 'http://example.com/comment', dataFormat: 'text/plain', - parentContextId: parentRecord.contextId - } + parentContextId: parentRecord.contextId, + }, }); // :snippet-start: pruneRecords const { status: deleteStatus } = await parentRecord.delete({ prune: true }); // :snippet-end: - const { records: childrenRecordsAfterDelete } = await web5.dwn.records.query({ - message: { - filter: { - protocol: protocol.definition.protocol, - protocolPath: 'post/comment' - } - } - }); + const { records: childrenRecordsAfterDelete } = + await web5.dwn.records.query({ + message: { + filter: { + protocol: protocol.definition.protocol, + protocolPath: 'post/comment', + }, + }, + }); const { records: parentRecordsAfterDelete } = await web5.dwn.records.query({ message: { filter: { protocol: protocol.definition.protocol, - protocolPath: 'post' - } - } + protocolPath: 'post', + }, + }, }); expect(deleteStatus.code).toBe(202); expect(parentRecordsAfterDelete).to.have.lengthOf(0); expect(childrenRecordsAfterDelete).to.have.lengthOf(0); }); -}); \ No newline at end of file +}); diff --git a/site/docs/web5/test/decentralized-web-nodes/protocol-roles.test.js b/site/docs/web5/test/decentralized-web-nodes/protocol-roles.test.js new file mode 100644 index 000000000..9e5b5934c --- /dev/null +++ b/site/docs/web5/test/decentralized-web-nodes/protocol-roles.test.js @@ -0,0 +1,172 @@ +import { test, beforeAll, expect, describe } from 'vitest'; +import { DidDht } from '@web5/dids'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; +let did; +let aliceDid; + +// :snippet-start: curatorPlaylistProtocolDefinitionJs +const curatorPlaylistProtocolDefinition = { + protocol: 'https://example.com/playlist-protocol', + published: true, + types: { + playlist: { + schema: 'https://example.com/playlist-protocol/schema/playlist', + dataFormats: ['application/json'], + }, + // highlight-start + curator: { + schema: 'https://example.com/playlist-protocol/schema/curator', + dataFormats: ['text/plain'], + }, + // highlight-end + admin: { + schema: 'https://example.com/playlist-protocol/schema/admin', + dataFormats: ['text/plain'], + }, + }, + structure: { + curator: { + // highlight-next-line + $role: true, + }, + admin: { + $role: true, + }, + playlist: { + $actions: [ + { + who: 'anyone', + can: ['read'], + }, + // highlight-start + { + role: 'curator', + can: ['create', 'update'], + }, + // highlight-end + { + role: 'admin', + can: ['create', 'update', 'delete'], + }, + ], + }, + }, +}; +// :snippet-end: + +// :snippet-start: chatProtocolDefinitionJs +const chatProtocolDefinition = { + protocol: 'https://example.com/chat-protocol', + published: true, + types: { + chat: { + schema: 'https://example.com/chat-protocol/schema/chat', + dataFormats: ['application/json'], + }, + }, + structure: { + chat: { + $actions: [ + { who: 'anyone', can: ['create'] }, + { who: 'author', of: 'chat', can: ['read'] }, + { who: 'recipient', of: 'chat', can: ['read'] }, + ], + }, + }, +}; +// :snippet-end: + +describe('Playlist protocol roles', () => { + // connect to web5 beforeAll tests and assign it to web5 variable + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + aliceDid = await DidDht.create(); + }); + + test('install chat protocol', async () => { + // just to make sure chat protocol is working as expected + const { protocol, status } = await web5.dwn.protocols.configure({ + message: { + definition: chatProtocolDefinition, + }, + }); + + // send protocol to remote DWNs immediately + const { status: sendStatus } = await protocol.send(did); + expect(protocol).toBeDefined(); + expect(status.code).to.equal(202); + expect(sendStatus.code).to.equal(202); + }); + + test('install playlist protocol', async () => { + // :snippet-start: installPlaylistProtocolJs + const { protocol, status } = await web5.dwn.protocols.configure({ + message: { + definition: curatorPlaylistProtocolDefinition, + }, + }); + + // send protocol to remote DWNs immediately + const { status: sendStatus } = await protocol.send(did); + // :snippet-end: + expect(protocol).toBeDefined(); + expect(status.code).to.equal(202); + expect(sendStatus.code).to.equal(202); + }); + + test('assign protocol protocol role', async () => { + const aliceDidUri = aliceDid.uri; + // :snippet-start: assignPlaylistRoleJs + const { record, status } = await web5.dwn.records.create({ + message: { + dataFormat: 'text/plain', + protocol: curatorPlaylistProtocolDefinition.protocol, + protocolPath: 'curator', + schema: curatorPlaylistProtocolDefinition.types.curator.schema, + recipient: aliceDidUri, + }, + }); + + const { status: recordSendStatus } = await record.send(did); + // :snippet-end: + + expect(record).toBeDefined(); + expect(status.code).to.equal(202); + expect(recordSendStatus.code).to.equal(202); + }); + + test('Create a record within a role', async () => { + const bobDidUri = did; + // :snippet-start: createRecordInRoleJs + const { record, status } = await web5.dwn.records.create({ + data: JSON.stringify({ + name: 'My Favorite Songs', + description: 'A collection of my all-time favorites', + songs: [], + }), + message: { + dataFormat: 'application/json', + protocol: curatorPlaylistProtocolDefinition.protocol, + protocolPath: 'playlist', + protocolRole: 'curator', + schema: curatorPlaylistProtocolDefinition.types.playlist.schema, + recipient: bobDidUri, + }, + }); + // :snippet-end: + const expectedPlaylistData = { + name: 'My Favorite Songs', + description: 'A collection of my all-time favorites', + songs: [], + }; + expect(await record.data.text()).to.equal( + JSON.stringify(expectedPlaylistData), + ); + expect(record).toBeDefined(); + expect(status.code).to.equal(202); + }); +}); diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/publish-records.test.js b/site/docs/web5/test/decentralized-web-nodes/publish-records.test.js similarity index 83% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/publish-records.test.js rename to site/docs/web5/test/decentralized-web-nodes/publish-records.test.js index b5e878d94..f7c75a3b1 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/publish-records.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/publish-records.test.js @@ -3,8 +3,8 @@ import { test, beforeAll, expect, describe } from 'vitest'; import { createPublishedRecord, createRecordWithDatePublished, -} from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/publishing-records'; -import { setUpWeb5 } from '../../../setup-web5'; +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/publishing-records'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; let did; diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/query-from-dwn.test.js b/site/docs/web5/test/decentralized-web-nodes/query-from-dwn.test.js similarity index 70% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/query-from-dwn.test.js rename to site/docs/web5/test/decentralized-web-nodes/query-from-dwn.test.js index 8eb807354..c5f3d33a2 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/query-from-dwn.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/query-from-dwn.test.js @@ -7,8 +7,8 @@ import { queryRecordsFromDid, queryRecordWithParentId, queryFromDwnByProtocolPath, -} from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/query-from-dwn'; -import { setUpWeb5 } from '../../../setup-web5'; +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/query-from-dwn'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; let did; @@ -52,47 +52,47 @@ describe('query-from-dwn', () => { test('playlistProtocolDefinition can be configured', async () => { // :snippet-start: playlistProtocolDefinition const playlistProtocolDefinition = { - protocol: "https://playlist.org/protocol", + protocol: 'https://playlist.org/protocol', published: true, types: { playlist: { - schema: "https://schema.org/MusicPlaylist", - dataFormats: ["application/json"], + schema: 'https://schema.org/MusicPlaylist', + dataFormats: ['application/json'], }, audio: { - schema: "https://schema.org/AudioObject", - dataFormats: ["audio/mp3"], + schema: 'https://schema.org/AudioObject', + dataFormats: ['audio/mp3'], }, video: { - schema: "https://schema.org/VideoObject", - dataFormats: ["video/mp4"], + schema: 'https://schema.org/VideoObject', + dataFormats: ['video/mp4'], }, }, structure: { playlist: { $actions: [ - { who: "anyone", can: ["create"] }, - { who: "author", of: "playlist", can: ["read"] }, - { who: "recipient", of: "playlist", can: ["read"] }, + { who: 'anyone', can: ['create'] }, + { who: 'author', of: 'playlist', can: ['read'] }, + { who: 'recipient', of: 'playlist', can: ['read'] }, ], audio: { $actions: [ - { who: "anyone", can: ["create"] }, - { who: "author", of: "audio", can: ["read"] }, - { who: "recipient", of: "audio", can: ["read"] }, + { who: 'anyone', can: ['create'] }, + { who: 'author', of: 'audio', can: ['read'] }, + { who: 'recipient', of: 'audio', can: ['read'] }, ], }, video: { $actions: [ - { who: "anyone", can: ["create"] }, - { who: "author", of: "video", can: ["read"] }, - { who: "recipient", of: "video", can: ["read"] }, - ] + { who: 'anyone', can: ['create'] }, + { who: 'author', of: 'video', can: ['read'] }, + { who: 'recipient', of: 'video', can: ['read'] }, + ], }, }, - } + }, }; - // :snippet-end: + // :snippet-end: const response = await web5.dwn.protocols.configure({ message: { definition: playlistProtocolDefinition, diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/read-from-dwn.test.js b/site/docs/web5/test/decentralized-web-nodes/read-from-dwn.test.js similarity index 68% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/read-from-dwn.test.js rename to site/docs/web5/test/decentralized-web-nodes/read-from-dwn.test.js index cc98e6752..401f5c995 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/read-from-dwn.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/read-from-dwn.test.js @@ -1,6 +1,6 @@ import { test, beforeAll, expect, describe } from 'vitest'; -import { readFromDwn } from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/read-from-dwn'; -import { setUpWeb5 } from '../../../setup-web5'; +import { readFromDwn } from '../../../../code-snippets/web5/build/decentralized-web-nodes/read-from-dwn'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/records.test.js b/site/docs/web5/test/decentralized-web-nodes/records.test.js similarity index 88% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/records.test.js rename to site/docs/web5/test/decentralized-web-nodes/records.test.js index 2cc48145f..0fa5b02b0 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/records.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/records.test.js @@ -3,8 +3,8 @@ import { test, beforeAll, expect, describe } from 'vitest'; import { readRecordFromId, deleteRecordFromDid, -} from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/records'; -import { setUpWeb5 } from '../../../setup-web5'; +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/records'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; let record; @@ -18,7 +18,6 @@ describe('records', () => { }); describe('tests for /api/web5-js/dwn/records', async () => { - test('readRecordFromId reads a record', async () => { const text = 'readRecordFromId'; const { record: textRecord } = await web5.dwn.records.create({ @@ -33,7 +32,7 @@ describe('records', () => { test('deleteRecordFromDid deletes a record', async () => { const { record } = await web5.dwn.records.create({ - data: "delete me", + data: 'delete me', message: { dataFormat: 'text/plain', }, diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/send.test.js b/site/docs/web5/test/decentralized-web-nodes/send.test.js similarity index 87% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/send.test.js rename to site/docs/web5/test/decentralized-web-nodes/send.test.js index c4ff21ec5..4938c5848 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/send.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/send.test.js @@ -7,8 +7,8 @@ import { sendRecordToRemoteDWNs, sendProtocolToRemoteDWNs, sendRecordToDWNOfRecipient, -} from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/send'; -import { setUpWeb5 } from '../../../setup-web5'; +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/send'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; let did; @@ -38,9 +38,7 @@ describe('send', () => { }, structure: { myobject: { - $actions: [ - { who: 'anyone', can: ['create', 'read'] }, - ], + $actions: [{ who: 'anyone', can: ['create', 'read'] }], }, }, }; @@ -71,9 +69,7 @@ describe('send', () => { }, structure: { myobject: { - $actions: [ - { who: 'anyone', can: ['create', 'read'] }, - ], + $actions: [{ who: 'anyone', can: ['create', 'read'] }], }, }, }; diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/update-dwn.test.js b/site/docs/web5/test/decentralized-web-nodes/update-dwn.test.js similarity index 81% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/update-dwn.test.js rename to site/docs/web5/test/decentralized-web-nodes/update-dwn.test.js index 62699f63b..b898e1613 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/update-dwn.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/update-dwn.test.js @@ -1,6 +1,6 @@ import { test, beforeAll, expect, describe } from 'vitest'; -import { updateDwnRecord } from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/update-dwn'; -import { setUpWeb5 } from '../../../setup-web5'; +import { updateDwnRecord } from '../../../../code-snippets/web5/build/decentralized-web-nodes/update-dwn'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/use-identity-agents.test.js b/site/docs/web5/test/decentralized-web-nodes/use-identity-agents.test.js similarity index 79% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/use-identity-agents.test.js rename to site/docs/web5/test/decentralized-web-nodes/use-identity-agents.test.js index d43f193bc..4712ccb3b 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/use-identity-agents.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/use-identity-agents.test.js @@ -1,6 +1,6 @@ import { test, expect, describe } from 'vitest'; -import { getDwnEndpoints } from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/use-identity-agents'; -import { setUpIdentityManager } from '../../../setup-web5'; +import { getDwnEndpoints } from '../../../../code-snippets/web5/build/decentralized-web-nodes/use-identity-agents'; +import { setUpIdentityManager } from '../../../test-utils/setup-web5'; let agent; diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/write-to-dwn.test.js b/site/docs/web5/test/decentralized-web-nodes/write-to-dwn.test.js similarity index 80% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/write-to-dwn.test.js rename to site/docs/web5/test/decentralized-web-nodes/write-to-dwn.test.js index 6ead345c0..ffec6c274 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/write-to-dwn.test.js +++ b/site/docs/web5/test/decentralized-web-nodes/write-to-dwn.test.js @@ -6,8 +6,8 @@ import { uploadImage, uploadFile, createMixedRecord, -} from '../../../../../../code-snippets/web5/build/decentralized-web-nodes/write-to-dwn'; -import { setUpWeb5 } from '../../../setup-web5'; +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/write-to-dwn'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; let web5; let did; @@ -25,12 +25,12 @@ describe('write-to-dwn', () => { // Create a plain text record const { record } = await web5.dwn.records.create({ data: { - content: "Hello Web5", - description: "Keep Building!" + content: 'Hello Web5', + description: 'Keep Building!', }, message: { - dataFormat: 'application/json' - } + dataFormat: 'application/json', + }, }); // :snippet-end: expect(record).toBeDefined(); @@ -41,14 +41,14 @@ describe('write-to-dwn', () => { // Create a JSON record const { record } = await web5.dwn.records.create({ data: { - content: "Hello Web5", - description: "Keep Building!" + content: 'Hello Web5', + description: 'Keep Building!', }, message: { - dataFormat: 'application/json' - } + dataFormat: 'application/json', + }, }); - // :snippet-end: + // :snippet-end: expect(record).toBeDefined(); }); @@ -61,12 +61,12 @@ describe('write-to-dwn', () => { // :snippet-start: uploadImage // Create a blob record async function upload(event) { - const blob = new Blob(event.currentTarget.files, { type: "image/png" }); + const blob = new Blob(event.currentTarget.files, { type: 'image/png' }); const { record } = await web5.dwn.records.create({ data: blob, message: { - dataFormat: "image/png" - } + dataFormat: 'image/png', + }, }); return record; } @@ -90,9 +90,9 @@ describe('write-to-dwn', () => { const { status: fileStatus, record } = await web5.dwn.records.create({ data: file, message: { - schema: "https://schema.org/path/to/schema", - dataFormat: "application/octet-stream" - } + schema: 'https://schema.org/path/to/schema', + dataFormat: 'application/octet-stream', + }, }); return record; } @@ -115,22 +115,22 @@ describe('write-to-dwn', () => { base64Image = btoa( new Uint8Array(binaryImage).reduce( (data, byte) => data + String.fromCharCode(byte), - "" - ) + '', + ), ); } const messageData = { username, message: messageText, - image: base64Image + image: base64Image, }; const { record } = await web5.dwn.records.create({ data: messageData, message: { - schema: "http://schema-registry.org/message", - dataFormat: "application/json" + schema: 'http://schema-registry.org/message', + dataFormat: 'application/json', }, }); return record; @@ -164,24 +164,23 @@ test('createRecordFrom creates a record from an existing record', async () => { expect(newRecordDataText).toBe('I am a new version of the original record!'); }); -test('createRecordWithTags creates a record with tags', async() => { - +test('createRecordWithTags creates a record with tags', async () => { // :snippet-start: createRecordWithTags // Creates a record with tags const { record } = await web5.dwn.records.create({ - data: "Chocolate Chip Cookies", + data: 'Chocolate Chip Cookies', message: { dataFormat: 'application/json', - tags : { + tags: { dishType: 'Dessert', - dietaryRestriction: 'Contains Gluten' - } - } + dietaryRestriction: 'Contains Gluten', + }, + }, }); - // :snippet-end: + // :snippet-end: expect(record.tags).to.exist; expect(record.tags).to.deep.equal({ dishType: 'Dessert', - dietaryRestriction: 'Contains Gluten' + dietaryRestriction: 'Contains Gluten', }); -}) \ No newline at end of file +}); diff --git a/site/testsuites/testsuite-javascript/__tests__/quickstart.test.js b/site/docs/web5/test/quickstart.test.js similarity index 93% rename from site/testsuites/testsuite-javascript/__tests__/quickstart.test.js rename to site/docs/web5/test/quickstart.test.js index 7e7486e27..b598c5b85 100644 --- a/site/testsuites/testsuite-javascript/__tests__/quickstart.test.js +++ b/site/docs/web5/test/quickstart.test.js @@ -1,5 +1,5 @@ import { test, beforeAll, expect, describe } from 'vitest'; -import { setUpWeb5 } from './setup-web5'; +import { setUpWeb5 } from '../../test-utils/setup-web5'; import { VerifiableCredential } from '@web5/credentials'; // This is the web5 instance that will be referred to for all tests. This comes back as a result from Web5.connect() being used in the didCreate function. @@ -24,7 +24,9 @@ describe('/site/tests/quickstart.test.js', async () => { test('getBearerDid returns a bearer identity', async () => { // :snippet-start: getBearerDid - const { did: aliceBearerDid } = await web5.agent.identity.get({ didUri: aliceDid }); + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); // :snippet-end: expect(aliceBearerDid.uri).toBe(aliceDid); }); @@ -38,15 +40,17 @@ describe('/site/tests/quickstart.test.js', async () => { data: { name: 'Alice Smith', completionDate: new Date().toISOString(), - expertiseLevel: 'Beginner' - } + expertiseLevel: 'Beginner', + }, }); // :snippet-end: expect(vc.vcDataModel.issuer).toBe(aliceDid); }); test('signQuickstartVc returns a jwt', async () => { - const { did: aliceBearerDid } = await web5.agent.identity.get({ didUri: aliceDid }); + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); class Web5QuickstartCompletionCredential { constructor(name, completionDate, expertiseLevel) { this.name = name; @@ -63,7 +67,7 @@ describe('/site/tests/quickstart.test.js', async () => { 'Alice Smith', '2024-05-22', 'Beginner', - ) + ), }); // :snippet-start: signQuickstartVc const signedVc = await vc.sign({ did: aliceBearerDid }); @@ -72,7 +76,9 @@ describe('/site/tests/quickstart.test.js', async () => { }); test('writeQuickstartVcToDwn writes a signed vc to dwn', async () => { - const { did: aliceBearerDid } = await web5.agent.identity.get({ didUri: aliceDid }); + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); class Web5QuickstartCompletionCredential { constructor(name, completionDate, expertiseLevel) { this.name = name; @@ -89,7 +95,7 @@ describe('/site/tests/quickstart.test.js', async () => { 'Alice Smith', '2024-05-22', 'Beginner', - ) + ), }); const signedVc = await vc.sign({ did: aliceBearerDid }); // :snippet-start: writeQuickstartVcToDwn @@ -98,15 +104,17 @@ describe('/site/tests/quickstart.test.js', async () => { message: { schema: 'Web5QuickstartCompletionCredential', dataFormat: 'application/vc+jwt', - published: true - } + published: true, + }, }); // :snippet-end: expect(record.author).toBe(aliceDid); }); test('readQuickstartVc reads jwt from DWN', async () => { - const { did: aliceBearerDid } = await web5.agent.identity.get({ didUri: aliceDid }); + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); class Web5QuickstartCompletionCredential { constructor(name, completionDate, expertiseLevel) { this.name = name; @@ -123,7 +131,7 @@ describe('/site/tests/quickstart.test.js', async () => { 'Alice Smith', '2024-05-22', 'Beginner', - ) + ), }); const signedVc = await vc.sign({ did: aliceBearerDid }); @@ -132,8 +140,8 @@ describe('/site/tests/quickstart.test.js', async () => { message: { schema: 'Web5QuickstartCompletionCredential', dataFormat: 'application/vc+jwt', - published: true - } + published: true, + }, }); // :snippet-start: readQuickstartVc const readSignedVc = await record.data.text(); @@ -142,7 +150,9 @@ describe('/site/tests/quickstart.test.js', async () => { }); test('parseQuickstartVc reads jwt from DWN', async () => { - const { did: aliceBearerDid } = await web5.agent.identity.get({ didUri: aliceDid }); + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); class Web5QuickstartCompletionCredential { constructor(name, completionDate, expertiseLevel) { this.name = name; @@ -159,7 +169,7 @@ describe('/site/tests/quickstart.test.js', async () => { 'Alice Smith', '2024-05-22', 'Beginner', - ) + ), }); const signedVc = await vc.sign({ did: aliceBearerDid }); @@ -169,8 +179,8 @@ describe('/site/tests/quickstart.test.js', async () => { message: { schema: 'Web5QuickstartCompletionCredential', dataFormat: 'application/vc+jwt', - published: true - } + published: true, + }, }); const readSignedVc = await record.data.text(); @@ -179,5 +189,4 @@ describe('/site/tests/quickstart.test.js', async () => { // :snippet-end: expect(parsedVc.vcDataModel.issuer).toBe(aliceDid); }); - -}); \ No newline at end of file +}); diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/fan-club-vc.test.js b/site/docs/web5/test/verifiable-credentials/fan-club-vc.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/fan-club-vc.test.js rename to site/docs/web5/test/verifiable-credentials/fan-club-vc.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/fan.club.sdks.bash b/site/docs/web5/test/verifiable-credentials/fan.club.sdks.bash similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/fan.club.sdks.bash rename to site/docs/web5/test/verifiable-credentials/fan.club.sdks.bash diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/jwt-to-vc.test.js b/site/docs/web5/test/verifiable-credentials/jwt-to-vc.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/jwt-to-vc.test.js rename to site/docs/web5/test/verifiable-credentials/jwt-to-vc.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-definition.test.js b/site/docs/web5/test/verifiable-credentials/presentation-definition.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-definition.test.js rename to site/docs/web5/test/verifiable-credentials/presentation-definition.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-exchange.test.js b/site/docs/web5/test/verifiable-credentials/presentation-exchange.test.js similarity index 85% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-exchange.test.js rename to site/docs/web5/test/verifiable-credentials/presentation-exchange.test.js index 19237e945..8c42d9ff2 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-exchange.test.js +++ b/site/docs/web5/test/verifiable-credentials/presentation-exchange.test.js @@ -3,9 +3,12 @@ import { test, describe, expect, beforeAll } from 'vitest'; import { pex_createPresentationFromCredentials, pex_getLoanAppPresentationDefinition, -} from '../../../../../../code-snippets/web5/build/verifiable-credentials/presentation-exchange'; -import { PresentationExchange, VerifiablePresentation } from '@web5/credentials'; -import { DidDht } from '@web5/dids' +} from '../../../../code-snippets/web5/build/verifiable-credentials/presentation-exchange'; +import { + PresentationExchange, + VerifiablePresentation, +} from '@web5/credentials'; +import { DidDht } from '@web5/dids'; const pd = await pex_getLoanAppPresentationDefinition(); @@ -19,31 +22,30 @@ describe('Presentation Exchange Process', () => { let verifiablePresentation; beforeAll(async () => { - signedEmploymentVcJwt = 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyI3o2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJFbXBsb3ltZW50Q3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjcyNDhiOTkyLTkwOTYtNDk2NS1hMGVjLTc3ZDhhODNhMWRmYiIsImlzc3VlciI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDoxMToyNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlEMTR4UmY0cTJNWlh1ZWY2X2ZXYnBGbVlTUG94dGFxTkp1SmdEMG96Wl84UTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWVubGFNbVYzTlhKeVVXdFVjbUV3WlZsVk16WlBTblJzTURCbFJWZHhhalZhV0dkNmNEZFpSVTVKUVNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpQZDJZMFQyMUViamxKWm5SNFdYWnBkRTFHWm1jMVVXeDVMVVV6VWs1b1dsUkdPVlpFTWtnNVQzVjNJaXdpZVNJNkltUnZjVmxtV2s1c1NtRlRNVll4U201bU9HdEZObEF6VkRsd2QzaDNla3hFVTJWc1ZqTlRUa2s1U2xFaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xEWm05bVFUQkpVbU5uY2tWdVVHZHdRbU5RV1ZsV2VFWlliR0pTYjJRd2RVNWZRVkJwTkVrNUxVRmZRU0o5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFtd3pWWG80VldGT2REZGxlREJKYjJJMFJFNXNhbFJGVmpaelQwTmtjbFJ3TWxvNE5FTkJPVFJPUWtFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVOWk9WRldZbWRKYkUxemRraEZYMVJtTld4a1MxQjBkR3d3WVV4blNrdHNSbmt6Vms0d2QzQTJhVFpSSW4xOSIsImVtcGxveW1lbnRTdGF0dXMiOiJlbXBsb3llZCJ9fX0.Sazc8Ndhs-NKjxvtVMKeC9dxjEkI26fVsp2kFNWM-SYLtxMzKvl5ffeWd81ysHgPmBBSk2ar4dMqGgUsyM4gAQ'; signedNameAndDobVcJwt = 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0I3o2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJOYW1lQW5kRG9iQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjliZjM2YzY5LTI0ODAtNDllZC1iMTYyLTRlZDEwOWE3MTc3NyIsImlzc3VlciI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0IiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDowNjowMVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlDS2o2M0FyZlBGcEpsb2lTd3gxQUhxVWtpWlNoSDZGdnZoSzRaTl9fZDFtQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWNscFdXbTVJVkVrNWFEWkJUVmxVV0dwT01HcFhTVkYwTTI5ak4xTnJTeTF4Y2kxcVVuSTBUalEzUlNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpaVDFwRE5WSmlUMHQ1T0dadVVUWTJVWEZPUkc5aldFMXZPVXhUZEdNNVYyOWthMHd0ZFZCZlExQnZJaXdpZVNJNklsWnZZM0UxVERodFozQlhXVTFrYjFwS1JrWlJUa1ZDT0hsR0xXTndkRWQzZFdkcFRWVm5hR2t6Y21jaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCTXpSMlMzb3llVmswZVV4dGRDMUdabkJuYWpWbGFFRm1ZWFI1YzFOa2MwNVNWbVpMYkhwUWRqTjVkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFF6ZGZjMXBzTW1wMVVXNUdhRVJIV1RSb2NFVTRiMlF4YVU5MWRuZG1PVFJ5TVVkbk9HMWFWbVJCVmxFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVKdU5sTnJiSEpWYzNKdVFuaFJPVXBqVXkxTlNVaGtWelYwTXpRM1MxWjNaMXBwVEZwMFQwcDRRVkYzSW4xOSIsIm5hbWUiOiJhbGljZSBib2IiLCJkYXRlT2ZCaXJ0aCI6IjEwLTAxLTE5OTAifX19.mNCDv_JntH-wZpYONKNL58UbOWaYXCYJO_HPI_WVlSgwzo6dhYmV_9qtpFKd_exFb-aaEYPeSE43twWlrJeSBg'; - + credentials = [signedEmploymentVcJwt, signedNameAndDobVcJwt]; - selectedCredentials = credentials + selectedCredentials = credentials; holderDid = await DidDht.create(); - presentationResult = PresentationExchange.createPresentationFromCredentials({ - vcJwts: selectedCredentials, - presentationDefinition: presentationDefinition - }); + presentationResult = PresentationExchange.createPresentationFromCredentials( + { + vcJwts: selectedCredentials, + presentationDefinition: presentationDefinition, + }, + ); - verifiablePresentation = await VerifiablePresentation.create({ + verifiablePresentation = await VerifiablePresentation.create({ holder: holderDid.uri, vcJwts: [selectedCredentials], - additionalData: { presentationResult } - }); - + additionalData: { presentationResult }, + }); }); - const presentationDefinition = { id: 'presDefIdloanAppVerification123', name: 'Loan Application Employment Verification', @@ -90,15 +92,15 @@ describe('Presentation Exchange Process', () => { { path: ['$.credentialSubject.name'], filter: { - type: 'string' - } - } - ] - } - } - ] + type: 'string', + }, + }, + ], + }, + }, + ], }; - + test('getLoanAppPresentationDefinition returns a presentation definition', async () => { // :snippet-start: getLoanAppPresentationDefinition const presentationDefinition = { @@ -147,13 +149,13 @@ describe('Presentation Exchange Process', () => { { path: ['$.credentialSubject.name'], filter: { - type: 'string' - } - } - ] - } - } - ] + type: 'string', + }, + }, + ], + }, + }, + ], }; // :snippet-end: expect(presentationDefinition).toBeDefined(); @@ -162,14 +164,14 @@ describe('Presentation Exchange Process', () => { expect(presentationDefinition.input_descriptors.length).toBe(3); }); test('selectCredentialsForPex selects VCs that match presentation defintion', async () => { - const allCredentials = credentials + const allCredentials = credentials; // :snippet-start: selectCredentialsForPex const selectedCredentials = PresentationExchange.selectCredentials({ vcJwts: allCredentials, - presentationDefinition: presentationDefinition + presentationDefinition: presentationDefinition, }); // :snippet-end: - + expect(selectedCredentials).toBeDefined(); expect(selectedCredentials).toBeInstanceOf(Array); expect.soft(selectedCredentials.length).toBe(2); @@ -178,49 +180,57 @@ describe('Presentation Exchange Process', () => { }); test('satisfiesPresentationDefinitionForPex checks if VCs satisfy PD', async () => { - const selectedCredentials = credentials + const selectedCredentials = credentials; expect(() => { - // :snippet-start: satisfiesPresentationDefinitionForPex - try { - PresentationExchange.satisfiesPresentationDefinition({ - vcJwts: selectedCredentials, - presentationDefinition: presentationDefinition - }); - } catch (err) { - //Handle errors here - - } - // :snippet-end: + // :snippet-start: satisfiesPresentationDefinitionForPex + try { + PresentationExchange.satisfiesPresentationDefinition({ + vcJwts: selectedCredentials, + presentationDefinition: presentationDefinition, + }); + } catch (err) { + //Handle errors here + } + // :snippet-end: }).not.toThrow(); }); test('createPresentationFromCredentialsForPex creates a presentation result', async () => { // :snippet-start: createPresentationFromCredentialsForPex - const presentationResult = PresentationExchange.createPresentationFromCredentials({ - vcJwts: selectedCredentials, - presentationDefinition: presentationDefinition - }); + const presentationResult = + PresentationExchange.createPresentationFromCredentials({ + vcJwts: selectedCredentials, + presentationDefinition: presentationDefinition, + }); const vp = await VerifiablePresentation.create({ holder: holderDid.uri, vcJwts: [selectedCredentials], - additionalData: { presentationResult } + additionalData: { presentationResult }, }); // :snippet-end: expect(presentationResult).toBeDefined(); expect.soft(presentationResult).toHaveProperty('presentation'); - expect.soft(presentationResult.presentation).toHaveProperty('presentation_submission'); - expect.soft(presentationResult.presentation).toHaveProperty('verifiableCredential'); - expect.soft(presentationResult.presentation.type).toContain('VerifiablePresentation'); - expect.soft(vp).toHaveProperty('vpDataModel') - } - ); + expect + .soft(presentationResult.presentation) + .toHaveProperty('presentation_submission'); + expect + .soft(presentationResult.presentation) + .toHaveProperty('verifiableCredential'); + expect + .soft(presentationResult.presentation.type) + .toContain('VerifiablePresentation'); + expect.soft(vp).toHaveProperty('vpDataModel'); + }); test('validPresentationSubmissionForPex check if the presention submission is valid', async () => { - const presentationResult = await pex_createPresentationFromCredentials(credentials, pd); + const presentationResult = await pex_createPresentationFromCredentials( + credentials, + pd, + ); // :snippet-start: validPresentationSubmissionForPex const submissionCheck = PresentationExchange.validateSubmission({ - presentationSubmission: presentationResult.presentationSubmission + presentationSubmission: presentationResult.presentationSubmission, }); // :snippet-end: expect(submissionCheck.length).toBe(1); @@ -235,5 +245,4 @@ describe('Presentation Exchange Process', () => { // :snippet-end: expect.soft(typeof vpJwt).toBe('string'); }); - }); diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/required.sdks.bash b/site/docs/web5/test/verifiable-credentials/required.sdks.bash similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/required.sdks.bash rename to site/docs/web5/test/verifiable-credentials/required.sdks.bash diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/revoke-credentials.test.js b/site/docs/web5/test/verifiable-credentials/revoke-credentials.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/revoke-credentials.test.js rename to site/docs/web5/test/verifiable-credentials/revoke-credentials.test.js diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/vc-issuance.test.js b/site/docs/web5/test/verifiable-credentials/vc-issuance.test.js similarity index 82% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/vc-issuance.test.js rename to site/docs/web5/test/verifiable-credentials/vc-issuance.test.js index 9d03c9a80..a73ce639f 100644 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/vc-issuance.test.js +++ b/site/docs/web5/test/verifiable-credentials/vc-issuance.test.js @@ -1,9 +1,7 @@ import { test, expect, describe } from 'vitest'; import { DidDht } from '@web5/dids'; import { VerifiableCredential } from '@web5/credentials'; -import { - signCredential, -} from '../../../../../../code-snippets/web5/build/verifiable-credentials/vc-issuance'; +import { signCredential } from '../../../../code-snippets/web5/build/verifiable-credentials/vc-issuance'; const employer = await DidDht.create(); const employee = await DidDht.create(); @@ -17,10 +15,10 @@ describe('issue a credential', () => { subject: employee.uri, expirationDate: '2023-09-30T12:34:56Z', data: { - "position": "Software Developer", - "startDate": "2023-04-01T12:34:56Z", - "employmentStatus": "Contractor" - } + position: 'Software Developer', + startDate: '2023-04-01T12:34:56Z', + employmentStatus: 'Contractor', + }, }); // :snippet-end: @@ -48,6 +46,8 @@ describe('issue a credential', () => { .toHaveProperty('employmentStatus', 'Contractor'); expect(vc_jwt_employment).toBeDefined(); - expect(vc_jwt_employment).toMatch(/^[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+$/); + expect(vc_jwt_employment).toMatch( + /^[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+$/, + ); }); -}); \ No newline at end of file +}); diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/verify-vc.test.js b/site/docs/web5/test/verifiable-credentials/verify-vc.test.js similarity index 100% rename from site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/verify-vc.test.js rename to site/docs/web5/test/verifiable-credentials/verify-vc.test.js diff --git a/site/docusaurus.config.js b/site/docusaurus.config.js index d0f3d8a1e..5062898c3 100644 --- a/site/docusaurus.config.js +++ b/site/docusaurus.config.js @@ -74,6 +74,26 @@ let config = { // sidebarPath: require.resolve('./learn-sidebars.js'), // }, // ], + [ + '@docusaurus/plugin-content-docs', + { + id: 'tbdex', + path: 'docs/tbdex', + routeBasePath: 'docs/tbdex', + sidebarPath: require.resolve('./tbdex-sidebars.js'), + breadcrumbs: false, + }, + ], + [ + '@docusaurus/plugin-content-docs', + { + id: 'web5', + path: 'docs/web5', + routeBasePath: 'docs/web5', + sidebarPath: require.resolve('./web5-sidebars.js'), + breadcrumbs: false, + }, + ], ], scripts: [ { @@ -95,6 +115,8 @@ let config = { /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { + path: 'docs/common', + routeBasePath: 'docs', sidebarPath: require.resolve('./sidebars.js'), breadcrumbs: false, }, @@ -182,6 +204,11 @@ let config = { to: '#ask', label: 'Ask 🤖', }, + { + label: 'Web5 SDK', + docsPluginId: 'web5', + type: 'docsVersionDropdown', + }, // { // to: 'https://tbd.website', // position: 'right', diff --git a/site/sidebars.js b/site/sidebars.js index ef1bd11a1..ea85aefc5 100644 --- a/site/sidebars.js +++ b/site/sidebars.js @@ -14,7 +14,13 @@ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], + tutorialSidebar: [ + { type: 'doc', id: 'index', label: 'Getting Started' }, + { type: 'link', href: './web5', label: 'Web5 SDK' }, + { type: 'link', href: './tbdex', label: 'tbDEX SDK' }, + { type: 'doc', id: 'api', label: 'API Reference Guides' }, + { type: 'doc', id: 'glossary', label: 'Glossary' }, + ], // But you can create a sidebar manually /* diff --git a/site/src/css/custom.css b/site/src/css/custom.css index 6462508ed..64a47348a 100644 --- a/site/src/css/custom.css +++ b/site/src/css/custom.css @@ -14,6 +14,8 @@ @import 'illustration.css'; @import 'web5-quickstart.css'; @import 'api.css'; +@import 'tbdex.css'; +@import 'web5.css'; root { --color-black: #000000; diff --git a/site/src/css/tbdex.css b/site/src/css/tbdex.css new file mode 100644 index 000000000..d49e67cf3 --- /dev/null +++ b/site/src/css/tbdex.css @@ -0,0 +1,874 @@ +/* Navbar and main */ + +.docs-wrapper #__docusaurus a:not(.table-of-contents__link, .navbar__link) { + text-decoration: none; + font-weight: 600; +} + +.docs-wrapper .DocSearch-Modal { + margin-top: 120px; +} + +.docs-wrapper #__docusaurus { + --color-white: #c6c6c6; +} + +.docs-wrapper #__docusaurus .navbar, +.docs-wrapper #__docusaurus .main-wrapper { + max-width: unset; +} + +.docs-wrapper #__docusaurus .main-wrapper { + margin-top: 0; +} + +/* Sidebar */ +.docs-wrapper #__docusaurus aside .menu__list { + gap: 0; +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item .menu__link { + font-weight: 500; + padding-block: 0.75rem; + color: var(--color-yellow); + text-decoration: none; + font-family: var(--tbd-font-mono); + letter-spacing: 0; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-link>.menu__link.menu__link--active, +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible--active .menu__link--sublist.menu__link--active, +.docs-wrapper #__docusaurus aside .menu__list-item-collapsible--active { + background-color: var(--color-yellow); + color: var(--color-black) !important; +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible--active .menu__link--sublist.menu__link--active:hover, +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category .menu__list-item-collapsible:hover .menu__link--sublist.menu__link--active { + color: var(--color-blue) !important; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category .menu__list-item-collapsible:hover { + background-color: var(--ifm-menu-color-background-hover); +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__caret:before { + content: url('/img/arrow-blue.svg'); + background: none; +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__caret:before, +.docs-wrapper #__docusaurus aside .menu__list .menu__link--sublist-caret:after { + filter: brightness(0) var(--color-primary-yellow-filter); +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible--active .menu__caret:before { + filter: brightness(0); +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible:hover .menu__caret:before, +.docs-wrapper #__docusaurus aside .menu__list .menu__link--sublist-caret:hover:after { + filter: none; +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible:hover .menu__link.menu__link--sublist { + color: var(--color-blue); +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__caret:hover { + height: auto; +} + +/* Submenu */ + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible--active~.menu__list { + margin: 0; +} + +.docs-wrapper #__docusaurus aside .menu__list-item-collapsible~.menu__list { + padding-left: 0; + margin: 0; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category-level-1 .menu__list-item-collapsible~.menu__list .menu__link { + padding-left: 2.5rem; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category-level-2 .menu__list-item-collapsible~.menu__list .menu__link { + padding-left: 3rem; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category-level-3 .menu__list-item-collapsible~.menu__list .menu__link { + padding-left: 3.5rem; +} + +.docs-wrapper #__docusaurus footer { + border-top: 1px solid var(--ifm-toc-border-color); + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.docs-wrapper #__docusaurus footer>div { + overflow-x: hidden; +} + +.docs-wrapper #__docusaurus h1~p, +.docs-wrapper #__docusaurus h2:not(.theme-card h2, .community-card h2)~p, +.docs-wrapper #__docusaurus h3:not(.theme-card h3, .community-card h3)~p, +.docs-wrapper #__docusaurus p { + font-family: var(--tbd-font-mono); + letter-spacing: 0; + line-height: 1.75rem; +} + +.docs-wrapper #__docusaurus p:not(.docs-section p, + .alert p, + .figure-container p, + .api-card p, + .search-widget p) { + font-size: 1.125rem; + color: var(--color-white); +} + +.docs-wrapper #__docusaurus article .markdown li { + font-family: var(--tbd-font-mono); + letter-spacing: 0; + color: var(--color-white); +} + +.docs-wrapper #__docusaurus .api-card { + border: 2px solid var(--color-yellow); + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; +} + +.docs-wrapper #__docusaurus .api-card p { + font-size: 1rem; +} + +.api-card-icon { + display: flex; + justify-content: start; + gap: 20px; + padding-top: 20px; + vertical-align: bottom; +} + +.api-card-icon img { + width: 50px; +} + +.docs-wrapper #__docusaurus h2, +.docs-wrapper #__docusaurus .primary-theme-card h2 { + font-size: 2rem; +} + +.docs-wrapper #__docusaurus .theme-card { + border-width: 2px; +} + +.docs-wrapper #__docusaurus .theme-card p:not(.primary-theme-card p) { + font-size: 1rem; +} + +.docs-wrapper #__docusaurus .theme-card p { + max-width: 70%; + font-family: var(--tbd-font-mono); + letter-spacing: 0; +} + +.docs-wrapper #__docusaurus .theme-card img { + height: fit-content; +} + +.docs-wrapper #__docusaurus .community-card h3 { + font-size: 1.25rem; + margin-bottom: 12px; +} + +.docs-wrapper #__docusaurus .community-card p { + font-size: 1rem; +} + +.docs-wrapper #__docusaurus .explore-card h3 { + font-size: 1rem; + margin-block: 0; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert { + border-radius: 0; + margin-bottom: 2rem; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert code, +.docs-wrapper #__docusaurus code:not(pre code, h4 code) { + background: #626262; + color: #fff; +} + +.docs-wrapper #__docusaurus h4>code { + background: #383838; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert--info { + background: #0b1a1f; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert--secondary { + background: #3d3d3d; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert--secondary p, +.docs-wrapper #__docusaurus .theme-admonition.alert--secondary p:last-child { + margin: 0; +} + +.docs-wrapper #__docusaurus .theme-admonition p, +.docs-wrapper #__docusaurus .theme-admonition li { + font-size: 1rem; +} + +.docs-wrapper #__docusaurus .theme-admonition p:last-child { + margin-bottom: 1.5rem; +} + +.docs-wrapper #__docusaurus .theme-admonition .admonitionHeading_node_modules-\@docusaurus-theme-classic-lib-theme-Admonition-styles-module { + margin-bottom: 1rem; +} + +.docs-wrapper #__docusaurus .theme-admonition h2 { + font-size: 1.125rem; + color: var(--color-blue); + font-weight: 500; + margin-bottom: 0.75rem; +} + +.docs-wrapper #__docusaurus .docs-section { + margin-block-start: 6rem; +} + +.docs-wrapper #__docusaurus .docs-section code:not(pre code) { + background: #424242; +} + +.docs-wrapper #__docusaurus .docs-section h3 { + margin-bottom: 1rem; +} + +.docs-wrapper #__docusaurus .docs-section h3:first-of-type { + margin-top: 2rem; +} + +.docs-wrapper #__docusaurus .docs-section h3:not(:first-of-type) { + margin-top: 4rem; +} + +.docs-wrapper #__docusaurus h4 { + font-size: 1.25rem; + margin-block-start: 2rem; +} + +.docs-wrapper #__docusaurus .docs-section h3+p { + margin-top: 0.5rem; +} + +.docs-wrapper #__docusaurus .docs-section .theme-code-block pre { + margin-top: 0.75rem; +} + +.docs-wrapper #__docusaurus pre code:not(.search-widget pre code) { + border: 1px solid #212121; + background: #161616; + padding: 1.5rem 1rem; + line-height: 1.65; +} + +.docs-wrapper #__docusaurus code:not(h4 > code) { + font-family: var(--tbd-font-mono); + letter-spacing: 0; + font-size: 0.9rem; +} + +.docs-wrapper #__docusaurus details.alert--info>summary { + color: var(--color-blue); + font-weight: 600; +} + +.docs-wrapper #__docusaurus details.alert--info p { + font-size: 1rem; +} + +.docs-wrapper #__docusaurus .input-container { + margin-block: 1rem; +} + +.docs-wrapper #__docusaurus .input-container label { + display: block; + font-size: 0.75rem; + margin-bottom: 4px; +} + +.docs-wrapper #__docusaurus .input-container input { + padding: 0.5rem; + border-radius: 4px; + border: 1px solid #616161; + background: none; + width: 100%; + max-width: 400px; +} + +.docs-wrapper #__docusaurus .input-container input:disabled { + opacity: 0.4; +} + +.docs-wrapper #__docusaurus .sandbox-container progress { + width: 32px; + height: 32px; + border: 4px solid #333333; + border-bottom-color: transparent; + border-radius: 50%; + display: inline-block; + box-sizing: border-box; + animation: rotation 1s linear infinite; + margin-left: 0.5rem; +} + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +.docs-wrapper #__docusaurus .sandbox-container progress::-webkit-progress-bar { + background: transparent; +} + +.docs-wrapper #__docusaurus .sandbox-container { + display: flex; + flex-direction: column; + gap: 1rem; + margin-bottom: 3rem; +} + +.docs-wrapper #__docusaurus .sandbox-container button { + white-space: nowrap; +} + +.docs-wrapper #__docusaurus .sandbox-container button:disabled { + cursor: default; + border: 2px solid transparent; + background: #262626; + color: #5a5a5a; +} + +.docs-wrapper #__docusaurus .sandbox-container code, +.docs-wrapper #__docusaurus .sandbox-container textarea { + background: none; +} + +.docs-wrapper #__docusaurus .sandbox-container code .sandbox-placeholder { + color: grey; +} + +.docs-wrapper #__docusaurus .sandbox-container textarea { + resize: none; + font-family: var(--tbd-font-mono); + font-size: 0.85rem; + line-height: 1.75; + color: grey; + padding: 1rem; +} + +.docs-wrapper #__docusaurus .sandbox-container .output { + flex: 1; + border: 1px solid #616161; + padding: 1rem; + background: #141414; + margin: 0; + border-radius: 0.25rem; +} + +.docs-wrapper #__docusaurus .figure-container { + margin-block: 3rem; +} + +.docs-wrapper #__docusaurus .figure-container p.copy { + margin-block: 1rem; + font-size: 0.875rem; + font-family: var(--tbd-font-mono); + letter-spacing: 0; +} + +@media screen and (max-width: 996px) { + .docs-wrapper #__docusaurus { + padding-inline: 0; + } + + .docs-wrapper #__docusaurus .navbar { + padding-top: 1.5rem; + padding-inline: 1rem; + } + + .docs-wrapper #__docusaurus article { + padding: 2rem; + } + + .docs-wrapper #__docusaurus footer { + background: var(--color-black); + } + + .docs-wrapper #__docusaurus .theme-card { + gap: 0; + align-items: end; + } + + .docs-wrapper #__docusaurus .theme-card p { + max-width: unset; + } +} + +@media screen and (min-width: 997px) { + .docs-wrapper #__docusaurus { + padding: 0; + } + + .docs-wrapper #__docusaurus article { + padding: 4rem 8vw; + } + + .docs-wrapper #__docusaurus .navbar { + padding-left: 1.875rem; + position: sticky; + z-index: 999999; + } + + .docs-wrapper #__docusaurus .docSidebarContainer_node_modules-\@docusaurus-theme-classic-lib-theme-DocPage-Layout-Sidebar-styles-module { + margin-top: 0; + } + + .docs-wrapper #__docusaurus .sidebarViewport_node_modules-\@docusaurus-theme-classic-lib-theme-DocPage-Layout-Sidebar-styles-module { + position: sticky; + top: 77px; + } + + .docs-wrapper #__docusaurus .sidebar_node_modules-\@docusaurus-theme-classic-lib-theme-DocSidebar-Desktop-styles-module { + padding-top: 0; + } +} + +.pagination-nav { + display: none; +} + +html.plugin-docs.plugin-id-tbdex body, +html.plugin-docs.plugin-id-tbdex .navbar { + background-color: #1c1c1c; +} + +html.plugin-docs.plugin-id-tbdex .navbar { + border-bottom: 1px solid var(--ifm-toc-border-color); + padding: 0; +} + +html.plugin-docs.plugin-id-tbdex .navbar__items, +html.plugin-docs.plugin-id-tbdex .navbar__items--right { + align-items: baseline; +} + +html.plugin-docs.plugin-id-tbdex .navbar__logo { + margin-bottom: 1.5rem; + height: 1.5rem; +} + +html.plugin-docs.plugin-id-tbdex .navbar-sidebar .navbar-sidebar__item:last-child .clean-btn.navbar-sidebar__back::before { + display: inline-block; + content: url('/static/img/arrow-yellow.svg'); + position: relative; +} + +html.plugin-docs.plugin-id-tbdex aside .menu__link { + color: var(--ifm-menu-color); + padding-top: 0rem; + padding-bottom: 0rem; +} + +html.plugin-docs.plugin-id-tbdex aside aside .menu__link { + padding-left: 0; +} + +html.plugin-docs.plugin-id-tbdex aside aside .menu__link li a { + padding-left: 0; +} + +html.plugin-docs.plugin-id-tbdex aside aside .menu__link--active { + padding-left: 0; +} + +html.plugin-docs.plugin-id-tbdex .thin-scrollbar { + color: white; + letter-spacing: 1px; + margin-top: 24px; +} + +html.plugin-docs.plugin-id-tbdex aside .sidebar_node_modules-\@docusaurus-theme-classic-lib-next-theme-DocSidebar-Desktop-styles-module { + top: auto; +} + +html.plugin-docs.plugin-id-tbdex .sidebar_RiAD { + top: 0 !important; +} + +@media (min-width: 997px) { + html.plugin-docs.plugin-id-tbdex .menu_node_modules-\@docusaurus-theme-classic-lib-next-theme-DocSidebar-Desktop-Content-styles-module { + flex-grow: 1; + padding-left: 0rem !important; + font-size: large; + } + + html.plugin-docs.plugin-id-tbdex aside.docSidebarContainer_src-layout-DocPage-styles-module, + html.plugin-docs.plugin-id-tbdex aside.theme-doc-sidebar-container { + margin-right: 1.5rem; + } +} + +/* Article styling */ + +article .markdown p { + font-weight: var(--typography-body-1-d-font-weight); + font-size: var(--typography-body-1-d-font-size); + line-height: var(--typography-body-1-d-line-height); + letter-spacing: 1px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +article .markdown li { + font-weight: var(--typography-body-1-d-font-weight); + font-size: var(--typography-body-1-d-font-size); + line-height: var(--typography-body-1-d-line-height); + letter-spacing: 1px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +article .markdown blockquote { + color: var(--color-yellow); + font-style: normal; + font-weight: 300; +} + +article .markdown blockquote b { + color: white; + font-style: normal; + font-weight: 800; +} + +article .theme-code-block pre { + background: #232323; +} + +article .theme-code-block pre code .token-line span { + color: white; +} + +article .markdown p { + font-weight: var(--typography-body-1-d-font-weight); + font-size: var(--typography-body-1-d-font-size); + line-height: var(--typography-body-1-d-line-height); + letter-spacing: 1px; + font-family: var(--tbd-font-sans); +} + +article .markdown p code { + color: white; + background-color: gray; +} + +.pagination-nav__sublabel { + display: none; +} + +.pagination-nav__label { + color: white; + letter-spacing: 1px; + font-weight: 400; + text-decoration: none; +} + +html.plugin-docs.plugin-id-tbdex .thin-scrollbar { + color: white; + letter-spacing: 1px; + margin-top: 24px; + top: 118px; + font-family: var(--tbd-font-sans); +} + +html.plugin-docs.plugin-id-tbdex article time { + color: white; +} + +html.plugin-docs.plugin-id-tbdex .avatar { + color: white; +} + +html.plugin-docs.plugin-id-tbdex .navbar__link { + font-size: 18px; + padding-block: 1.5rem; + border-bottom: 4px solid transparent; +} + +html.plugin-docs.plugin-id-tbdex .navbar__link:hover { + border-bottom: 4px solid var(--color-yellow); + color: var(--color-yellow); +} + +html.plugin-docs.plugin-id-tbdex .navbar__link--active { + border-bottom: 4px solid var(--color-yellow); + color: var(--color-yellow); +} + +html.plugin-docs.plugin-id-tbdex .thin-scrollbar ul li a:hover { + color: var(--color-yellow); +} + +html.plugin-docs.plugin-id-tbdex .sidebarItemLinkActive_node_modules-\@docusaurus-theme-classic-lib-next-theme-BlogSidebar-styles-module { + color: var(--color-yellow); +} + +html.plugin-docs.plugin-id-tbdex article h2 { + color: var(--color-yellow); + font-size: 24px; +} + +html.plugin-docs.plugin-id-tbdex ul.table-of-contents li a code, +html.plugin-docs.plugin-id-tbdex ul.table-of-contents li code { + background-color: transparent; +} + +html.plugin-docs.plugin-id-tbdex .table-of-contents__link:hover, +html.plugin-docs.plugin-id-tbdex .table-of-contents__link:hover code, +html.plugin-docs.plugin-id-tbdex .table-of-contents__link--active, +html.plugin-docs.plugin-id-tbdex .table-of-contents__link--active code { + color: var(--color-yellow); + text-decoration: none; +} + +.docs-wrapper #__docusaurus .table-of-contents ul { + padding-inline-start: 1rem; +} + +html.plugin-docs.plugin-id-tbdex .table-of-contents a strong { + color: white !important; +} + +html.plugin-docs.plugin-id-tbdex .table-of-contents li { + list-style: none !important; +} + +html.plugin-docs.plugin-id-tbdex .table-of-contents__link { + color: #969696; + font-size: 1rem; + font-weight: 500; + line-height: 1.375rem; + text-decoration: none; + padding: 0.75rem 0.5rem; +} + +html.plugin-docs.plugin-id-tbdex .table-of-contents__left-border { + border-left: 1px solid #343434; + padding-left: 1rem; +} + +html.plugin-docs.plugin-id-tbdex .table-of-contents__left-border li { + margin-right: 0; + margin-left: 0; + font-family: var(--tbd-font-mono); + letter-spacing: 0; +} + +/* Redocusaurus */ +.redocusaurus { + overflow: auto; + height: 95vh; +} + +/* Changes */ +.hero-button { + z-index: 99999; + width: fit-content; +} + +.hero-button a { + text-decoration: none; + text-transform: uppercase; + font-weight: bold; +} + +.theme-card { + --color-card: 36, 242, 255; + border: 1px solid rgba(var(--color-card), 0.35); +} + +.theme-card .hero-button a>div { + background: #fff; + color: #000; +} + +article .markdown .theme-card>p { + line-height: 1.5; + font-size: 100%; + margin-bottom: 2rem; +} + +html.plugin-docs.plugin-id-tbdex article .theme-card h3 { + color: rgba(var(--color-card)); +} + +html.plugin-docs.plugin-id-tbdex article .theme-card p { + color: #fff; +} + +.theme-card-cyan { + --color-card: 36, 242, 255; +} + +.theme-card-yellow { + --color-card: 255, 236, 25; +} + +.theme-card-purple { + --color-card: 183, 43, 255; +} + +.community-button { + text-decoration: none; + text-transform: uppercase; + font-weight: bold; + background-color: #fff !important; + color: #000 !important; +} + +.tbd-blue-illustration { + filter: var(--color-primary-cyan-filter); +} + +.markdown>h3 { + font-size: 1.5rem; +} + +.language-switch-header { + text-align: right; + margin-bottom: 10px; +} + +.language-switcher-select { + width: auto; + font-size: 16px; + color: #000; + font-weight: bold; + background-color: #ffec19; + margin-left: 15px; + padding: 5px; +} + +.languageTabs { + display: flex; + justify-content: flex-start; + cursor: pointer; + margin: 0; +} + +.mermaid text { + font-weight: bold; +} + +.beat-fade { + animation-name: beat-fade; + animation-duration: 2s; + animation-iteration-count: infinite; + animation-timing-function: cubic-bezier(.4, 0, .6, 1); + -webkit-animation-name: beat-fade; + -webkit-animation-duration: 2s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: cubic-bezier(.4, 0, .6, 1); +} + +@keyframes beat-fade { + 0% { + transform: scale(1); + opacity: 1; + } + + 50% { + transform: scale(1.1); + opacity: 0.7; + } + + 100% { + transform: scale(1); + opacity: 1; + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; + -webkit-animation-delay: 0s; + animation-delay: 0s; + -webkit-animation-direction: normal; + animation-direction: normal; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; + -webkit-animation-timing-function: cubic-bezier(.28, .84, .42, 1); + animation-timing-function: cubic-bezier(.28, .84, .42, 1); +} + +@keyframes bounce { + + 0%, + 20%, + 50%, + 80%, + 100% { + transform: translateY(0); + } + + 40% { + transform: translateY(-30px); + } + + 60% { + transform: translateY(-15px); + } +} + +.flip { + -webkit-animation-name: flip; + animation-name: flip; + -webkit-animation-delay: 0s; + animation-delay: 0s; + -webkit-animation-direction: normal; + animation-direction: normal; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-iteration-count: 1; + animation-iteration-count: 1; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; +} + +@keyframes flip { + from { + transform: rotateY(0deg); + } + + to { + transform: rotateY(360deg); + } +} \ No newline at end of file diff --git a/site/src/css/web5.css b/site/src/css/web5.css new file mode 100644 index 000000000..64e12fc8f --- /dev/null +++ b/site/src/css/web5.css @@ -0,0 +1,874 @@ +/* Navbar and main */ + +.docs-wrapper #__docusaurus a:not(.table-of-contents__link, .navbar__link) { + text-decoration: none; + font-weight: 600; +} + +.docs-wrapper .DocSearch-Modal { + margin-top: 120px; +} + +.docs-wrapper #__docusaurus { + --color-white: #c6c6c6; +} + +.docs-wrapper #__docusaurus .navbar, +.docs-wrapper #__docusaurus .main-wrapper { + max-width: unset; +} + +.docs-wrapper #__docusaurus .main-wrapper { + margin-top: 0; +} + +/* Sidebar */ +.docs-wrapper #__docusaurus aside .menu__list { + gap: 0; +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item .menu__link { + font-weight: 500; + padding-block: 0.75rem; + color: var(--color-yellow); + text-decoration: none; + font-family: var(--tbd-font-mono); + letter-spacing: 0; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-link>.menu__link.menu__link--active, +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible--active .menu__link--sublist.menu__link--active, +.docs-wrapper #__docusaurus aside .menu__list-item-collapsible--active { + background-color: var(--color-yellow); + color: var(--color-black) !important; +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible--active .menu__link--sublist.menu__link--active:hover, +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category .menu__list-item-collapsible:hover .menu__link--sublist.menu__link--active { + color: var(--color-blue) !important; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category .menu__list-item-collapsible:hover { + background-color: var(--ifm-menu-color-background-hover); +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__caret:before { + content: url('/img/arrow-blue.svg'); + background: none; +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__caret:before, +.docs-wrapper #__docusaurus aside .menu__list .menu__link--sublist-caret:after { + filter: brightness(0) var(--color-primary-yellow-filter); +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible--active .menu__caret:before { + filter: brightness(0); +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible:hover .menu__caret:before, +.docs-wrapper #__docusaurus aside .menu__list .menu__link--sublist-caret:hover:after { + filter: none; +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible:hover .menu__link.menu__link--sublist { + color: var(--color-blue); +} + +.docs-wrapper #__docusaurus aside .menu__list .menu__caret:hover { + height: auto; +} + +/* Submenu */ + +.docs-wrapper #__docusaurus aside .menu__list .menu__list-item-collapsible--active~.menu__list { + margin: 0; +} + +.docs-wrapper #__docusaurus aside .menu__list-item-collapsible~.menu__list { + padding-left: 0; + margin: 0; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category-level-1 .menu__list-item-collapsible~.menu__list .menu__link { + padding-left: 2.5rem; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category-level-2 .menu__list-item-collapsible~.menu__list .menu__link { + padding-left: 3rem; +} + +.docs-wrapper #__docusaurus aside .theme-doc-sidebar-item-category-level-3 .menu__list-item-collapsible~.menu__list .menu__link { + padding-left: 3.5rem; +} + +.docs-wrapper #__docusaurus footer { + border-top: 1px solid var(--ifm-toc-border-color); + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.docs-wrapper #__docusaurus footer>div { + overflow-x: hidden; +} + +.docs-wrapper #__docusaurus h1~p, +.docs-wrapper #__docusaurus h2:not(.theme-card h2, .community-card h2)~p, +.docs-wrapper #__docusaurus h3:not(.theme-card h3, .community-card h3)~p, +.docs-wrapper #__docusaurus p { + font-family: var(--tbd-font-mono); + letter-spacing: 0; + line-height: 1.75rem; +} + +.docs-wrapper #__docusaurus p:not(.docs-section p, + .alert p, + .figure-container p, + .api-card p, + .search-widget p) { + font-size: 1.125rem; + color: var(--color-white); +} + +.docs-wrapper #__docusaurus article .markdown li { + font-family: var(--tbd-font-mono); + letter-spacing: 0; + color: var(--color-white); +} + +.docs-wrapper #__docusaurus .api-card { + border: 2px solid var(--color-yellow); + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; +} + +.docs-wrapper #__docusaurus .api-card p { + font-size: 1rem; +} + +.api-card-icon { + display: flex; + justify-content: start; + gap: 20px; + padding-top: 20px; + vertical-align: bottom; +} + +.api-card-icon img { + width: 50px; +} + +.docs-wrapper #__docusaurus h2, +.docs-wrapper #__docusaurus .primary-theme-card h2 { + font-size: 2rem; +} + +.docs-wrapper #__docusaurus .theme-card { + border-width: 2px; +} + +.docs-wrapper #__docusaurus .theme-card p:not(.primary-theme-card p) { + font-size: 1rem; +} + +.docs-wrapper #__docusaurus .theme-card p { + max-width: 70%; + font-family: var(--tbd-font-mono); + letter-spacing: 0; +} + +.docs-wrapper #__docusaurus .theme-card img { + height: fit-content; +} + +.docs-wrapper #__docusaurus .community-card h3 { + font-size: 1.25rem; + margin-bottom: 12px; +} + +.docs-wrapper #__docusaurus .community-card p { + font-size: 1rem; +} + +.docs-wrapper #__docusaurus .explore-card h3 { + font-size: 1rem; + margin-block: 0; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert { + border-radius: 0; + margin-bottom: 2rem; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert code, +.docs-wrapper #__docusaurus code:not(pre code, h4 code) { + background: #626262; + color: #fff; +} + +.docs-wrapper #__docusaurus h4>code { + background: #383838; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert--info { + background: #0b1a1f; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert--secondary { + background: #3d3d3d; +} + +.docs-wrapper #__docusaurus .theme-admonition.alert--secondary p, +.docs-wrapper #__docusaurus .theme-admonition.alert--secondary p:last-child { + margin: 0; +} + +.docs-wrapper #__docusaurus .theme-admonition p, +.docs-wrapper #__docusaurus .theme-admonition li { + font-size: 1rem; +} + +.docs-wrapper #__docusaurus .theme-admonition p:last-child { + margin-bottom: 1.5rem; +} + +.docs-wrapper #__docusaurus .theme-admonition .admonitionHeading_node_modules-\@docusaurus-theme-classic-lib-theme-Admonition-styles-module { + margin-bottom: 1rem; +} + +.docs-wrapper #__docusaurus .theme-admonition h2 { + font-size: 1.125rem; + color: var(--color-blue); + font-weight: 500; + margin-bottom: 0.75rem; +} + +.docs-wrapper #__docusaurus .docs-section { + margin-block-start: 6rem; +} + +.docs-wrapper #__docusaurus .docs-section code:not(pre code) { + background: #424242; +} + +.docs-wrapper #__docusaurus .docs-section h3 { + margin-bottom: 1rem; +} + +.docs-wrapper #__docusaurus .docs-section h3:first-of-type { + margin-top: 2rem; +} + +.docs-wrapper #__docusaurus .docs-section h3:not(:first-of-type) { + margin-top: 4rem; +} + +.docs-wrapper #__docusaurus h4 { + font-size: 1.25rem; + margin-block-start: 2rem; +} + +.docs-wrapper #__docusaurus .docs-section h3+p { + margin-top: 0.5rem; +} + +.docs-wrapper #__docusaurus .docs-section .theme-code-block pre { + margin-top: 0.75rem; +} + +.docs-wrapper #__docusaurus pre code:not(.search-widget pre code) { + border: 1px solid #212121; + background: #161616; + padding: 1.5rem 1rem; + line-height: 1.65; +} + +.docs-wrapper #__docusaurus code:not(h4 > code) { + font-family: var(--tbd-font-mono); + letter-spacing: 0; + font-size: 0.9rem; +} + +.docs-wrapper #__docusaurus details.alert--info>summary { + color: var(--color-blue); + font-weight: 600; +} + +.docs-wrapper #__docusaurus details.alert--info p { + font-size: 1rem; +} + +.docs-wrapper #__docusaurus .input-container { + margin-block: 1rem; +} + +.docs-wrapper #__docusaurus .input-container label { + display: block; + font-size: 0.75rem; + margin-bottom: 4px; +} + +.docs-wrapper #__docusaurus .input-container input { + padding: 0.5rem; + border-radius: 4px; + border: 1px solid #616161; + background: none; + width: 100%; + max-width: 400px; +} + +.docs-wrapper #__docusaurus .input-container input:disabled { + opacity: 0.4; +} + +.docs-wrapper #__docusaurus .sandbox-container progress { + width: 32px; + height: 32px; + border: 4px solid #333333; + border-bottom-color: transparent; + border-radius: 50%; + display: inline-block; + box-sizing: border-box; + animation: rotation 1s linear infinite; + margin-left: 0.5rem; +} + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +.docs-wrapper #__docusaurus .sandbox-container progress::-webkit-progress-bar { + background: transparent; +} + +.docs-wrapper #__docusaurus .sandbox-container { + display: flex; + flex-direction: column; + gap: 1rem; + margin-bottom: 3rem; +} + +.docs-wrapper #__docusaurus .sandbox-container button { + white-space: nowrap; +} + +.docs-wrapper #__docusaurus .sandbox-container button:disabled { + cursor: default; + border: 2px solid transparent; + background: #262626; + color: #5a5a5a; +} + +.docs-wrapper #__docusaurus .sandbox-container code, +.docs-wrapper #__docusaurus .sandbox-container textarea { + background: none; +} + +.docs-wrapper #__docusaurus .sandbox-container code .sandbox-placeholder { + color: grey; +} + +.docs-wrapper #__docusaurus .sandbox-container textarea { + resize: none; + font-family: var(--tbd-font-mono); + font-size: 0.85rem; + line-height: 1.75; + color: grey; + padding: 1rem; +} + +.docs-wrapper #__docusaurus .sandbox-container .output { + flex: 1; + border: 1px solid #616161; + padding: 1rem; + background: #141414; + margin: 0; + border-radius: 0.25rem; +} + +.docs-wrapper #__docusaurus .figure-container { + margin-block: 3rem; +} + +.docs-wrapper #__docusaurus .figure-container p.copy { + margin-block: 1rem; + font-size: 0.875rem; + font-family: var(--tbd-font-mono); + letter-spacing: 0; +} + +@media screen and (max-width: 996px) { + .docs-wrapper #__docusaurus { + padding-inline: 0; + } + + .docs-wrapper #__docusaurus .navbar { + padding-top: 1.5rem; + padding-inline: 1rem; + } + + .docs-wrapper #__docusaurus article { + padding: 2rem; + } + + .docs-wrapper #__docusaurus footer { + background: var(--color-black); + } + + .docs-wrapper #__docusaurus .theme-card { + gap: 0; + align-items: end; + } + + .docs-wrapper #__docusaurus .theme-card p { + max-width: unset; + } +} + +@media screen and (min-width: 997px) { + .docs-wrapper #__docusaurus { + padding: 0; + } + + .docs-wrapper #__docusaurus article { + padding: 4rem 8vw; + } + + .docs-wrapper #__docusaurus .navbar { + padding-left: 1.875rem; + position: sticky; + z-index: 999999; + } + + .docs-wrapper #__docusaurus .docSidebarContainer_node_modules-\@docusaurus-theme-classic-lib-theme-DocPage-Layout-Sidebar-styles-module { + margin-top: 0; + } + + .docs-wrapper #__docusaurus .sidebarViewport_node_modules-\@docusaurus-theme-classic-lib-theme-DocPage-Layout-Sidebar-styles-module { + position: sticky; + top: 77px; + } + + .docs-wrapper #__docusaurus .sidebar_node_modules-\@docusaurus-theme-classic-lib-theme-DocSidebar-Desktop-styles-module { + padding-top: 0; + } +} + +.pagination-nav { + display: none; +} + +html.plugin-docs.plugin-id-web5 body, +html.plugin-docs.plugin-id-web5 .navbar { + background-color: #1c1c1c; +} + +html.plugin-docs.plugin-id-web5 .navbar { + border-bottom: 1px solid var(--ifm-toc-border-color); + padding: 0; +} + +html.plugin-docs.plugin-id-web5 .navbar__items, +html.plugin-docs.plugin-id-web5 .navbar__items--right { + align-items: baseline; +} + +html.plugin-docs.plugin-id-web5 .navbar__logo { + margin-bottom: 1.5rem; + height: 1.5rem; +} + +html.plugin-docs.plugin-id-web5 .navbar-sidebar .navbar-sidebar__item:last-child .clean-btn.navbar-sidebar__back::before { + display: inline-block; + content: url('/static/img/arrow-yellow.svg'); + position: relative; +} + +html.plugin-docs.plugin-id-web5 aside .menu__link { + color: var(--ifm-menu-color); + padding-top: 0rem; + padding-bottom: 0rem; +} + +html.plugin-docs.plugin-id-web5 aside aside .menu__link { + padding-left: 0; +} + +html.plugin-docs.plugin-id-web5 aside aside .menu__link li a { + padding-left: 0; +} + +html.plugin-docs.plugin-id-web5 aside aside .menu__link--active { + padding-left: 0; +} + +html.plugin-docs.plugin-id-web5 .thin-scrollbar { + color: white; + letter-spacing: 1px; + margin-top: 24px; +} + +html.plugin-docs.plugin-id-web5 aside .sidebar_node_modules-\@docusaurus-theme-classic-lib-next-theme-DocSidebar-Desktop-styles-module { + top: auto; +} + +html.plugin-docs.plugin-id-web5 .sidebar_RiAD { + top: 0 !important; +} + +@media (min-width: 997px) { + html.plugin-docs.plugin-id-web5 .menu_node_modules-\@docusaurus-theme-classic-lib-next-theme-DocSidebar-Desktop-Content-styles-module { + flex-grow: 1; + padding-left: 0rem !important; + font-size: large; + } + + html.plugin-docs.plugin-id-web5 aside.docSidebarContainer_src-layout-DocPage-styles-module, + html.plugin-docs.plugin-id-web5 aside.theme-doc-sidebar-container { + margin-right: 1.5rem; + } +} + +/* Article styling */ + +article .markdown p { + font-weight: var(--typography-body-1-d-font-weight); + font-size: var(--typography-body-1-d-font-size); + line-height: var(--typography-body-1-d-line-height); + letter-spacing: 1px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +article .markdown li { + font-weight: var(--typography-body-1-d-font-weight); + font-size: var(--typography-body-1-d-font-size); + line-height: var(--typography-body-1-d-line-height); + letter-spacing: 1px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +article .markdown blockquote { + color: var(--color-yellow); + font-style: normal; + font-weight: 300; +} + +article .markdown blockquote b { + color: white; + font-style: normal; + font-weight: 800; +} + +article .theme-code-block pre { + background: #232323; +} + +article .theme-code-block pre code .token-line span { + color: white; +} + +article .markdown p { + font-weight: var(--typography-body-1-d-font-weight); + font-size: var(--typography-body-1-d-font-size); + line-height: var(--typography-body-1-d-line-height); + letter-spacing: 1px; + font-family: var(--tbd-font-sans); +} + +article .markdown p code { + color: white; + background-color: gray; +} + +.pagination-nav__sublabel { + display: none; +} + +.pagination-nav__label { + color: white; + letter-spacing: 1px; + font-weight: 400; + text-decoration: none; +} + +html.plugin-docs.plugin-id-web5 .thin-scrollbar { + color: white; + letter-spacing: 1px; + margin-top: 24px; + top: 118px; + font-family: var(--tbd-font-sans); +} + +html.plugin-docs.plugin-id-web5 article time { + color: white; +} + +html.plugin-docs.plugin-id-web5 .avatar { + color: white; +} + +html.plugin-docs.plugin-id-web5 .navbar__link { + font-size: 18px; + padding-block: 1.5rem; + border-bottom: 4px solid transparent; +} + +html.plugin-docs.plugin-id-web5 .navbar__link:hover { + border-bottom: 4px solid var(--color-yellow); + color: var(--color-yellow); +} + +html.plugin-docs.plugin-id-web5 .navbar__link--active { + border-bottom: 4px solid var(--color-yellow); + color: var(--color-yellow); +} + +html.plugin-docs.plugin-id-web5 .thin-scrollbar ul li a:hover { + color: var(--color-yellow); +} + +html.plugin-docs.plugin-id-web5 .sidebarItemLinkActive_node_modules-\@docusaurus-theme-classic-lib-next-theme-BlogSidebar-styles-module { + color: var(--color-yellow); +} + +html.plugin-docs.plugin-id-web5 article h2 { + color: var(--color-yellow); + font-size: 24px; +} + +html.plugin-docs.plugin-id-web5 ul.table-of-contents li a code, +html.plugin-docs.plugin-id-web5 ul.table-of-contents li code { + background-color: transparent; +} + +html.plugin-docs.plugin-id-web5 .table-of-contents__link:hover, +html.plugin-docs.plugin-id-web5 .table-of-contents__link:hover code, +html.plugin-docs.plugin-id-web5 .table-of-contents__link--active, +html.plugin-docs.plugin-id-web5 .table-of-contents__link--active code { + color: var(--color-yellow); + text-decoration: none; +} + +.docs-wrapper #__docusaurus .table-of-contents ul { + padding-inline-start: 1rem; +} + +html.plugin-docs.plugin-id-web5 .table-of-contents a strong { + color: white !important; +} + +html.plugin-docs.plugin-id-web5 .table-of-contents li { + list-style: none !important; +} + +html.plugin-docs.plugin-id-web5 .table-of-contents__link { + color: #969696; + font-size: 1rem; + font-weight: 500; + line-height: 1.375rem; + text-decoration: none; + padding: 0.75rem 0.5rem; +} + +html.plugin-docs.plugin-id-web5 .table-of-contents__left-border { + border-left: 1px solid #343434; + padding-left: 1rem; +} + +html.plugin-docs.plugin-id-web5 .table-of-contents__left-border li { + margin-right: 0; + margin-left: 0; + font-family: var(--tbd-font-mono); + letter-spacing: 0; +} + +/* Redocusaurus */ +.redocusaurus { + overflow: auto; + height: 95vh; +} + +/* Changes */ +.hero-button { + z-index: 99999; + width: fit-content; +} + +.hero-button a { + text-decoration: none; + text-transform: uppercase; + font-weight: bold; +} + +.theme-card { + --color-card: 36, 242, 255; + border: 1px solid rgba(var(--color-card), 0.35); +} + +.theme-card .hero-button a>div { + background: #fff; + color: #000; +} + +article .markdown .theme-card>p { + line-height: 1.5; + font-size: 100%; + margin-bottom: 2rem; +} + +html.plugin-docs.plugin-id-web5 article .theme-card h3 { + color: rgba(var(--color-card)); +} + +html.plugin-docs.plugin-id-web5 article .theme-card p { + color: #fff; +} + +.theme-card-cyan { + --color-card: 36, 242, 255; +} + +.theme-card-yellow { + --color-card: 255, 236, 25; +} + +.theme-card-purple { + --color-card: 183, 43, 255; +} + +.community-button { + text-decoration: none; + text-transform: uppercase; + font-weight: bold; + background-color: #fff !important; + color: #000 !important; +} + +.tbd-blue-illustration { + filter: var(--color-primary-cyan-filter); +} + +.markdown>h3 { + font-size: 1.5rem; +} + +.language-switch-header { + text-align: right; + margin-bottom: 10px; +} + +.language-switcher-select { + width: auto; + font-size: 16px; + color: #000; + font-weight: bold; + background-color: #ffec19; + margin-left: 15px; + padding: 5px; +} + +.languageTabs { + display: flex; + justify-content: flex-start; + cursor: pointer; + margin: 0; +} + +.mermaid text { + font-weight: bold; +} + +.beat-fade { + animation-name: beat-fade; + animation-duration: 2s; + animation-iteration-count: infinite; + animation-timing-function: cubic-bezier(.4, 0, .6, 1); + -webkit-animation-name: beat-fade; + -webkit-animation-duration: 2s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: cubic-bezier(.4, 0, .6, 1); +} + +@keyframes beat-fade { + 0% { + transform: scale(1); + opacity: 1; + } + + 50% { + transform: scale(1.1); + opacity: 0.7; + } + + 100% { + transform: scale(1); + opacity: 1; + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; + -webkit-animation-delay: 0s; + animation-delay: 0s; + -webkit-animation-direction: normal; + animation-direction: normal; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; + -webkit-animation-timing-function: cubic-bezier(.28, .84, .42, 1); + animation-timing-function: cubic-bezier(.28, .84, .42, 1); +} + +@keyframes bounce { + + 0%, + 20%, + 50%, + 80%, + 100% { + transform: translateY(0); + } + + 40% { + transform: translateY(-30px); + } + + 60% { + transform: translateY(-15px); + } +} + +.flip { + -webkit-animation-name: flip; + animation-name: flip; + -webkit-animation-delay: 0s; + animation-delay: 0s; + -webkit-animation-direction: normal; + animation-direction: normal; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-iteration-count: 1; + animation-iteration-count: 1; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; +} + +@keyframes flip { + from { + transform: rotateY(0deg); + } + + to { + transform: rotateY(360deg); + } +} \ No newline at end of file diff --git a/site/tbdex-sidebars.js b/site/tbdex-sidebars.js new file mode 100644 index 000000000..8605bf1a2 --- /dev/null +++ b/site/tbdex-sidebars.js @@ -0,0 +1,6 @@ +const sidebars = { + // By default, Docusaurus generates a sidebar from the docs folder structure + web5Sidebars: [{ type: 'autogenerated', dirName: '.' }], +}; + +module.exports = sidebars; diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/exchangesApiProvider.js b/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/exchangesApiProvider.js deleted file mode 100644 index b62565115..000000000 --- a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/exchangesApiProvider.js +++ /dev/null @@ -1,24 +0,0 @@ -import { MockExchangesApiProvider } from '../../utils/mockExchangesApiProvider' - -export class ExchangesApiProvider extends MockExchangesApiProvider { - - // :snippet-start: pfiOverviewWriteJs - async write(message, replyTo) { - await this.dataProvider.insert('exchange', { - exchangeid: message.exchangeId, - messagekind: message.kind, - messageid: message.id, - subject: message.subject, - message: JSON.stringify(message) - }); - - if (replyTo != null) { - await this.dataProvider.insert('callbacks', { - exchangeId: message.exchangeId, - uri: replyTo - }); - } - } - // :snippet-end: - -} \ No newline at end of file diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/offeringsApiProvider.js b/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/offeringsApiProvider.js deleted file mode 100644 index 681634944..000000000 --- a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/offeringsApiProvider.js +++ /dev/null @@ -1,43 +0,0 @@ -import { MockOfferingsApiProvider } from '../../utils/mockOfferingsApiProvider' -import { Parser } from '@tbdex/http-server' - -export class OfferingsApiProvider extends MockOfferingsApiProvider { - - constructor(pfiDid) { - super(); - this.pfiDid = pfiDid; - } - - // :snippet-start: pfiOverviewReadOfferingsJs - async getOffering(id) { - return this.dataProvider.get('offering', id).then(async (result) => { - if(result){ - return await Parser.parseResource(result); - } - }); - } - - - async getOfferings() { - return this.dataProvider.query('offering', "*").then((results) => { - const offerings = []; - - for (let result of results) { - const offering = Parser.rawToResourceModel(result); - offerings.push(offering); - } - - return offerings; - }); - } - - async setOffering(offering) { - await this.dataProvider.insert('offering', { - offeringid: offering.metadata.id, - payoutcurrency: offering.data.payout.currencyCode, - payincurrency: offering.data.payin.currencyCode, - offering: Parser.rawToResourceModel(offering) - }); - } - // :snippet-end: -} \ No newline at end of file diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/processingOrders.test.js b/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/processingOrders.test.js deleted file mode 100644 index 26095f65c..000000000 --- a/site/testsuites/testsuite-javascript/__tests__/tbdex/pfi/processingOrders.test.js +++ /dev/null @@ -1,105 +0,0 @@ -import { Order, OrderStatus, Close } from '@tbdex/http-server'; -import { DevTools } from '@tbdex/http-client'; -import { DidDht } from '@web5/dids'; -import { MockDataProvider } from '../../utils/mockDataProvider'; -import { test, expect, describe, beforeAll, assert } from 'vitest'; - -let pfiDid; -let senderDid; -let dataProvider; -let orderMessage; - -describe('PFI: Orders', () => { - beforeAll(async () => { - // Set up providers and DID - pfiDid = await DidDht.create({ - options:{ - publish: true, - services: [{ - id: 'pfi', - type: 'PFI', - serviceEndpoint: 'https://example.com/' - }] - } - }); - - senderDid = await DidDht.create({ - options: { publish: true } - }); - - dataProvider = new MockDataProvider(); - orderMessage = Order.create({ - metadata: { - from: senderDid.uri, - to: pfiDid.uri, - exchangeId: "MyExchange" - } - }); - }); - - test('PFI Accesses Private Data', async () => { - const rfq = await DevTools.createRfq({ - sender: senderDid, - receiver: pfiDid - }); - - // :snippet-start: pfiAccessPrivateDataJs - const creditCardNumber = rfq.privateData.payin.paymentDetails.cardNumber - // :snippet-end: - - expect(creditCardNumber).toBeDefined(); - }); - - test('PFI Creates OrderStatus', async () => { - // :snippet-start: pfiOrderStatusJs - const orderStatus = OrderStatus.create({ - metadata: { - from: pfiDid.uri, - to: orderMessage.metadata.from, - exchangeId: orderMessage.metadata.exchangeId - }, - data: { orderStatus: 'PROCESSING' } - }) - - await orderStatus.sign(pfiDid) - dataProvider.insert(orderStatus) - // :snippet-end: - - expect.soft(orderStatus.data.orderStatus).toBe('PROCESSING'); - - try { - await orderStatus.verifySignature(); - await orderStatus.verify(); - } catch(e) { - assert.fail(`Failed to verify offering requirements: : ${e.message}`); - } - }); - - test('PFI Creates Close', async () => { - // :snippet-start: pfiCloseOrderJs - const closeMessage = Close.create({ - metadata: { - from: pfiDid.uri, - to: orderMessage.metadata.from, - exchangeId: orderMessage.metadata.exchangeId - }, - data: { - reason: 'COMPLETED', - success: true // Indicates the transaction was successful - } - }) - - await closeMessage.sign(pfiDid) - dataProvider.insert(closeMessage) - // :snippet-end: - - expect.soft(closeMessage.data.reason).toBe('COMPLETED'); - - try { - await closeMessage.verifySignature(); - await closeMessage.verify(); - } catch(e) { - assert.fail(`Failed to verify offering requirements: ${e.message}`); - } - }); -}); \ No newline at end of file diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/protocol-roles.test.js b/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/protocol-roles.test.js deleted file mode 100644 index 8579f2bf9..000000000 --- a/site/testsuites/testsuite-javascript/__tests__/web5/build/decentralized-web-nodes/protocol-roles.test.js +++ /dev/null @@ -1,167 +0,0 @@ -import { test, beforeAll, expect, describe } from 'vitest'; -import { DidDht } from '@web5/dids' -import { setUpWeb5 } from '../../../setup-web5'; - -let web5; -let did; -let aliceDid; - -// :snippet-start: curatorPlaylistProtocolDefinitionJs -const curatorPlaylistProtocolDefinition = { - protocol: 'https://example.com/playlist-protocol', - published: true, - types: { - playlist: { - schema: "https://example.com/playlist-protocol/schema/playlist", - dataFormats: ['application/json'] - }, - // highlight-start - curator: { - schema: "https://example.com/playlist-protocol/schema/curator", - dataFormats: ['text/plain'] - }, - // highlight-end - admin: { - schema: "https://example.com/playlist-protocol/schema/admin", - dataFormats: ['text/plain'] - } - }, - structure: { - curator: { - // highlight-next-line - $role: true, - }, - admin: { - $role: true, - }, - playlist: { - $actions: [ - { - who: 'anyone', - can: ['read'] - }, - // highlight-start - { - role: 'curator', - can: ['create', 'update'] - }, - // highlight-end - { - role: 'admin', - can: ['create', 'update', 'delete'] - }, - ], - } - } -}; -// :snippet-end: - -// :snippet-start: chatProtocolDefinitionJs -const chatProtocolDefinition = { - protocol: "https://example.com/chat-protocol", - published: true, - types: { - chat: { - schema: "https://example.com/chat-protocol/schema/chat", - dataFormats: ["application/json"], - }, - }, - structure: { - chat: { - $actions: [ - { who: "anyone", can: ["create"] }, - { who: "author", of: "chat", can: ["read"] }, - { who: "recipient", of: "chat", can: ["read"] }, - ], - }, - }, - }; - // :snippet-end: - -describe('Playlist protocol roles', () => { - // connect to web5 beforeAll tests and assign it to web5 variable - beforeAll(async () => { - await setUpWeb5(); - web5 = globalThis.web5; - did = globalThis.did; - aliceDid = await DidDht.create(); - }); - - test('install chat protocol', async () => { - // just to make sure chat protocol is working as expected - const { protocol, status } = await web5.dwn.protocols.configure({ - message: { - definition: chatProtocolDefinition, - }, - }); - - // send protocol to remote DWNs immediately - const { status: sendStatus } = await protocol.send(did); - expect(protocol).toBeDefined(); - expect(status.code).to.equal(202); - expect(sendStatus.code).to.equal(202); - }); - - - test('install playlist protocol', async () => { - // :snippet-start: installPlaylistProtocolJs - const { protocol, status } = await web5.dwn.protocols.configure({ - message: { - definition: curatorPlaylistProtocolDefinition, - }, - }); - - // send protocol to remote DWNs immediately - const { status: sendStatus } = await protocol.send(did); - // :snippet-end: - expect(protocol).toBeDefined(); - expect(status.code).to.equal(202); - expect(sendStatus.code).to.equal(202); - }); - - test('assign protocol protocol role', async () => { - const aliceDidUri = aliceDid.uri; - // :snippet-start: assignPlaylistRoleJs - const { record, status } = await web5.dwn.records.create({ - message: { - dataFormat: 'text/plain', - protocol: curatorPlaylistProtocolDefinition.protocol, - protocolPath: 'curator', - schema: curatorPlaylistProtocolDefinition.types.curator.schema, - recipient: aliceDidUri, - }, - }); - - const { status: recordSendStatus } = await record.send(did); - // :snippet-end: - - expect(record).toBeDefined(); - expect(status.code).to.equal(202); - expect(recordSendStatus.code).to.equal(202); - }); - - test('Create a record within a role', async () => { - const bobDidUri = did; - // :snippet-start: createRecordInRoleJs - const { record, status } = await web5.dwn.records.create({ - data: JSON.stringify({ - name: "My Favorite Songs", - description: "A collection of my all-time favorites", - songs: [], - }), - message: { - dataFormat: 'application/json', - protocol: curatorPlaylistProtocolDefinition.protocol, - protocolPath: 'playlist', - protocolRole: 'curator', - schema: curatorPlaylistProtocolDefinition.types.playlist.schema, - recipient: bobDidUri, - }, - }); - // :snippet-end: - const expectedPlaylistData = {"name":"My Favorite Songs","description":"A collection of my all-time favorites","songs":[]} - expect(await record.data.text()).to.equal(JSON.stringify(expectedPlaylistData)); - expect(record).toBeDefined(); - expect(status.code).to.equal(202); - }); -}); \ No newline at end of file diff --git a/site/web5-sidebars.js b/site/web5-sidebars.js new file mode 100644 index 000000000..2c10a96b3 --- /dev/null +++ b/site/web5-sidebars.js @@ -0,0 +1,20 @@ +/** + * Creating a sidebar enables you to: + - create an ordered group of docs + - render a sidebar for each doc of that group + - provide next/previous navigation + + The sidebars can be generated from the filesystem, or explicitly defined here. + + Create as many sidebars as you want. + */ + +// @ts-check + +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + // By default, Docusaurus generates a sidebar from the docs folder structure + web5Sidebars: [{ type: 'autogenerated', dirName: '.' }], +}; + +module.exports = sidebars; diff --git a/site/web5_versioned_docs/version-1.0.1/_category_.json b/site/web5_versioned_docs/version-1.0.1/_category_.json new file mode 100644 index 000000000..c88e29956 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Web5 SDK", + "position": 3 +} diff --git a/site/web5_versioned_docs/version-1.0.1/apps/_category_.json b/site/web5_versioned_docs/version-1.0.1/apps/_category_.json new file mode 100644 index 000000000..8e52fedc9 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/apps/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Apps", + "position": 5 +} diff --git a/site/web5_versioned_docs/version-1.0.1/apps/book-reviews-tutorial.mdx b/site/web5_versioned_docs/version-1.0.1/apps/book-reviews-tutorial.mdx new file mode 100644 index 000000000..f8bf396ce --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/apps/book-reviews-tutorial.mdx @@ -0,0 +1,424 @@ +--- +sidebar_position: 4 +--- + +import PackageJson from '@site/src/components/PackageJson'; + +# Build a Book Review App + +There are many sites that allow users to write book reviews such as Amazon, GoodReads, Barnes & Noble, LibraryThing, and BookBub just to name a few. +Some of these apps even function as social media sites for book enthusiasts. + +Let's create a decentralized app for readers who want to keep reviews on the various books they've read. Rather than being locked into one book review app, they wish to have control over their reviews and easily share them across different platforms. + +This tutorial demonstrates how to use Web5.js to create an app that stores users' book reviews within their [DWNs](/docs/web5/decentralized-web-nodes/what-are-dwns) so that they can be accessed by any app on the web that has [permission](/docs/web5/decentralized-web-nodes/what-are-dwns#authorization). + +## Running Locally + +Download a copy to your local machine and run the following commands to start the app. + +Note: If you don't have `pnpm` installed, you can install it by running `npm install -g pnpm`. + +```bash +git clone https://github.com/TBD54566975/tbd-examples.git +cd tbd-examples/javascript/book-reviews +pnpm install +pnpm start +``` + +:::info +## Online Playground +You can open the finished app directly in CodeSandbox +[![Play in CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/TBD54566975/tbd-examples/tree/main/javascript/book-reviews) +::: + +## Setup + +1. Install `Web5` + +```bash +npm install @web5/api +``` + +2. In the root of your project, find and open `package.json` and add `module` as a type: + + + +3. Within a code editor, create a new JS file (e.g. `book_reviews.js`). + +4. Open this JS file and import `Web5` + +```js +import { Web5 } from "@web5/api"; +``` + +
+Additional import for Node 18 + +```js +/* +Needs globalThis.crypto polyfill. +This is *not* the crypto you're thinking of. +It's the original crypto...CRYPTOGRAPHY. +*/ +import { webcrypto } from "node:crypto"; + +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; +``` +
+ +
+Additional imports for React Native + +```js +/* +React Native needs crypto.getRandomValues polyfill and sha512. +This is *not* the crypto you're thinking of. +It's the original crypto...CRYPTOGRAPHY. +*/ +import "react-native-get-random-values"; +import { hmac } from "@noble/hashes/hmac"; +import { sha256 } from "@noble/hashes/sha256"; +import { sha512 } from "@noble/hashes/sha512"; +ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)); +ed.etc.sha512Async = (...m) => Promise.resolve(ed.etc.sha512Sync(...m)); + +secp.etc.hmacSha256Sync = (k, ...m) => + hmac(sha256, k, secp.etc.concatBytes(...m)); +secp.etc.hmacSha256Async = (k, ...m) => + Promise.resolve(secp.etc.hmacSha256Sync(k, ...m)); +``` +
+ +Now we have the Web5 SDK installed and are ready to start building! + +## Connect to Web5 + +The first thing we want to do is **connect**. In Web5 apps, this replaces the traditional login step. Instead of centralized accounts that belong to one application, users have [DIDs](/docs/web5/decentralized-identifiers/what-are-dids) and [DWNs](/docs/web5/decentralized-web-nodes/what-are-dwns), decentralized identity and storage, respectively. + +```js +const {web5, did: userDid} = await Web5.connect(); +``` + +Without parameters, this function will look for a local DWN and DID on the user's device and will connect to it. If it doesn't already exist, a DID and local DWN will be automatically created. + +The `connect()` function returns an instance of `Web5` as `web5` as well as the user's DID as `did`. The `web5` instance gives you access to Web5's three core pillars: `web5.did`, `web5.vc`, and `web5.dwn`. + +:::note +Note that your app can [allow users to connect with an agent](https://tbd54566975.github.io/web5-js/modules/_web5_api.html#md:web5connectoptions) and provide an existing DID and remote DWN. +::: + +## Create Interoperable Data + +Now that the user has connected, your app can obtain any public or permissioned data from their DWN, even if your app didn't create the data. This is the power of DWNs - users are able to keep their data with them and utilize it in any app that allows this. + +In order for data to be interoperable across apps, the apps must understand and support the same data structure. Fortunately, this is not a new concept. In fact, [schema.org](https://schema.org) exists for this very reason - to provide a shared vocabulary for structured data. + +There's a schema for just about anything you can think of, including [reviews](https://schema.org/Review)! + +Provided this is the schema the app will support for reviews, let's indicate that within our code: + +```js +//Schema we'll use for Book Reviews +const schema = { + "context": 'https://schema.org/', + "type": 'Review', + get uri() { return this.context + this.type; } +} +``` + +## Get Book Reviews + +The objects that are written to a DWN are called records. To query for records from a user's DWN, we call `web5.dwn.records.query()` and pass in [filters](/docs/web5/decentralized-web-nodes/query-from-dwn#filterable-record-properties) to find the specific records we want. In our case, we want to query for all records that follow the Review schema. + +```js +//Query book review (search for DWN records) +async function getReviews() { + //highlight-start + let {records} = await web5.dwn.records.query({ + message: { + filter: { + schema: schema.uri + }, + }, + dateSort: 'createdAscending' + }); + //highlight-end + return records; +} + +let existingReviews = await getReviews(); +process.exit(); +``` + +This returns an array of records that match our schema. Your app can display the user's existing reviews as you see fit. + + +## Add Book Review +Your app may offer the ability for users to create new book reviews. If so, you'll write their reviews to their DWNs as opposed to your own backend server. + +Your interface may allow the user to enter a star rating (1-5) as well as a review for any given book. Your app would then take that data and structure objects that match the Review schema. + +This JSON represents multiple reviews the user wants to add and follows the Review schema. + +```js +//Book Reviews +let reviews = [ + { + "@context": schema.context, + "@type": schema.type, + "itemReviewed": { + "@type": "Book", + "name": "The Great Gatsby", + "author": { + "@type": "Person", + "name": "F. Scott Fitzgerald" + }, + "datePublished": "1925", + "genre": "Fiction", + "identifier": "978-1982149482", + }, + "author": { + "@type": "Person", + "identifier": userDid + }, + "datePublished": "2023-09-18", + "reviewRating": { + "@type": "Rating", + "ratingValue": "4.5" + }, + "reviewBody": "A classic novel with timeless themes and memorable characters. Fitzgerald's prose is simply enchanting." + }, + { + "@context": schema.context, + "@type": schema.type, + "itemReviewed": { + "@type": "Book", + "name": "To Kill a Mockingbird", + "author": { + "@type": "Person", + "name": "Harper Lee" + }, + "datePublished": "1960", + "genre": "Fiction", + "identifier": "978-0446310789", + }, + "author": { + "@type": "Person", + "identifier": userDid + }, + "datePublished": "2023-09-18", + "reviewRating": { + "@type": "Rating", + "ratingValue": "5.0" + }, + "reviewBody": "A powerful exploration of morality, justice, and the human condition. Truly a must-read." + }, + { + "@context": schema.context, + "@type": schema.type, + "itemReviewed": { + "@type": "Book", + "name": "1984", + "author": { + "@type": "Person", + "name": "George Orwell" + }, + "datePublished": "1949", + "genre": "Dystopian", + "identifier": "978-0451524935", + }, + "author": { + "@type": "Person", + "identifier": userDid + }, + "datePublished": "2023-09-18", + "reviewRating": { + "@type": "Rating", + "ratingValue": "4.7" + }, + "reviewBody": "A disturbing vision of a totalitarian future. Orwell's work is as relevant today as it was when it was first published." + }, + { + "@context": schema.context, + "@type": schema.type, + "itemReviewed": { + "@type": "Book", + "name": "Brave New World", + "author": { + "@type": "Person", + "name": "Aldous Huxley" + }, + "datePublished": "1932", + "genre": "Dystopian", + "identifier": "978-0060850524", + }, + "author": { + "@type": "Person", + "identifier": userDid + }, + "datePublished": "2023-09-18", + "reviewRating": { + "@type": "Rating", + "ratingValue": "4.8" + }, + "reviewBody": "A deeply disturbing yet essential read. Huxley's vision of a future driven by technology and hedonism serves as a potent warning for society." + } +] +``` + +Notice that the reviews contain `author.identifier` that specifies the DID of the user who is authoring the review. + +Now that we have the data in an interoperable format, we want to write it to the user's DWN. But first, let's make a function to see if the review they are submitting already exists within their DWN. + +```js +//checks if review already exists (you may not want duplicate data in the DWN) +async function isReviewPresent(review) { + for(let record of existingReviews) { + let bookData = await record.data.json(); + let isbn = bookData.itemReviewed.identifier; + + if(isbn === review.itemReviewed.identifier) { + return true; + } + }; + return false; +} +``` + +Within our `addReviews()` function, we'll loop through the reviews that the user wants to add, call `isReviewPresent()` to make sure it doesn't already exist, and if it doesn't, we'll call `web5.dwn.records.create()` to add the review to the user's DWN. + +```js +//Create book review (write record to DWN) +async function addReviews() { + for (const review of reviews) { + let reviewExists = await isReviewPresent(review); + if (reviewExists) { + console.log(`Review for ${review.itemReviewed.name} already exists`); + } + else { + //highlight-start + const response = await web5.dwn.records.create({ + data: review, + message: { + schema: schema.uri, + dataFormat: 'application/json', + published: true, + }, + }); + //highlight-end + + if (response.status.code === 202) { + console.log(`Review for ${review.itemReviewed.name} added successfully`); + } else { + console.log(`${response.status}. Error adding review for ${review.itemReviewed.name}`); + } + } + } + existingReviews = await getReviews(); +} + +let existingReviews = await getReviews(); +//highlight-next-line +await addReviews(); +process.exit(); +``` + +Let's look a bit closer at the call that creates the record: + +```js +const response = await web5.dwn.records.create({ + data: review, + message: { + schema: schema.uri, + dataFormat: 'application/json', + published: true, + }, +}); +``` + +We see that `create()` accepts `data` and `message`. The `data` is the payload of the record and the `message` is metadata about that payload. + +Notice that we provide the actual book review as the value of `data`, and for the `message`, we're specifying the: +* URI of the schema +* format of the data +* [publicity](/docs/web5/decentralized-web-nodes/publishing-records) of the data. Because we specified the records as published, they are available for public queries without requiring authorization. + +All of this metadata helps with the discoverability of the data by apps. + +Also note that while the user wants to create multiple reviews, there's no way to create multiple records at the same time with Web5. Instead, we called `create()` for each record that needed to be added, via a loop. + +## Update Book Review + +Your app may have access to update book reviews as well. Let's write a function that calls the `web5.dwn.record.update()` method to change the rating of one of the reviews. + +```js +//Update book review rating +async function updateReviewRating(review, newRating) { + let bookData = await review.data.json(); + console.log(`old rating for ${bookData.itemReviewed.name}`, bookData.reviewRating.ratingValue); + + //highlight-start + //Update the value within the JSON then send the entire JSON to update + bookData.reviewRating.ratingValue = newRating; + let response = await review.update({ + data: bookData + }); + //highlight-end + + if (response.status.code === 202) { + //Obtain the updated record + const { record: updatedReview } = await web5.dwn.records.read({ + message: { + filter: { + recordId: review.id + } + }, + }); + const updatedData = await updatedReview.data.json(); + console.log(`updated rating for ${bookData.itemReviewed.name}`, updatedData.reviewRating.ratingValue); + } + else console.log(`${response.status}. Error updating rating for ${bookData.itemReviewed.name}`); +} + +let existingReviews = await getReviews(); +await addReviews(); +//highlight-next-line +await updateReviewRating(existingReviews[1], "4.2"); +process.exit(); +``` + +## Delete Book Reviews +If your app is permissioned to do so, you can also delete book reviews from the user's DWN. + +```js +//Delete all book reviews +async function deleteReviews() { + let records = await getReviews(); + + for (const record of records) { + let title = (await record.data.json()).itemReviewed.name; + //highlight-start + let response = await web5.dwn.records.delete({ + message: { + recordId: record.id, + } + }); + //highlight-end + console.log(`deleted ${title}. status: ${response.status.code}`); + } +} + + +let existingReviews = await getReviews(); +await addReviews(); +await updateReviewRating(existingReviews[1], "4.2"); +//highlight-next-line +await deleteReviews(); + +process.exit(); +``` + + +Congrats! You've created an application that creates, reads, updates, and deletes book reviews from a user's DWN. The user is free to take their reviews with them and use it in any app that they grant permission. diff --git a/site/web5_versioned_docs/version-1.0.1/apps/dinger-tutorial.mdx b/site/web5_versioned_docs/version-1.0.1/apps/dinger-tutorial.mdx new file mode 100644 index 000000000..df289a3a7 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/apps/dinger-tutorial.mdx @@ -0,0 +1,419 @@ +--- +sidebar_position: 1 +--- + +import KnowledgeCheck from '@site/src/components/KnowledgeCheck'; + +# Build a Chat App + +Welcome to Web5, the magical land where Web2 and Web3 intertwine. My name is Alice. I am a wanderer in search of an application where I can send short-text messages to my best friend, Bob. + +🚀 **Your mission** + +Build [Dinger](https://dinger-chat.vercel.app/), a self-sovereign, modern day pager that allows users to send short text-based messages or "dings" to each other. + +🛠️ **Your toolkit** + +To build this app, you will wield the following technologies: + +- [React](https://react.dev/) - A JavaScript library, your brush to paint user interfaces, craft reusable components, and manage the state of your creation. +- [Next.js](https://nextjs.org/) - A React metaframework, your compass to guide you through creating pages, managing routes, and managing server-side rendering. +- [Web5.js](https://tbd54566975.github.io/web5-js/) - A Web5 JavaScript SDK, your key to creating decentralized applications, facilitating direct communication between users, and granting them sovereignty over their data and identity. + +🏆 **Your reward** + +By the end of this journey, you will acquire a deeper understanding of peer-to-peer communication in a Web5 ecosystem. + +If you're ready for the challenge, let's get started! + +:::note +This tutorial is most suitable for those who prefer to learn by doing. +::: + +## Level 1: Explore the starter code + +Good news! You don't have to start from scratch. There is a pre-built application that contains the user interface for Dinger. Right now, the functions in the code don’t do much. They just print messages to the console and don’t have any real actions. As you progress through the tutorial, you will add logic to the functions. +### 📝 Task + +To access the starter code, follow the instructions below or explore the project directly in CodeSandbox. + +Note: If you don't have `pnpm` installed, you can install it by running `npm install -g pnpm`. + +```bash +git clone https://github.com/TBD54566975/tbd-examples.git +cd tbd-examples/javascript/dinger-starter +pnpm install +pnpm start +``` + +[![Play in CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/TBD54566975/tbd-examples/tree/main/javascript/dinger-starter) + +
+Finished Dinger App +

+ +If you’d like to skip ahead and see the finished version of this tutorial, you can check out the running app on CodeSandbox. + +[![Play in CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/TBD54566975/tbd-examples/tree/main/javascript/dinger) + +You can also download and run the example by executing: + +Note: If you don't have `pnpm` installed, you can install it by running `npm install -g pnpm`. + +```bash +git clone https://github.com/TBD54566975/tbd-examples.git +cd tbd-examples/javascript/dinger +pnpm install +pnpm start +``` + +There is also a hosted example deployed at [https://dinger-chat.vercel.app/](https://dinger-chat.vercel.app/) +

+
+ +To fully interact with the web app, please click **"Open in New Tab"** as demonstrated in the GIF below. + +![Description of the image](@site/static/img/open-sandbox-in-new-tab.gif) + +### 🕵️ What to Explore +- Navigate to the `src/pages/index.js` file. +- Identify the functions that currently only print console.logs. +- Try to understand how each function interacts. + +## Level 2: Connect to Web5 and create a DID +### 📝 Task +Locate the line: +```javascript title="src/pages/index.js" +console.log(`this log is in initWeb5`); +``` +and replace it with the following code snippet: +```javascript title="src/pages/index.js" +const { web5, did } = await Web5.connect(); +setWeb5(web5); +setMyDid(did); +``` + +### 🧩 Breaking it down +- **Web5.connect()** returns a Web5 instance: It initializes and returns an instance of Web5, allowing you to interact with the Web5 ecosystem. +- **Web5.connect()** also creates or connects to a DID: A DID ([Decentralized Identifier](https://developer.tbd.website/docs/web5/decentralized-identifiers/what-are-dids)) is a user’s unique ID card for the decentralized, digital world. It belongs only to you. `Web5.connect()` will either connect to an existing DID that you have or create a new one for you if you don't have one yet. A DID acts as an authenticator for Web5 apps, removing the need for login credentials. + +### 📘 Learn more +Learn more about [DID authentication](https://developer.tbd.website/blog/did-authentication). + +## Level 3: Define a protocol +### 📝 Task +Locate the line: +```javascript title="src/pages/index.js" +console.log('this log is in createProtocolDefinition'); +``` + +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +const dingerProtocolDefinition = { + protocol: "https://blackgirlbytes.dev/dinger-chat-protocol", + published: true, + types: { + ding: { + schema: "https://blackgirlbytes.dev/ding", + dataFormats: ["application/json"], + }, + }, + structure: { + ding: { + $actions: [ + { who: "anyone", can: ["create"] }, + { who: "author", of: "ding", can: ["read"] }, + { who: "recipient", of: "ding", can: ["read"] }, + ], + }, + }, +}; +return dingerProtocolDefinition; +``` +### 🧩 Breaking it down +- **dingerProtocolDefinition:** This object defines the protocol, its structure, and it grants permissions outlining who can perform specific actions like reading or writing a ding. + +### 📘 Learn more +Learn more about [defining protocols](https://developer.tbd.website/docs/web5/decentralized-web-nodes/what-are-protocols#defining-a-protocol). + +## Level 4: Install the defined protocol +### 📝 Task +Locate the line: +```javascript title="src/pages/index.js" +console.log(`this log is in installProtocolLocally`); +``` + +And replace it with the following code snippet: +```javascript title="src/pages/index.js" + return await web5.dwn.protocols.configure({ + message: { + definition: protocolDefinition, + }, + }); +``` +### 🧩 Breaking it down +- **web5.dwn.protocols.configure():** This function installs the protocol. The protocol needs to be installed because it ensures that both the sender's and recipient's applications (or Decentralized Web Nodes - DWNs) are aligned in terms of communication standards and data formats. + +## Level 5: Query the protocol +### 📝 Task +```javascript title="src/pages/index.js" +console.log(`this log is in queryForProtocol`); +``` + +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +return await web5.dwn.protocols.query({ + message: { + filter: { + protocol: "https://blackgirlbytes.dev/dinger-chat-protocol", + }, + }, +}); +``` +### 🧩 Breaking it down +- **web5.dwn.protocols.query():** This function checks if the protocol already exists + +### 📘 Learn more +Learn more about [querying protocols](https://developer.tbd.website/docs/web5/decentralized-web-nodes/what-are-protocols#protocols-in-practice). + +## Level 6: Bring all the protocol steps together +### 📝 Task +```javascript title="src/pages/index.js" +console.log(`this log is in configureProtocol`); +``` +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +const protocolDefinition = await createProtocolDefinition(); + +const { protocols: localProtocol, status: localProtocolStatus } = await queryForProtocol(web5); + +console.log({ localProtocol, localProtocolStatus }); + +if (localProtocolStatus.code !== 200 || localProtocol.length === 0) { + + const { protocol, status } = await installProtocolLocally(web5, protocolDefinition); + console.log("Protocol installed locally", protocol, status); + const { status: configureRemoteStatus } = await protocol.send(did); + console.log("Did the protocol install on the remote DWN?", configureRemoteStatus); + +} else { + console.log("Protocol already installed"); +} +``` +- **configureProtocol()**: This function is responsible for configuring the protocol in both the local and remote DWNs. It first checks if the protocol is already installed locally. If not, it installs the protocol locally and then attempts to install it on the remote DWN associated with the user's DID. + +### 📘 Learn more +- Learn more about [protocols](https://developer.tbd.website/docs/web5/decentralized-web-nodes/what-are-protocols/). + + +## Level 7: Write to the DWN + +### 📝 Task +Locate the line: +```javascript title="src/pages/index.js" +console.log(`this log is in writeToDwn`); +``` + +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +const { record } = await web5.dwn.records.write({ + data: ding, + message: { + protocol: "https://blackgirlbytes.dev/dinger-chat-protocol", + protocolPath: "ding", + schema: "https://blackgirlbytes.dev/ding", + recipient: recipientDid, + }, +}); +return record; +``` + +### 🧩 Breaking it down +- You are storing each ding on a Decentralized Web Node (DWN), or personal data store. This method **web5.dwn.records.write()** is responsible for writing data to the DWN. + + +### 📘 Learn more +- Learn more about[ Decentralized Web Nodes](/docs/web5/decentralized-web-nodes/what-are-dwns) and [writing DWN records](https://developer.tbd.website/docs/web5/decentralized-web-nodes/write-to-dwn). + +## Level 8: Send dings + +### 📝 Task +Locate the line: +```javascript title="src/pages/index.js" +console.log(`this log is in sendRecord`); +``` + +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +return await record.send(recipientDid); +``` + +### 🧩 Breaking it down +- Users can have multiple DWNs, such as on their phone, laptop, or in the cloud. The method **record.send()** immediately sends the ding to all of the recipient's Decentralized Web Nodes (DWNs). + + +### 📘 Learn more +- Learn more about [sync intervals](https://developer.tbd.website/docs/web5/decentralized-web-nodes/sync#sync-intervals). + +## Level 9: Retrieve dings +### 📝 Task 1 +Locate the line: +```javascript title="src/pages/index.js" +console.log(`this log is in fetchSentMessages`); +``` +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +const response = await web5.dwn.records.query({ + message: { + filter: { + protocol: "https://blackgirlbytes.dev/dinger-chat-protocol", + }, + }, +}); + +if (response.status.code === 200) { + const sentDings = await Promise.all( + response.records.map(async (record) => { + const data = await record.data.json(); + return data; + }) + ); + console.log(sentDings, "I sent these dings"); + return sentDings; +} else { + console.log("error", response.status); +} +``` +### 📝 Task 2 +Locate the line: +```javascript title="src/pages/index.js" +console.log(`this log is in fetchReceivedMessages`); +``` +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +const response = await web5.dwn.records.query({ + from: did, + message: { + filter: { + protocol: "https://blackgirlbytes.dev/dinger-chat-protocol", + schema: "https://blackgirlbytes.dev/ding", + }, + }, +}); + +if (response.status.code === 200) { + const receivedDings = await Promise.all( + response.records.map(async (record) => { + const data = await record.data.json(); + return data; + }) + ); + console.log(receivedDings, "I received these dings"); + return receivedDings; +} else { + console.log("error", response.status); +} +``` +### 📝 Task 3 +Locate the line: +```javascript title="src/pages/index.js" +console.log(`this log is in fetchDings`); +``` +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +const sentMessages = await fetchSentMessages(web5, did); +const receivedMessages = await fetchReceivedMessages(web5, did); +const allMessages = [...(sentMessages || []), ...(receivedMessages || [])]; +setAllDings(allMessages); + +``` + +### 🧩 Breaking it down +- The method web5.dwn.records.query() queries the DWN for records matching a specified filter. In the above code snippet, it retrieves records with the protocol https://blackgirlbytes.dev/dinger-chat-protocol. + +### 📘 Learn more +- - Learn more about how to [query DWN records](/docs/web5/decentralized-web-nodes/query-from-dwn) and [filterable record properties](/docs/web5/decentralized-web-nodes/query-from-dwn#filterable-record-properties). + + +## Level 10: Sync the UI with the DWN + +### 📝 Task +Locate the line: +```javascript title="src/pages/index.js" +console.log(`this log is in intervalId`); +``` + +And replace it with the following code snippet: +```javascript title="src/pages/index.js" +await fetchDings(web5, myDid); +``` + +This line is located within the following function: +```javascript title="src/pages/index.js" + useEffect(() => { + if (!web5 || !myDid) return; + + const intervalId = setInterval(async () => { + console.log(`this log is in intervalId`); + }, 2000); + + return () => clearInterval(intervalId); + }, [web5, myDid]); +``` +### 🧩 Breaking it down +- The **useEffect()** and **setInterval()** fetch new dings every two seconds, allowing the UI to accurately display the chat. + +### 📘 Learn more +- Learn more about React's [useEffect()](https://react.dev/reference/react/useEffect) hook and JavaScript's [setInterval()](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) method. + +## Level 11: Try it out! +### 📝 Task +Time to test the Dinger app by simulating a conversation between two users by using two different browser sessions. + +**Set Up Your Browsers**: +- Open your browser in **regular mode**. +- Open a new window in **incognito/private mode**. + +**Send a Message from Incognito to Regular Mode**: +- In the **incognito** window, locate and press the `Copy DID` button to copy the DID. +- Switch to the **regular mode** browser window. +- Press the `Create +` button +- Paste the copied DID into the `recipient DID` input box, then `Confirm` +- Type a short message in the `note` input box. +- Press `Send` to send the message. + +**Reply from Regular Mode to Incognito**: +- In the **regular mode** window, locate and press the `Copy DID` button to copy the DID. +- Switch back to the **incognito** window. +- Press the `Create +` button +- Paste the copied DID into the `recipient DID` input box, then `Confirm` +- Type a reply in the `note` input box. +- Press `Send` to send your reply. + +After following these steps, you should see a conversation taking place between the two browser sessions! + +
+Not receiving any messages? +Sync intervals occur every two minutes. If you're eager to see immediate updates for testing purposes, you can adjust the sync settings. Update the line: + +```javascript title="src/pages/index.js" +const { web5, did } = await Web5.connect(); +``` + +to: + +```javascript title="src/pages/index.js" +const { web5, did } = await Web5.connect({sync: '5s'}); +``` +This change forces a synchronization interval to happen every 5 seconds. However, be cautious: this is not recommended for production applications as it can lead to higher resource usage, battery drain, and potential rate limiting. +
+ +## 🧠 Knowledge check + + + +## 🫡 Mission complete +Congratulations! By building Dinger, you've successfully empowered users like Alice to communicate peer-to-peer and maintain sovereignty over her data and identity. Now, Alice can send messages to her best friend, Bob. +You can check out the finished version of the app running on CodeSandbox. + +[![Play in CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/TBD54566975/tbd-examples/tree/main/javascript/dinger) diff --git a/site/web5_versioned_docs/version-1.0.1/apps/shared-todo-app.mdx b/site/web5_versioned_docs/version-1.0.1/apps/shared-todo-app.mdx new file mode 100644 index 000000000..02c19a891 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/apps/shared-todo-app.mdx @@ -0,0 +1,592 @@ +--- +sidebar_position: 3 +--- + +import PackageJson from '@site/src/components/PackageJson'; + +# Build a Shared Todo App + +In the [Build a Todo App](/docs/web5/apps/todo-app-tutorial) tutorial, we learned how to build a single-user decentralized todo app with `Web5.js` and `Vue.js`. + +This tutorial takes it a step further. We will expand on the core features and build out a more collaborative app that allows multiple users work on the same, shared todo list. + +This is also a multi-paged app, so we will learn how we can reuse a `web5` instance across multiple pages. + +## Objectives + +Bob and Alice are two friends that love doing things together. They often have specific goals and a bunch of tasks each of them have to complete to achieve this goal. + +We will build a decentralized shared todo app that allows them to collaborate on their tasks while also being able to have these tasks on other apps they use if needed. + +We are going to learn how to: +- Enforce permissions or authorization on records through [protocols](/docs/web5/decentralized-web-nodes/what-are-protocols) +- Send and update records across multiple users' [Decentralized Web Nodes (DWN)](/docs/web5/decentralized-web-nodes/what-are-dwns) + +By the end of this tutorial, we'll have gained an understanding of protocols, DWN to DWN communication, and how to build a multi-user app on the decentralized web. + + +## Our Toolkit + +We will use the following tools to build our app: +- [Web5 SDK](https://www.npmjs.com/package/@web5/api) - a JavaScript SDK for building decentralized web apps +- [NuxtJS](https://nuxtjs.org/) - a VueJS framework for building web applications +- [TailwindCSS](https://tailwindcss.com/) - a utility-first CSS framework for rapidly building custom designs + +## Get the Skeleton App + +To follow this tutorial, you can use your own UI design or use our starter code. As you work through the tutorial, we'll add logic to the various methods and make adjustments to the UI. + +To access the starter code, follow the instructions below or explore the project directly in CodeSandbox. + +Note: If you don't have `pnpm` installed, you can install it by running `npm install -g pnpm`. + +```bash +git clone https://github.com/TBD54566975/tbd-examples.git +cd tbd-examples/javascript/todo-starter +pnpm install --shamefully-hoist +pnpm dev +``` + + +[![Play in CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/TBD54566975/tbd-examples/tree/main/tbd-examples/javascript/shared-todo-starter) + +
+Finished Shared Todo App +

+ +If you’d like to skip ahead and see the finished version of this tutorial, you can check out the running app on CodeSandbox. + +[![Play in CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/TBD54566975/tbd-examples/tree/main/javascript/shared-todo) + +You can also download and run the example by executing: + +Note: If you don't have `pnpm` installed, you can install it by running `npm install -g pnpm`. + +```bash +git clone https://github.com/TBD54566975/tbd-examples.git +cd tbd-examples/javascript/shared-todo +pnpm install --shamefully-hoist +pnpm dev +``` + + +

+
+ +## Set Up the App + +Add `Web5` to package.json in the `dependencies` section: + + + +Install the dependencies and get the project running: + +```bash +npm install +npm run dev +``` + +You should now have the app running on [http://localhost:3000](http://localhost:3000). + +## App Architecture + +We are building a [NuxtJS App](https://nuxt.com/) using file-based routing through the `pages` folder. We have the following key files in our app: + +- `pages/index.vue` - the home page of the app +- `pages/todos/[id].vue` - the page for viewing a todo list +- `assets/shared-todo-protocol.json`: a JSON file defining the protocol used for sharing to-do lists + +Our shared todo app will have the following features that we need to build out: + +- Creating a todo list with another user +- Displaying all todo lists with different users +- Adding tasks to a todo list +- Marking tasks as completed + +## Initialize Constants and Variables + +We are building a multi-paged application and as a result, we will need to access Web5 on multiple pages. However we want to avoid running `Web5.connect()` on every page as it would impact performance. Instead, we will create a Nuxt plugin and have our Web5 instance and DID available across our application. + +Create a new file at `plugins/web5.client.js` and add the following code: + +```js title="plugins/web5.client.js" + import { Web5 } from '@web5/api'; + + export default defineNuxtPlugin({ + async setup (nuxtApp) { + console.log("Plugin loaded") + let web5; + let myDID; + + ({ web5, did: myDID } = await Web5.connect({ + sync: '5s' + })); + + console.log("Here's your DID", myDID) + + return { + provide: { + web5, + myDID + } + }; + }, + }); +``` +The first time a user accesses your Shared Todo app, you’ll need to handle creating an _account_ for them - this means ensuring they have a DID and DWN available to access their app data. Once they come back for subsequent sessions, you’ll want to fetch and load that data for them. + + +In the code above, we import and connect to Web5 while setting `sync` interval to 5 seconds. This is the amount of time between automatic syncs of the user's local and remote DWNs. We also return the `web5` and `myDID` variables to be available across our application. + +Open `pages/index.vue` and add the following code after all the `import` statements under ` +``` + +## Address polyfill errors +Sometimes, you may get errors where the Web5 SDK isn't initializing in your client app e.g `process.nextTick is not a function`. This is usually because some polyfills are missing. To solve this; + +- Install the `vite-plugin-node-stdlib-browser` plugin: + +```bash +npm install vite-plugin-node-stdlib-browser --save-dev +``` + +If you are using Nuxt, add this to your `nuxt.config.js` file: + +```js title="nuxt.config.ts" +import nodePolyfills from 'vite-plugin-node-stdlib-browser'; + +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + devtools: { enabled: true }, + ssr: false, + vite: { + plugins: [nodePolyfills()], + }, +}) +``` + +Then add the following to your `vite.config.js` file: + +```js title="vite.config.js" +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import nodePolyfills from 'vite-plugin-node-stdlib-browser'; + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + target: ['chrome109', 'edge112', 'firefox102', 'safari15.6', 'ios15.6'] + }, + define: { + global: 'globalThis' + }, + plugins: [vue(), nodePolyfills()], +}); + +``` + +If you run into any errors, check out our [troubleshooting guide](./common-errors) for a possible solution. + +## Uncaught ReferenceError: xxxx is not defined +With the latest Nuxt releases, there seems to be a transpiling error when trying to deploy Nuxt with `npm run build`. If you get an error like `Uncaught ReferenceError: require is not defined`, you can fix it by adding the following to your `nuxt.config.js` file: + +```js title="nuxt.config.js" +export defineConfig({ + vite: { + build: { + commonjsOptions: { transformMixedEsModules: true } + } + } +}); +``` + +If the error isn't fixed, then try downgrading your Vue / Nuxt version to lower than you currently have it. \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/update-to-dwn.mdx b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/update-to-dwn.mdx new file mode 100644 index 000000000..15565f7ba --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/update-to-dwn.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 7 +--- + +import CodeSnippet from '@site/src/components/CodeSnippet'; + +# Update DWN Records + +The following snippet allows you to read and update a particular record in a DWN: + + diff --git a/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/using-identity-agents.mdx b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/using-identity-agents.mdx new file mode 100644 index 000000000..e84290004 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/using-identity-agents.mdx @@ -0,0 +1,93 @@ +--- +draft: true +--- + +import CodeSnippet from '@site/src/components/CodeSnippet'; + +# Using Identity Agents + +Identity Agents are a specialized type of [agent](https://developer.tbd.website/docs/web5/decentralized-web-nodes/agents/) that act as personal identity managers. Similar to how a password manager securely stores login credentials for different web apps, Identity Agents manage your [DIDs](https://developer.tbd.website/docs/web5/decentralized-identifiers/what-are-dids). + +By default, the `Web5.connect()` function generates a **new** identity; however, Identity Agents allow you to connect to a Web5 app with an **existing** identity. This is useful for maintaining a cohesive online presence. + +This guide will walk you through creating an Identity Agent as a standalone application to manage multiple user identities and connect to a **separate** Web5 application. + + +
+Prerequisites + +**Install the following packages** + +```bash +npm i @web5/dids @web5/identity-agent @web5/api +``` + +**Import the following modules** + +```js +import { DidIonMethod } from '@web5/dids'; +import { IdentityAgent } from '@web5/identity-agent'; +import { Web5, getTechPreviewDwnEndpoints } from '@web5/api'; +``` + +
+ +## Initialize the Agent +To create an Identity Agent, start by creating an instance of the `IdentityAgent`. + + + +## Prompt Users to Authenticate +In your Identity Agent app, include the code snippet below to prompt users to authenticate with a one-time passphrase for security purposes. + + + +## Connect DIDs to DWNs +To add [DWNs](https://developer.tbd.website/docs/web5/decentralized-web-nodes/what-are-dwns/) as service endpoints to the DIDs that will be managed by the Identity Agent, you can add the endpoints as `didOptions`. + +Below is an example using TBD-hosted DWN endpoints: + + + + +
+Expected Output of didOptions + +```js +{ + keySet: { verificationMethodKeys: [ [Object], [Object] ] }, + services: [ + { + id: '#dwn', + serviceEndpoint: [Object], + type: 'DecentralizedWebNode' + } + ] +} +``` + +
+ +## Create Identities +Now that `didOptions` contains cryptographic key pairs and service endpoints, you can create as many identities as necessary. + +Each identity, linked to a unique DID, represents and compartmentalizes different aspects of a user. For example, a user can have an identity for social media interactions and another for professional engagements. + + + +:::note +* The `name` field serves as a friendly name for reference. +* The `kms` field stands for [Key Management Service](/docs/web5/decentralized-identifiers/key-management) and can securely store and manage the cryptographic keys associated with a DID. If you don't specify a particular key management service, such as AWS Key Management Service, Web5 will default to an in-memory key manager. +::: + +## Use your Identity in a Web5 Application +Once your Identity Agent is set up to manage multiple identities, you can use it to help you login to a Web5 application. Here's the typical workflow: + +1. The Web5 application may display a QR code or a deep link for login purposes. +2. Use the Identity Agent to scan the QR code or click the deep link to establish a connection between your Identity Agent and the Web5 application. +3. The Identity Agent then prompts you to select the identity you prefer to use for authentication. + +## Connect to Web5 +Finally, the Web5 application can use the DID linked to your identity, so that you can interact within the Web5 application as your chosen identity. + + diff --git a/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/web5-connect.mdx b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/web5-connect.mdx new file mode 100644 index 000000000..327e9cf74 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/web5-connect.mdx @@ -0,0 +1,266 @@ +--- +sidebar_position: 2 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ApiDetails from '@site/src/components/ApiDetails'; +import ScriptSrc from '@site/src/components/ScriptSrc'; +import ImportSrc from '@site/src/components/ImportSrc'; + +import CodeSnippet from '@site/src/components/CodeSnippet'; + + +# Web5.connect() + +The Web5 JS SDK provides a simple interface to get started building Web5 applications quickly. It provides APIs to use [Decentralized Identifiers (DIDs)](/docs/web5/decentralized-identifiers/what-are-dids) and [Decentralized Web Nodes (DWNs)](/docs/web5/decentralized-web-nodes/what-are-dwns) right away by initializing the SDK through `Web5.connect()`. +
+ +## Install + + + + +```bash +npm install @web5/api +``` + + + + + +or + + + + + + +## Import + + + + +
+Node 19+ + +```js +import { Web5 } from '@web5/api'; +``` +
+ +
+Node <=18 + +```js +/* +Needs globalThis.crypto polyfill. +This is *not* the crypto you're thinking of. +It's the original crypto...CRYPTOGRAPHY. +*/ +import { webcrypto } from "node:crypto"; +import { Web5 } from '@web5/api'; + +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; +``` +
+ +
+React Native + +```js +/* +React Native needs crypto.getRandomValues polyfill and sha512. +This is *not* the crypto you're thinking of. +It's the original crypto...CRYPTOGRAPHY. +*/ +import "react-native-get-random-values"; +import { hmac } from "@noble/hashes/hmac"; +import { sha256 } from "@noble/hashes/sha256"; +import { sha512 } from "@noble/hashes/sha512"; +import { Web5 } from '@web5/api'; + +ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)); +ed.etc.sha512Async = (...m) => Promise.resolve(ed.etc.sha512Sync(...m)); + +secp.etc.hmacSha256Sync = (k, ...m) => + hmac(sha256, k, secp.etc.concatBytes(...m)); +secp.etc.hmacSha256Async = (k, ...m) => + Promise.resolve(secp.etc.hmacSha256Sync(k, ...m)); +``` +
+ +
+ + + + +or + + + + +
+ +
+ +
+ +## Connect + +Using `Web5.connect()`, an instance of Web5 is created that enables an app to connect to an existing decentralized identifier (DID) either by direct creation or connection to an [identity agent app](/docs/web5/decentralized-web-nodes/agents) or create a new one. The method also creates a locally running Decentralized Web Node (DWN) and associates it with the DID. + +This local DWN is synchronized with a remote DWN node (if specified) that enables connectivity and sync across the user's devices and other users. While the Web5 JS SDK is in technical preview, a remote DWN is automatically provisioned and connected to the local DWN - syncing every 2 minutes. + + + +### Options + +Enables an app to request connection to a user's local identity app (like a desktop or mobile [agent](/docs/web5/decentralized-web-nodes/agents) - work is underway for reference apps of each), or generate an in-app DID to represent the user (e.g. if the user does not have an identity app) + +:::note +The outputs of this method invocation will be used throughout the other API methods. +::: + +#### Parameters + +, + }, + } + ]} + />, + }, + } + ]} +/> + +#### Return Value + + + +
+ +## Examples +Below are examples of how to use `Web5.connect()` with or without additional option parameters. + +##### Connect with no parameters + + +##### Connect to DIF Community Node + +Connect to the free [DIF community node instance](https://blog.identity.foundation/dwn-community-node/) hosted by Google Cloud: + +```js +const {did, web5} = await Web5.connect({ + didCreateOptions: { + // Use community DWN instance hosted on Google Cloud + dwnEndpoints: ['https://dwn.gcda.xyz'] + }, + registration: { + onSuccess: () => { + /* + Registration succeeded, set a local storage value to indicate the user + is registered and registration does not need to occur again + */ + }, + onFailure: (error) => { + /* + Registration failed, display an error message to the user, and pass in + the registration object again to retry next time the user connects + */ + }, + }, +}) +``` + +##### Connect with an existing Decentralized Web Node endpoint + + + +##### Connect with an existing agent and existing DID +If `connectedDid` is provided, the `agent` property must also be provided. + + + +##### Configure sync interval when connecting to Web5 +Sync defaults to running every 2 minutes and can be set to [any value accepted by ms](https://www.npmjs.com/package/ms). To disable sync set to 'off'. + + +
diff --git a/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/what-are-dwns.md b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/what-are-dwns.md new file mode 100644 index 000000000..0e6faf5e1 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/what-are-dwns.md @@ -0,0 +1,68 @@ +--- +title: What are DWNs +hide_title: true +sidebar_position: 1 +--- + +# Decentralized Web Nodes + +A Decentralized Web Node (DWN) is a data storage and message relay mechanism that entities can use to locate public or private permissioned data related to a given [Decentralized Identifier(DID)](/docs/web5/decentralized-identifiers/what-are-dids). + +## Personal Data Store + +A DWN is a personal data store. This means you can: + +- **Own your data:** You decide where to host your node. You control who has access. +- **Back up your data:** Host multiple nodes in different places, and keep them all [synced](/docs/web5/decentralized-web-nodes/sync) effortlessly. If one goes down, you have your backup. +- **Send and receive data:** Alice controls her DWN using her DID. Bob controls his DWN with his DID. Alice can send data to Bob just by resolving his DID. + +
+ +![](/img/did-resolve.png) + + + +Topology of an exchange between Decentralized Web Nodes, duplicated from the [DIF Decentralized Web Node Spec](https://identity.foundation/decentralized-web-node/spec/#topology) + + + +
+ +## Authorization + +DWNs have two mechanisms to allow others access to read, write, or delete data on your node. + +- **Permissions:** Allow someone access to read, write, or delete specific data records on your node. +- **Protocols:** Install a [protocol](/docs/web5/decentralized-web-nodes/what-are-protocols) that lets you define data types and authorization for a decentralized web app. + +The easiest way to understand this distinction is to think of permissions as active, explicit, and manual, whereas protocols are passive, syntactic, and contractual. + +## Data Model + +Data types are bound to known schemas, letting applications agree on data models. This opens the door to applications working together in ways that have been much more difficult with traditional development platforms. + +User-defined schemas are not validated on the payload of the record, as this sort of validation is impossible for encrypted payloads since the DWN doesn't have the private keys to decrypt. It is up the application layer if such validation is to be performed on the data. + +## Messaging + +All communication is done through simple JSON objects called messages. Web5 constructs messages and helps you send them to their destination by resolving a recipient's DID and getting the address of their Decentralized Web Node. A message can install protocols, grant permissions, and read, write, update, query, or delete a record. For example, a message writing a record to a DWN may look like the example below. + +```json +{ // Message + "recordId": "b65b7r8n7bewv5w6eb7r8n7t78yj7hbevsv567n8r77bv65b7e6vwvd67b6", + "descriptor": { + "parentId": CID(PREVIOUS_DESCRIPTOR), + "dataCid": CID(data), + "dateCreated": 123456789, + "published": true, + "encryption": "jwe", + "interface": "Records", + "method": "Write", + "schema": "https://schema.org/SocialMediaPosting", + "commitStrategy": "json-merge", + "dataFormat": "application/json" + } +} +``` + +See the [DWN spec](https://identity.foundation/decentralized-web-node/spec/#messages) for a detailed explanation of each field. diff --git a/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/what-are-protocols.mdx b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/what-are-protocols.mdx new file mode 100644 index 000000000..ff7b06026 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/what-are-protocols.mdx @@ -0,0 +1,390 @@ +--- +title: What are Protocols +sidebar_position: 10 +--- + +import ReactPlayer from 'react-player'; + +# Protocols +Protocols define a data schema and the contract by which two Decentralized Web Nodes (DWNs) agree to communicate and share data. In other words, protocols define both the data schema and the data permissions as it relates to a certain application or use case. + +Protocols are written in a JSON format that is flexible enough to detail what objects are stored as part of the application, as well as who has what permissions on those objects. Although schema and permissions are traditionally decoupled in Web2 development, protocol documents serve as a robust and concise way to define how your application handles data. + +## Protocol Basics + +Every protocol document has a few basic keys: + +- `types` - Defines all the elements used in your protocol +- `structure` - Outlines the relationship and interaction rules between different types +- `$actions` - Specifies a set of permissions outlining who is allowed to perform specific actions like reading or writing on a given type + +These terms are combined in a human-readable way to define both the data schema and permissions of your app. + +To apply these concepts, let's consider if we wanted to build a basic social networking application. In our social network, we want users to be able to add posts and replies, as well as images with captions that can be replied to. This application has a data design of two key objects: posts and images. Images have caption and reply properties, while posts just have a replies property. + +Now let’s imagine how we’d construct the permissions for such an app. We want all users to be able to add images and posts, but we have a few constraints on the other properties: + +- Only the author should be able to add an image caption +- Only the recipient(s) or the author of a post should be able to reply to it +- Only the recipient(s) of an image should be able to reply to it + +![illustration of protocol rules](/img/protocols-illustration.png) + +## Defining a Protocol + +We know the key words for defining protocols - `types`, `structure`, and `actions` - as well as our data and permissions schemas. So our protocol would look this: + +```js +const protocolDefinition = { + "protocol": "https://social-media.xyz", + "published": true, + "types": { + "post": { + "schema": "https://social-media.xyz/schemas/postSchema", + "dataFormats": ["text/plain"] + }, + "reply": { + "schema": "https://social-media.xyz/schemas/replySchema", + "dataFormats": ["text/plain"] + }, + "image": { + "dataFormats": ["image/jpeg"] + }, + "caption": { + "schema": "https://social-media.xyz/schemas/captionSchema", + "dataFormats": ["text/plain"] + } + }, + "structure": { + "post": { + "$actions": [ + { + "who": "anyone", + "can": ["create", "read"] + } + ], + "reply": { + "$actions": [ + { + "who": "recipient", + "of": "post", + "can": ["create"] + }, + { + "who": "author", + "of": "post", + "can": ["create"] + } + ] + } + }, + "image": { + "$actions": [ + { + "who": "anyone", + "can": ["create", "read"] + } + ], + "caption": { + "$actions": [ + { + "who": "anyone", + "can": ["read"] + }, + { + "who": "author", + "of": "image", + "can": ["create"] + } + ] + }, + "reply": { + "$actions": [ + { + "who": "author", + "of": "image", + "can": ["read"] + }, + { + "who": "recipient", + "of": "image", + "can": ["create"] + } + ] + } + } + } +} +``` + +The value for `protocol` is a URI that serves as the key of the protocol being configured. If two or more applications use the same protocol, identified by the URI, users can enjoy common experiences across these apps in a more interoperable way. Ideally, the protocol URI should lead to a JSON Schema document that validates the configurations, however, this URI is not resolved nor are its contents validated by Web5. + +The `published` attribute indicates whether the protocol should be public. Published protocols are accessible by anyone who [queries](https://tbd54566975.github.io/web5-js/classes/_web5_api.DwnApi.html#protocols) for them. + +```json +{ + "protocol": "https://social-media.xyz", + "published": true, +} +``` + +In the `types` section, we can see how the data schema of each data type is defined. While you’d ideally use a resolvable schema in this property, that is not a requirement. + +```json +"types": { + "post": { + "schema": "https://social-media.xyz/schemas/postSchema", + "dataFormats": ["text/plain"] + }, + "reply": { + "schema": "https://social-media.xyz/schemas/replySchema", + "dataFormats": ["text/plain"] + }, + "image": { + "dataFormats": ["image/jpeg"] + }, + "caption": { + "schema": "https://social-media.xyz/schemas/captionSchema", + "dataFormats": ["text/plain"] + } +}, +``` + +You’ll then notice how each of those `types` is used in the large `structure` object, which at a top level houses the `post` and `image` data types which are critical to our social network. + +```json +"structure": { + //highlight-next-line + "post": { + "$actions": [ + { + "who": "anyone", + "can": ["create", "read"] + } + ], + "reply": { + "$actions": [ + { + "who": "recipient", + "of": "post", + "can": ["create"] + }, + { + "who": "author", + "of": "post", + "can": ["create"] + } + ] + } + }, + //highlight-next-line + "image": { + "$actions": [ + { + "who": "anyone", + "can": ["read"] + }, + { + "who": "anyone", + "can": ["create"] + } + ], + "caption": { + "$actions": [ + { + "who": "anyone", + "can": ["read"] + }, + { + "who": "author", + "of": "image", + "can": ["create"] + } + ] + }, + "reply": { + "$actions": [ + { + "who": "author", + "of": "image", + "can": ["read"] + }, + { + "who": "recipient", + "of": "image", + "can": ["create"] + } + ] + } + } +} +``` + +Within `post`, you’ll notice we define `actions` permissions to let anyone read or create one. + +```json +"post": { + "$actions": [ + { + "who": "anyone", + "can": ["create", "read"] + } + ], +}, +``` + +But then we nest another `structure` object to hold the child property of `reply` and define permissions on `reply`. + +```json +"post": { + "$actions": [ + { + "who": "anyone", + "can": ["read", "create"] + } + ], + //highlight-next-line + "reply": { + "$actions": [ + { + "who": "recipient", + "of": "post", + "can": ["create"] + }, + { + "who": "author", + "of": "post", + "can": ["create"] + } + ] + } +}, +``` + +Additionally, you’ll notice the `image` object below it also defines `actions` permissions, along with more child properties and their permissions. + +```json +//highlight-next-line +"image": { + "$actions": [ + { + "who": "anyone", + "can": ["create","read"] + } + ], + //highlight-next-line + "caption": { + "$actions": [ + { + "who": "anyone", + "can": ["read"] + }, + { + "who": "author", + "of": "image", + "can": ["create"] + } + ] + }, + //highlight-next-line + "reply": { + "$actions": [ + { + "who": "author", + "of": "image", + "can": ["read"] + }, + { + "who": "recipient", + "of": "image", + "can": ["create"] + } + ] + } +} +``` + +## Installing a Protocol + +To use a protocol in your app, you’ll need to install that protocol to your DWN. You can do so using the following snippet where `protocolDefinition` is the messaging protocol object from above: + +```js +const { protocol, status } = await web5.dwn.protocols.configure({ + message: { + definition: protocolDefinition + } +}); + +//sends protocol to remote DWNs immediately (vs waiting for sync) +await protocol.send(myDid); +``` + +Once you’ve installed that protocol to your DWN, you’re ready to communicate using the schema and permissions it defines. + +## Protocols in Practice + +Building on our social media example, let’s say that you wanted to post a message to your friend Alice. First, ensure that she has also installed the `https://social-media.xyz` protocol on her DWN. Then, you can write to her DWN via the `https://social-media.xyz` protocol using: + +```js +const { record: postRecord, status: createStatus } = await web5.dwn.records.create({ + data: 'Hey this is my first post!', + message: { + recipient: aliceDid, + schema: 'https://social-media.xyz/schemas/postSchema', + dataFormat: 'text/plain', + protocol: protocolDefinition.protocol, + protocolPath: 'post' + } +}); +``` + +Now, let's say Alice wants to reply to the post. Remember, `reply` is a child of `post`, therefore this record should reference the post record's id as its parent. + +```js +const replyResponse = await web5.dwn.records.create({ + data: "replying to post", + message: { + recipient: senderDid, + protocol: protocolDefinition.protocol, + protocolPath: 'post/reply', + //highlight-next-line + parentContextId: postRecord.id, + schema: "https://social-media.xyz/schemas/replySchema", + dataFormat: 'text/plain', + }, +}) +``` + +If an app wants to display all of a post's replies, it can obtain the post's record ID and then query for records that have that id as a `parentId`. + +```js +const { records: replies } = await web5.dwn.records.query({ + message: { + filter: { + parentId: postRecord.id + } + } +}) +``` + +And that’s it! Via the `social-media` protocol, you’ve now written a message to Alice’s DWN and she has replied. + +This protocol enables a basic social network using Web5, which means we’ve created a basic trustless, decentralized social network where your users host all of their own data. + +## Validating Protocols +As you write your protocol documents, you can use this [simple protocol validator UI](https://radiant-semifreddo-af73bb.netlify.app/) app to explore and validate the types and structures in your protocol. This should help with testing and improving your protocol. Find out more about how to use this project on [GitHub](https://github.com/kirahsapong/protocol-validator#protocol-validator). + +## Example Protocols +Here are a bunch of [example protocols](https://github.com/TBD54566975/dwn-sdk-js/tree/main/tests/vectors/protocol-definitions) for more inspiration! + +## Video Tutorials + +
+ + + + + + + +
diff --git a/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/write-to-dwn.mdx b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/write-to-dwn.mdx new file mode 100644 index 000000000..580bd9124 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/decentralized-web-nodes/write-to-dwn.mdx @@ -0,0 +1,40 @@ +--- +sidebar_position: 3 +--- +import CodeSnippet from '@site/src/components/CodeSnippet'; + +# Write DWN Records + +Various data types, such as text, JSON, images, streams or files, can be stored as records in a DWN. Below are examples of each. + +### Create a text record + + + +### Create a JSON record + + + +### Create a blob or image record + + + +### Create a file record + + + +### Create records with mixed data +You can also store mixed data via JSON - including text, images, or files. + + + +### Create records from an existing record +You can create a new version of a record while maintaining a link to the version of the original record. + + + +### Create records with tags +You can assign tags to records to enable filtering by specific keywords. + + +For more information on writing records to DWNs, see our [API Guide](https://tbd54566975.github.io/web5-js/classes/_web5_api.DwnApi.html#records). \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/index.mdx b/site/web5_versioned_docs/version-1.0.1/index.mdx new file mode 100644 index 000000000..54d6abc01 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/index.mdx @@ -0,0 +1,66 @@ +--- +title: WEB5 SDK +hide_table_of_contents: true +--- + +import metacontent from '@site/src/content/global-meta'; +import { content } from '@site/src/content/projects/web5/web5.js'; + + + {content.meta.title} + + + + + + + +
+ +## A decentralized web platform that puts you in control of your data and identity. + +The web democratized the exchange of information, but it's missing a key layer: identity. We struggle to secure personal data with hundreds of accounts and passwords we can’t remember. On the web today, identity and personal data have become the property of third parties. + +Web5 brings [decentralized identity](decentralized-identifiers/what-are-dids), [verifiable credentials](verifiable-credentials/what-are-vcs) and [data storage](decentralized-web-nodes/what-are-dwns) to your applications. It lets devs focus on creating delightful user experiences, while returning ownership of data and identity to individuals. + +The Web5 SDK helps you create a decentralized app in minutes. + + + + + + + + +
\ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/quickstart.mdx b/site/web5_versioned_docs/version-1.0.1/quickstart.mdx new file mode 100644 index 000000000..a3d409bea --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/quickstart.mdx @@ -0,0 +1,431 @@ +--- +sidebar_position: 1 +title: Quickstart +hide_title: true +hide_table_of_contents: false +--- + +import { QuickstartExecutionProvider } from '@site/src/components/QuickstartExecutionContext'; +import Version from '@site/src/components/Version'; +import PackageJson from '@site/src/components/PackageJson'; +import CodeSnippet from '@site/src/components/CodeSnippet'; +import QuickstartCodeRunner from '@site/src/components/QuickstartCodeRunner'; + + + + +# Web5 in 5️⃣ minutes + +Let's build a decentralized application on the Web5 platform - in under 5 minutes. You will learn how to: + +✅ Create unique digital IDs for users known as Decentralized Identifiers (DIDs) + +✅ Issue and manage digital proofs using Verifiable Credentials(VCs) + +✅ Store and read data from your users' personal data store called a Decentralized Web Node (DWN) + +Let’s go! 🚀 + +## Installation + +:::info + +

Prerequisites

+
    +
  • +

    Node Package Manager, npm, installed and on your system's $PATH.

    +
  • +
  • +

    Node version 18 and above

    +
  • +
+::: + +### 1. Create a directory + +

This will be the home of your new Web5 app. Run the following commands in your CLI:

{' '} + +```bash +mkdir web5-app +cd web5-app +``` + +### 2. Install Web5 + +Use NPM to initialize a `package.json` file: + +```bash +npm init -y +``` + +

Use NPM to install Web5:

+ + + +These steps will create a `package.json` in the root of your project. Open the newly created `package.json` and add `module` as a type: + + + +### 3. Create App File + +Create an `index.js` file where you will write all of your application logic: + +```bash +touch index.js +``` + +For Windows using PowerShell: + +```powershell +New-Item index.js -ItemType File +``` + +:::note +After `npm` resolves the dependency, you may see a few warnings. You can ignore these for now. +::: + +### 4. Import Web5 + +At the top of your `index.js` file, add these lines to import the Web5 package dependencies that you will use to build your application:: + +```js +import { Web5 } from '@web5/api'; +import { VerifiableCredential } from '@web5/credentials'; +``` + +
+Additional import for Node 18 + +```js +/* +Needs globalThis.crypto polyfill. +This is *not* the crypto you're thinking of. +It's the original crypto...CRYPTOGRAPHY. +*/ +import { webcrypto } from 'node:crypto'; + +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; +``` + +
+ +Now that you've installed the Web5 SDKs, you are ready to start building! + +## Create Web5 App + +### 1. Instantiate Web5 and Create DID + +In Web5 apps, a user’s unique identifier - like an email address - is called a [Decentralized Identifier (DID)](/docs/web5/decentralized-identifiers/what-are-dids). +We are building a decentralized app, so your users are using identifiers that aren't tied to a centralized authority. + +The `Web5` class is an isolated API object for doing all things Web5. The `connect()` function creates an instance of Web5 and also creates a decentralized identifier or obtains connection to an existing one. + +In `index.js` below the import statement, create a new instance of `Web5`: + + +This Web5 instance is what you'll use to access the other objects of Web5 such as `dwn`. Within the connect function we’re using `dht` as the DID method. Learn more [DID methods](/docs/web5/decentralized-identifiers/what-are-dids#methods). + + +
+ Test your code + + Wanna see the DID you just created? + + In index.js, add the following line and save your file: + ```js + console.log(aliceDid); + ``` + + Then from the terminal, run: + ```bash + node index.js + ``` + + You'll see the newly created DID. + +
+ +#### Try it! + + + + ### 2. Access Bearer DID + +In the previous step, you generated what is known as a (DID), which appears as an alphanumeric string. It's more specifically referred to as a DID URI. + +This DID URI is part of a broader JSON object, known as a [Bearer DID](/docs/web5/decentralized-identifiers/how-to-create-did#create-did), which contains metadata such as cryptographic keys that prove ownership of the DID and rules governing its usage, management, or modification. With a Bearer DID, the owner can securely sign and verify data, ensuring authenticity and integrity. + + +To access your Bearer DID, add the following lines of code to your `index.js`: + + + +
+ Test your code + +Wanna see the Bearer DID? + +In index.js, add the following line and save your file: + +```js +console.log(aliceBearerDid); +``` + +Then from the terminal, run: +```bash +node index.js +``` + +You'll see the Bearer DID. + +
+ +#### Try it! + + +### 3. Create a VC + +[Verifiable Credentials (VCs)](https://developer.tbd.website/docs/web5/verifiable-credentials/what-are-vcs) are digital proofs used to confirm specific facts about individuals, organizations, or entities. +In this step, you'll enable your users to create a self-signed VC -- allowing them to issue a Verifiable Credential that makes claims about themselves. For this example, the credential will state that the user has completed the Web5 Quickstart Guide. + +In your `index.js`, create your Verifiable Credential: + + + + +
+ Test your code + +Wanna see the VC you just created? + +In index.js, add the following line and save your file: + +```js +console.log(vc); +``` + +Then from the terminal, run: +```bash +node index.js +``` + +You'll see the newly created Verifiable Credential. +
+ +Enter a name in the input field and click "Run" to create a Verifiable Credential. + +#### Try it! + + + +### 4. Sign VC + +Now, your users can sign the VC with their Bearer DID to ensure its authenticity and integrity. + +To allow users to sign a VC, add this line to your `index.js`: + + + + +When an issuer signs a VC, it attests that they are truly the one who issued it. +This cryptographic process also converts the VC into a [JSON Web Token (JWT)](https://jwt.io/), making the credential tamper-evident and verifiable by others. + +
+ Test your code + +Wanna see the JWT version of your signed VC? + +In index.js, add the following line and save your file: + +```js +console.log(signedVc); +``` + +Then from the terminal, run: +```bash +node index.js +``` + +You'll see the JWT-encoded version of your Verifiable Credential similar to the alphanumeric string below: +```bash +eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6cDNpeHo1MXp4azR1MW5uN29xcnhpaGJzc3o3amNnNXd4ZTVpeThwcGo4ZXVwOHF4OXB1eSMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiV2ViNVF1aWNrc3RhcnRDb21wbGV0aW9uQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOmVkZDdmN2FhLTA2MDgtNDlmYi1iYzYwLWUyYWVkNTFhM2IxNSIsImlzc3VlciI6ImRpZDpkaHQ6cDNpeHo1MXp4azR1MW5uN29xcnhpaGJzc3o3amNnNXd4ZTVpeThwcGo4ZXVwOHF4OXB1eSIsImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDUtMjJUMTk6NDQ6MjlaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZGh0OnAzaXh6NTF6eGs0dTFubjdvcXJ4aWhic3N6N2pjZzV3eGU1aXk4cHBqOGV1cDhxeDlwdXkiLCJ1c2VybmFtZSI6IkBibGFja2dpcmxieXRlcyIsImNvbXBsZXRpb25EYXRlIjoiMjAyNC0wNS0yMiIsImV4cGVydGlzZUxldmVsIjoiQmVnaW5uZXIifSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI2LTA5LTMwVDEyOjM0OjU2WiJ9LCJuYmYiOjE3MTY0MDcwNjksImp0aSI6InVybjp1dWlkOmVkZDdmN2FhLTA2MDgtNDlmYi1iYzYwLWUyYWVkNTFhM2IxNSIsImlzcyI6ImRpZDpkaHQ6cDNpeHo1MXp4azR1MW5uN29xcnhpaGJzc3o3amNnNXd4ZTVpeThwcGo4ZXVwOHF4OXB1eSIsInN1YiI6ImRpZDpkaHQ6cDNpeHo1MXp4azR1MW5uN29xcnhpaGJzc3o3amNnNXd4ZTVpeThwcGo4ZXVwOHF4OXB1eSIsImlhdCI6MTcxNjQwNzA4MCwiZXhwIjoxNzkwNzcxNjk2fQ.6R8jx0Z3xhab7X-Y-sGPVnZ2NdFL746ILcjJbGvSfCixUY1DBsd86CVJfnXXAwbfRm5KQv0xMS5XH4h7gKkZDA +``` + +
+ +#### Try it! + + + + +### 5. Store VC in DWN + +Your users can now store the JWT form of their VC in a [DWN (Decentralized Web Node)](https://developer.tbd.website/docs/web5/decentralized-web-nodes/what-are-dwns). + +A DWN is a personal data store - a platform for messages, pictures, videos, medical records, and just about any content a user may want to store. + +
+ Read more about DWNs + +Your app should not store users' data in your centralized database. Instead, their data should be stored in their DWN. This is how the user retains ownership over their content. Through permissions, users can decide which apps can read, write, and delete content from their DWN. + +The DWN exists in local storage. The DWN persists across browser sessions and can be synched across a user's devices. + +A user can host their DWN in multiple locations. The Web5 SDK is both browser and Node.js compliant, meaning you can use the same APIs on both client side and serverside. + +
+ +To store a VC in a DWN, add the following lines of code to your `index.js`: + + + +
+ Test your code + +Let's see what was just written to the DWN. + +In index.js, add the following line and save your file: + +```js +console.log('writeResult', record); +``` + +Then from the terminal, run: +```bash +node index.js +``` + +You'll see the record that was written to the user's DWN. It will resemble: +```bash +writeResult _Record {} +``` + +
+ +#### Try it! + + +### 6. Read VC from DWN + +If the user has given your app read permissions to their DWN, you can read their data by accessing it through the record property. + +To read the JWT-encoded VC stored in your DWN, add the following to your `index.js`: + + + + + +:::note +Since the record is [public](/docs/web5/decentralized-web-nodes/publishing-records) +(set by the `published` property), anyone can access it without permissions. +If you need to access a private record from a DWN that doesn't belong to you, you would need permissions from the DWN's owner and to use +the [query](/docs/web5/decentralized-web-nodes/query-from-dwn) method. +::: + +
+ Test your code + +To see the record that was read from the DWN, add the following to index.js: + +```js +console.log('readResult', readSignedVc); +``` + +Then from the terminal, run: +```bash +node index.js +``` + + +You'll see the JWT-encoded version of your Verifiable Credential similar to the alphanumeric string below: +```bash +eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6cDNpeHo1MXp4azR1MW5uN29xcnhpaGJzc3o3amNnNXd4ZTVpeThwcGo4ZXVwOHF4OXB1eSMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiV2ViNVF1aWNrc3RhcnRDb21wbGV0aW9uQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOmVkZDdmN2FhLTA2MDgtNDlmYi1iYzYwLWUyYWVkNTFhM2IxNSIsImlzc3VlciI6ImRpZDpkaHQ6cDNpeHo1MXp4azR1MW5uN29xcnhpaGJzc3o3amNnNXd4ZTVpeThwcGo4ZXVwOHF4OXB1eSIsImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDUtMjJUMTk6NDQ6MjlaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZGh0OnAzaXh6NTF6eGs0dTFubjdvcXJ4aWhic3N6N2pjZzV3eGU1aXk4cHBqOGV1cDhxeDlwdXkiLCJ1c2VybmFtZSI6IkBibGFja2dpcmxieXRlcyIsImNvbXBsZXRpb25EYXRlIjoiMjAyNC0wNS0yMiIsImV4cGVydGlzZUxldmVsIjoiQmVnaW5uZXIifSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI2LTA5LTMwVDEyOjM0OjU2WiJ9LCJuYmYiOjE3MTY0MDcwNjksImp0aSI6InVybjp1dWlkOmVkZDdmN2FhLTA2MDgtNDlmYi1iYzYwLWUyYWVkNTFhM2IxNSIsImlzcyI6ImRpZDpkaHQ6cDNpeHo1MXp4azR1MW5uN29xcnhpaGJzc3o3amNnNXd4ZTVpeThwcGo4ZXVwOHF4OXB1eSIsInN1YiI6ImRpZDpkaHQ6cDNpeHo1MXp4azR1MW5uN29xcnhpaGJzc3o3amNnNXd4ZTVpeThwcGo4ZXVwOHF4OXB1eSIsImlhdCI6MTcxNjQwNzA4MCwiZXhwIjoxNzkwNzcxNjk2fQ.6R8jx0Z3xhab7X-Y-sGPVnZ2NdFL746ILcjJbGvSfCixUY1DBsd86CVJfnXXAwbfRm5KQv0xMS5XH4h7gKkZDA +``` + +
+ +#### Try it! + + + +### 7. Parse VC + +Upon receiving a Verifiable Credential (VC) as a JSON Web Token (JWT), applications can convert it back to JSON format using the `VerifiableCredential.parseJwt()` method. +This allows you to inspect the contents of the VC in a readable format. + +Add the following line to your index.js to parse the JWT-encoded VC: + + + + +
+ Test your code + +Wanna see the parsed JWT? + +In index.js, add the following line and save your file: + +```js +console.log(parsedVc); +``` + +Then from the terminal, run: +```bash +node index.js +``` + + +You will see a decoded version of the JWT! + +
+ +#### Try it! + + + +## Summary + +Congrats! You've successfully created an application with two key functionalities: + +1️⃣ Issuing and cryptographically signing Verifiable Credentials, providing users with a reliable way to verify facts about individuals, organizations, or entities. + +2️⃣ Creating a local Decentralized Web Node (DWN) as a personal data store for users. With their DID and appropriate permissions, your app can read, write, or delete data from the user's DWN, ensuring they have full control over their content. + +:::info +[Download the completed index.js file](/files/index.txt) +::: + + +## Next Steps + +* [Learn more about DIDs](/docs/web5/decentralized-identifiers/what-are-dids) +* [Learn more about VCs](/docs/web5/verifiable-credentials/what-are-vcs) +* [Learn more about DWNs](/docs/web5/decentralized-web-nodes/what-are-dwns) +* [Build a shared ToDo app](/docs/web5/apps/shared-todo-app) + +
diff --git a/site/web5_versioned_docs/version-1.0.1/test/apps/upgrade-to-web5.test.js b/site/web5_versioned_docs/version-1.0.1/test/apps/upgrade-to-web5.test.js new file mode 100644 index 000000000..102730559 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/apps/upgrade-to-web5.test.js @@ -0,0 +1,31 @@ +import { test, expect, beforeAll, describe, vi } from 'vitest'; +import { createAliceDid } from '../../../../code-snippets/web5/build/apps/upgrade-to-web5'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; + +describe('ugprade-to-web5', () => { + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + + vi.mock('@web5/api', () => { + return { + Web5: { + connect: vi.fn(() => { + return { + web5, + did, + }; + }), + }, + }; + }); + }); + + test('read result comes back from creating alice did', async () => { + const readResult = await createAliceDid(); + expect(readResult).toBe('Hello Web5'); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-dependency.gradle b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-dependency.gradle new file mode 100644 index 000000000..eea34f5ea --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-dependency.gradle @@ -0,0 +1,9 @@ +// kotlin Gradle dependencies +// :snippet-start: createADidDependencyGradle +repositories { + mavenCentral() +} +dependencies { + implementation("xyz.block:web5-dids:0.10.0") +} + // :snippet-end: \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-dependency.xml b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-dependency.xml new file mode 100644 index 000000000..7aca00e17 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-dependency.xml @@ -0,0 +1,22 @@ +// Kotlin Maven dependencies +// :snippet-start: createADidDependencyMaven + + + mavenCentral + https://repo1.maven.org/maven2/ + + + + + + xyz.block + web5-dids + 0.10.0 + + + decentralized-identity + did-common-java + 1.11.0 + + +// :snippet-end: \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-import.bash b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-import.bash new file mode 100644 index 000000000..245a51c8e --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did-import.bash @@ -0,0 +1,3 @@ +# :snippet-start: createADidDependency +npm install @web5/dids +# :snippet-end: \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did.test.js new file mode 100644 index 000000000..2b84c3fd7 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-create-did.test.js @@ -0,0 +1,84 @@ +import { test, expect, vi, describe, beforeAll } from 'vitest'; +import { DidDht, DidJwk } from '@web5/dids'; +import { createDidAutomatically } from '../../../../code-snippets/web5/build/decentralized-identifiers/how-to-create-did'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; + +describe('how-to-create-did', () => { + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + + vi.mock('@web5/api', () => { + return { + Web5: { + connect: vi.fn(() => { + return { + web5, + did, + }; + }), + }, + }; + }); + }); + + test('show required imports to create did', async () => { + const requiredImports = ` + // :snippet-start: requiredDidImports +// did:dht +import { DidDht } from '@web5/dids' + +//did:jwk +import { DidJwk } from '@web5/dids' + + // :snippet-end: + `; + }); + + test('createDidAutomatically returns a DID', async () => { + const did = await createDidAutomatically(); + expect(did).toBeDefined(); + expect(did).toMatch(/^did:/); + }); + + test('createDidDht creates a DID with did:dht method', async () => { + // :snippet-start: createDidDht + // Creates a DID using the DHT method and publishes the DID Document to the DHT + const didDht = await DidDht.create({ publish: true }); + + // DID and its associated data which can be exported and used in different contexts/apps + const portableDid = didDht.export(); + + // DID string + const did = didDht.uri; + + // DID Document + const didDocument = JSON.stringify(didDht.document); + + // :snippet-end: + + expect(did).toMatch(/^did:dht:/); + }); + + test('createDidJwk creates a DID with did:jwk method', async () => { + // :snippet-start: createDidJwk + //Creates a DID using the did:jwk method + const didJwk = await DidJwk.create(); + + //DID and its associated data which can be exported and used in different contexts/apps + const portableDid = didJwk.export(); + + //DID string + const did = didJwk.uri; + + //DID Document + const didDocument = JSON.stringify(didJwk.document); + + // :snippet-end: + + expect(did).toMatch(/^did:jwk:/); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-resolve-did.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-resolve-did.test.js new file mode 100644 index 000000000..accfef45d --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-resolve-did.test.js @@ -0,0 +1,27 @@ +// :prepend-start: resolveDidJS +import {DidDht, DidJwk} from '@web5/dids'; +// :prepend-end: +import { test, expect, describe } from 'vitest'; + + +describe('DID Document Resolver Tests', () => { + test('didDocument retrieves a valid DID document', async () => { + const didDht = await DidDht.create({ options: { publish: true } }); + const didJwk = await DidJwk.create({ options: { publish: true } }); + const didDhtUri = didDht.uri; + const didJwkUri = didJwk.uri; + + // :snippet-start: resolveDidJS + // resolve did:jwk DID + const resolvedJwkDid = await DidJwk.resolve(didJwkUri); + const jwkDidDocument = resolvedJwkDid.didDocument; + + // resolve did:dht DID + const resolvedDhtDid = await DidDht.resolve(didDhtUri); + const dhtDidDocument = resolvedDhtDid.didDocument; + // :snippet-end: + + expect(jwkDidDocument.id).equals(didJwkUri); + expect(dhtDidDocument.id).equals(didDhtUri); + }); + }); \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-update-did.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-update-did.test.js new file mode 100644 index 000000000..296c23844 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/how-to-update-did.test.js @@ -0,0 +1,77 @@ +// :prepend-start: addServiceToDidDocJS updateServiceEndpointJS removeServiceEndpointJS +import {DidDht} from '@web5/dids'; +// :prepend-end: +import { test, expect, describe } from 'vitest'; + + +describe('Update DID Document', () => { + + test('add service to DID Document', async () => { + const myBearerDid = await DidDht.create({ options: { publish: true } }); + + // :snippet-start: addServiceToDidDocJS + // if service does not exist, create an empty array + myBearerDid.document.service = myBearerDid.document.service || []; + + // add a service to the DID document + myBearerDid.document.service.push({ + id: 'pfi', + type: 'PFI', + serviceEndpoint: 'https://example.com/' + }); + + // republish the updated DID document + DidDht.publish({did: myBearerDid}); + // :snippet-end: + + expect(myBearerDid.document.service[0].id).equals('pfi'); + }); + + test('modify service in DID Document', async () => { + const myBearerDid = await DidDht.create({ + options:{ + publish: true, + services: [{ + id: 'pfi', + type: 'PFI', + serviceEndpoint: 'https://example.com/' + }] + } + }); + const newEndpoint = 'https://newexample.com/'; + + // :snippet-start: updateServiceEndpointJS + // update the service endpoint + myBearerDid.document.service.find(s => s.type === 'PFI').serviceEndpoint = newEndpoint; + + // republish the updated DID document + DidDht.publish({did: myBearerDid}); + // :snippet-end: + + expect(myBearerDid.document.service[0].serviceEndpoint).equals(newEndpoint); + }); + + test('remove service from DID Document', async () => { + const myBearerDid = await DidDht.create({ + options:{ + publish: true, + services: [{ + id: 'pfi', + type: 'PFI', + serviceEndpoint: 'https://example.com/' + }] + } + }); + + // :snippet-start: removeServiceEndpointJS + // keep all services except the one with type 'PFI' + const updatedServices = myBearerDid.document.service.filter(s => s.type != 'PFI') + myBearerDid.document.service = updatedServices; + + // republish the updated DID document + DidDht.publish({did: myBearerDid}); + // :snippet-end: + + expect(myBearerDid.document.service.length).equals(0); + }); + }); \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/key-management.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/key-management.test.js new file mode 100644 index 000000000..439feb478 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-identifiers/key-management.test.js @@ -0,0 +1,37 @@ +import { test, expect, describe } from 'vitest'; +// :snippet-start: importKeyManagementJs +import { DidDht } from '@web5/dids'; +import { LocalKeyManager } from "@web5/crypto"; +import { AwsKeyManager } from "@web5/crypto-aws-kms"; +// :snippet-end: + +describe('key-management', () => { + // :snippet-start: initializeKeyManagementJs + async function initKeyManagement(env, portableDid) { + // Determine which key manager to use based on the environment + let keyManager; + if (env === "production") { + keyManager = new AwsKeyManager(); + } else { + keyManager = new LocalKeyManager(); + } + + // Initialize or load a DID + if (portableDid == null) { + // Create a new DID + return await DidDht.create(keyManager); + } else { + // Load existing DID + return await DidDht.import({portableDid, keyManager}); + } + } + // :snippet-end: + + + test('initialize key management', async () => { + const returnedDid = await initKeyManagement("dev"); + expect(returnedDid.keyManager).toHaveProperty('_algorithmInstances'); + expect(returnedDid.keyManager).toHaveProperty('_keyStore'); + }); + +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/access-data-with-drls.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/access-data-with-drls.test.js new file mode 100644 index 000000000..795e5b39f --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/access-data-with-drls.test.js @@ -0,0 +1,89 @@ +import { + beforeAll, + beforeEach, + afterEach, + describe, + test, + expect, + vi, +} from 'vitest'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; +import { uploadImage } from '../../../../code-snippets/web5/build/decentralized-web-nodes/write-to-dwn'; + +describe('Testing upgrade to PWA', () => { + let web5, did; + let originalFetch; + let recordId; + + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + }); + + beforeEach(() => { + originalFetch = global.fetch; + + vi.mock('@web5/api', () => ({ + Web5: { + connect: vi.fn(() => Promise.resolve({ web5, did })), + }, + })); + + global.fetch = vi.fn((url) => { + // Step 3: Use the variable in your mocked fetch function + if (url === `https://dweb/${did}/read/records/${recordId}`) { + return Promise.resolve({ + ok: true, + status: 200, + blob: () => + Promise.resolve( + new Blob(['fake image data'], { type: 'image/png' }), + ), + }); + } + return Promise.reject(new Error('URL not found')); + }); + }); + + afterEach(() => { + vi.resetAllMocks(); + global.fetch = originalFetch; + }); + + test('drl fetches a read record', async () => { + const mockEvent = { + currentTarget: { + files: [new Blob(['fake image data'], { type: 'image/png' })], + }, + }; + const record = await uploadImage(mockEvent); + recordId = record.id; + // :snippet-start: drlFetchReadRecord + const drl = `https://dweb/${did}/read/records/${recordId}`; + const response = await fetch(drl); + const imageUrl = URL.createObjectURL(await response.blob()); + // :snippet-end: + expect(response.ok).toBeTruthy(); + expect(response.status).toBe(200); + expect(imageUrl.startsWith('blob:')).toBeTruthy(); + }); + + test('set image src to url', async () => { + const mockEvent = { + currentTarget: { + files: [new Blob(['fake image data'], { type: 'image/png' })], + }, + }; + const record = await uploadImage(mockEvent); + recordId = record.id; + const drl = `https://dweb/${did}/read/records/${recordId}`; + const response = await fetch(drl); + const imageUrl = URL.createObjectURL(await response.blob()); + return ` + // :snippet-start: renderImageUrlTag + uploaded image + // :snippet-end: + `; + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/connect.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/connect.test.js new file mode 100644 index 000000000..005be6673 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/connect.test.js @@ -0,0 +1,51 @@ +import { test, expect, describe, beforeAll, vi } from 'vitest'; +import { + connectWithDWNEndpoint, + connectWithAgentAndConnectedDid, + connectWithSyncConfig, + connectToCommunityNode, +} from '../../../../code-snippets/api/web5-js'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +// Mock needed to not conflict with globalThis.web5 +vi.mock('@web5/api', () => { + return { + Web5: { + connect: vi.fn(({ connectedDid }) => { + return { + web5: {}, + did: connectedDid || 'did:ion:EiBq1ELpOTStuDt...', + }; + }), + }, + }; +}); + +describe('web5-js-api-index', () => { + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + }); + + test('connect with DWN endpoint', async () => { + const did = await connectWithDWNEndpoint(); + expect(did).toBeTypeOf('string'); + }); + + test('connect with agent and connectedDid', async () => { + const existingDid = + 'did:ion:EiBq1ELpOTStuDtHW1C66IXbcQhBN-mABVzfj5UhchWG_w:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoiSmI5VTZyVlNXeUJ5UGpVaWU5YWJCWDMwbTM0VUlHcUppc25INmpCYzdNcyJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifSx7ImlkIjoiZHduLWVuYyIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJ0cl9QQUktaWdxNFc1N04xNFpmemthaUtCaHdrSjBGQ2s5ckw4NTU2OHJFIiwieSI6Ik9FZWkwUDAtcl95R0NzZE45aGN3MUdtdllmWnBLSElBOG9zNHBoSlJYUTQifSwicHVycG9zZXMiOlsia2V5QWdyZWVtZW50Il0sInR5cGUiOiJKc29uV2ViS2V5MjAyMCJ9XSwic2VydmljZXMiOlt7ImlkIjoiZHduIiwic2VydmljZUVuZHBvaW50Ijp7ImVuY3J5cHRpb25LZXlzIjpbIiNkd24tZW5jIl0sIm5vZGVzIjpbImh0dHBzOi8vZHduLnRiZGRldi5vcmcvZHduNiIsImh0dHBzOi8vZHduLnRiZGRldi5vcmcvZHduMyJdLCJzaWduaW5nS2V5cyI6WyIjZHduLXNpZyJdfSwidHlwZSI6IkRlY2VudHJhbGl6ZWRXZWJOb2RlIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlEQUFsemJCYXhDR2JjYVJva3NHR3lyMjUtbWVjVUZGa2NfTl93VFRWcEJyUSJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpRFhuak12bGpWUjNvSzF0WE1hQ2N2Ukc5TkQ2Zlk3b2JzaFJtdkU4Uzh3RVEiLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUJWZjFoOHV5Ulh4QS1fUl9xRlBkRHZVVWhWQzZ2OU5SV1BKSmwzVkZjMndRIn19'; + const returnedDid = await connectWithAgentAndConnectedDid(existingDid); + expect(returnedDid).toBe(existingDid); + }); + + test('connect to community node', async () => { + const testDid = await connectToCommunityNode(); + expect(testDid).toBeTypeOf('string'); + }); + + test('connect with sync configuration', async () => { + const did = await connectWithSyncConfig(); + expect(did).toBeTypeOf('string'); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/delete-from-dwn.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/delete-from-dwn.test.js new file mode 100644 index 000000000..2c89f9c1b --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/delete-from-dwn.test.js @@ -0,0 +1,118 @@ +import { test, beforeAll, expect, describe } from 'vitest'; +import { + createLocalRecord, + sendLocalRecordToTarget, +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/send'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; +let did; + +describe('delete-from-dwn', () => { + // connect to web5 beforeAll tests and assign it to web5 variable + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + }); + + test('deleteRecordFromLocalDwn deletes a record', async () => { + const record = await createLocalRecord(web5); + // :snippet-start: deleteRecordFromLocalDwn + const { status: deleteStatus } = await record.delete(); + // :snippet-end: + const readResult = await web5.dwn.records.read({ + message: { + filter: { + recordId: record.id, + }, + }, + }); + expect(deleteStatus.code).toBe(202); + expect(readResult.status.code).toBe(404); + }); + + test('deleteRecordFromRemoteDwn deletes a remote record', async () => { + const record = await sendLocalRecordToTarget(web5, did); + // :snippet-start: deleteRecordFromRemoteDwn + const { status: deleteStatus } = await record.delete(); + // send the delete request to the remote DWN + const { status: deleteSendStatus } = await record.send(did); + // :snippet-end: + expect(record.deleted).toBe(true); + expect(deleteStatus.code).toBe(202); + expect(deleteSendStatus.code).toEqual(202); + }); + + test('pruneRecords deletes parents record and its children', async () => { + const { status: protocolStatus, protocol } = + await web5.dwn.protocols.configure({ + message: { + definition: { + protocol: 'http://example.com/parent-child', + published: true, + types: { + post: { + schema: 'http://example.com/post', + }, + comment: { + schema: 'http://example.com/comment', + }, + }, + structure: { + post: { + comment: {}, + }, + }, + }, + }, + }); + + const { record: parentRecord } = await web5.dwn.records.create({ + data: 'Hello, world!', + message: { + protocol: protocol.definition.protocol, + protocolPath: 'post', + schema: 'http://example.com/post', + dataFormat: 'text/plain', + }, + }); + + const { record: childRecord } = await web5.dwn.records.create({ + data: 'Hello, world!', + message: { + protocol: protocol.definition.protocol, + protocolPath: 'post/comment', + schema: 'http://example.com/comment', + dataFormat: 'text/plain', + parentContextId: parentRecord.contextId, + }, + }); + + // :snippet-start: pruneRecords + const { status: deleteStatus } = await parentRecord.delete({ prune: true }); + // :snippet-end: + + const { records: childrenRecordsAfterDelete } = + await web5.dwn.records.query({ + message: { + filter: { + protocol: protocol.definition.protocol, + protocolPath: 'post/comment', + }, + }, + }); + + const { records: parentRecordsAfterDelete } = await web5.dwn.records.query({ + message: { + filter: { + protocol: protocol.definition.protocol, + protocolPath: 'post', + }, + }, + }); + expect(deleteStatus.code).toBe(202); + expect(parentRecordsAfterDelete).to.have.lengthOf(0); + expect(childrenRecordsAfterDelete).to.have.lengthOf(0); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/protocol-roles.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/protocol-roles.test.js new file mode 100644 index 000000000..9e5b5934c --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/protocol-roles.test.js @@ -0,0 +1,172 @@ +import { test, beforeAll, expect, describe } from 'vitest'; +import { DidDht } from '@web5/dids'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; +let did; +let aliceDid; + +// :snippet-start: curatorPlaylistProtocolDefinitionJs +const curatorPlaylistProtocolDefinition = { + protocol: 'https://example.com/playlist-protocol', + published: true, + types: { + playlist: { + schema: 'https://example.com/playlist-protocol/schema/playlist', + dataFormats: ['application/json'], + }, + // highlight-start + curator: { + schema: 'https://example.com/playlist-protocol/schema/curator', + dataFormats: ['text/plain'], + }, + // highlight-end + admin: { + schema: 'https://example.com/playlist-protocol/schema/admin', + dataFormats: ['text/plain'], + }, + }, + structure: { + curator: { + // highlight-next-line + $role: true, + }, + admin: { + $role: true, + }, + playlist: { + $actions: [ + { + who: 'anyone', + can: ['read'], + }, + // highlight-start + { + role: 'curator', + can: ['create', 'update'], + }, + // highlight-end + { + role: 'admin', + can: ['create', 'update', 'delete'], + }, + ], + }, + }, +}; +// :snippet-end: + +// :snippet-start: chatProtocolDefinitionJs +const chatProtocolDefinition = { + protocol: 'https://example.com/chat-protocol', + published: true, + types: { + chat: { + schema: 'https://example.com/chat-protocol/schema/chat', + dataFormats: ['application/json'], + }, + }, + structure: { + chat: { + $actions: [ + { who: 'anyone', can: ['create'] }, + { who: 'author', of: 'chat', can: ['read'] }, + { who: 'recipient', of: 'chat', can: ['read'] }, + ], + }, + }, +}; +// :snippet-end: + +describe('Playlist protocol roles', () => { + // connect to web5 beforeAll tests and assign it to web5 variable + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + aliceDid = await DidDht.create(); + }); + + test('install chat protocol', async () => { + // just to make sure chat protocol is working as expected + const { protocol, status } = await web5.dwn.protocols.configure({ + message: { + definition: chatProtocolDefinition, + }, + }); + + // send protocol to remote DWNs immediately + const { status: sendStatus } = await protocol.send(did); + expect(protocol).toBeDefined(); + expect(status.code).to.equal(202); + expect(sendStatus.code).to.equal(202); + }); + + test('install playlist protocol', async () => { + // :snippet-start: installPlaylistProtocolJs + const { protocol, status } = await web5.dwn.protocols.configure({ + message: { + definition: curatorPlaylistProtocolDefinition, + }, + }); + + // send protocol to remote DWNs immediately + const { status: sendStatus } = await protocol.send(did); + // :snippet-end: + expect(protocol).toBeDefined(); + expect(status.code).to.equal(202); + expect(sendStatus.code).to.equal(202); + }); + + test('assign protocol protocol role', async () => { + const aliceDidUri = aliceDid.uri; + // :snippet-start: assignPlaylistRoleJs + const { record, status } = await web5.dwn.records.create({ + message: { + dataFormat: 'text/plain', + protocol: curatorPlaylistProtocolDefinition.protocol, + protocolPath: 'curator', + schema: curatorPlaylistProtocolDefinition.types.curator.schema, + recipient: aliceDidUri, + }, + }); + + const { status: recordSendStatus } = await record.send(did); + // :snippet-end: + + expect(record).toBeDefined(); + expect(status.code).to.equal(202); + expect(recordSendStatus.code).to.equal(202); + }); + + test('Create a record within a role', async () => { + const bobDidUri = did; + // :snippet-start: createRecordInRoleJs + const { record, status } = await web5.dwn.records.create({ + data: JSON.stringify({ + name: 'My Favorite Songs', + description: 'A collection of my all-time favorites', + songs: [], + }), + message: { + dataFormat: 'application/json', + protocol: curatorPlaylistProtocolDefinition.protocol, + protocolPath: 'playlist', + protocolRole: 'curator', + schema: curatorPlaylistProtocolDefinition.types.playlist.schema, + recipient: bobDidUri, + }, + }); + // :snippet-end: + const expectedPlaylistData = { + name: 'My Favorite Songs', + description: 'A collection of my all-time favorites', + songs: [], + }; + expect(await record.data.text()).to.equal( + JSON.stringify(expectedPlaylistData), + ); + expect(record).toBeDefined(); + expect(status.code).to.equal(202); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/publish-records.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/publish-records.test.js new file mode 100644 index 000000000..f7c75a3b1 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/publish-records.test.js @@ -0,0 +1,29 @@ +import { test, beforeAll, expect, describe } from 'vitest'; + +import { + createPublishedRecord, + createRecordWithDatePublished, +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/publishing-records'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; +let did; + +describe('publish-records', () => { + // connect to web5 beforeAll tests and assign it to web5 variable + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + }); + + test('createPublishedRecord creates a public record', async () => { + const record = await createPublishedRecord(web5); + expect(record.published).toBe(true); + }); + + test('createRecordWithDatePublished creates a record that will be published later', async () => { + const isDateCorrect = await createRecordWithDatePublished(web5); + expect(isDateCorrect).toBe(true); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/query-from-dwn.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/query-from-dwn.test.js new file mode 100644 index 000000000..c5f3d33a2 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/query-from-dwn.test.js @@ -0,0 +1,110 @@ +import { test, beforeAll, expect, describe } from 'vitest'; + +import { + queryProtocolsWithFilterDescending, + queryRecordsWithFilterAscending, + queryProtocolsForMusic, + queryRecordsFromDid, + queryRecordWithParentId, + queryFromDwnByProtocolPath, +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/query-from-dwn'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; +let did; + +describe('query-from-dwn', () => { + // connect to web5 beforeAll tests and assign it to web5 variable + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + }); + + test('queryProtocolsForMusic returns an array of protocols', async () => { + const protocols = await queryProtocolsForMusic(web5); + expect(Array.isArray(protocols)).toBe(true); + }); + + test('queryProtocolsWithFilterDescending returns an array of protocols', async () => { + const protocols = await queryProtocolsWithFilterDescending(web5); + expect(Array.isArray(protocols)).toBe(true); + }); + + test('queryRecordsWithFilterAscending returns an array of protocols', async () => { + const response = await queryRecordsWithFilterAscending(web5); + + expect.soft(response.status.code).toBe(200); + expect(Array.isArray(response.records)).toBe(true); + }); + + test('queryRecordsFromDid returns an array of records', async () => { + const response = await queryRecordsFromDid(web5, did); + expect(Array.isArray(response)).toBe(true); + }); + + test('queryRecordWithParentId returns a record', async () => { + const response = await queryRecordWithParentId(web5); + expect.soft(response.status.code).toBe(200); + expect(Array.isArray(response.records)).toBe(true); + }); + + test('playlistProtocolDefinition can be configured', async () => { + // :snippet-start: playlistProtocolDefinition + const playlistProtocolDefinition = { + protocol: 'https://playlist.org/protocol', + published: true, + types: { + playlist: { + schema: 'https://schema.org/MusicPlaylist', + dataFormats: ['application/json'], + }, + audio: { + schema: 'https://schema.org/AudioObject', + dataFormats: ['audio/mp3'], + }, + video: { + schema: 'https://schema.org/VideoObject', + dataFormats: ['video/mp4'], + }, + }, + structure: { + playlist: { + $actions: [ + { who: 'anyone', can: ['create'] }, + { who: 'author', of: 'playlist', can: ['read'] }, + { who: 'recipient', of: 'playlist', can: ['read'] }, + ], + audio: { + $actions: [ + { who: 'anyone', can: ['create'] }, + { who: 'author', of: 'audio', can: ['read'] }, + { who: 'recipient', of: 'audio', can: ['read'] }, + ], + }, + video: { + $actions: [ + { who: 'anyone', can: ['create'] }, + { who: 'author', of: 'video', can: ['read'] }, + { who: 'recipient', of: 'video', can: ['read'] }, + ], + }, + }, + }, + }; + // :snippet-end: + const response = await web5.dwn.protocols.configure({ + message: { + definition: playlistProtocolDefinition, + }, + }); + + expect(response.status.code).toBe(202); + }); + + test('queryFromDwnByProtocolPath returns an array of records', async () => { + const response = await queryFromDwnByProtocolPath(web5); + + expect(Array.isArray(response)).toBe(true); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/read-from-dwn.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/read-from-dwn.test.js new file mode 100644 index 000000000..401f5c995 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/read-from-dwn.test.js @@ -0,0 +1,17 @@ +import { test, beforeAll, expect, describe } from 'vitest'; +import { readFromDwn } from '../../../../code-snippets/web5/build/decentralized-web-nodes/read-from-dwn'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; + +describe('Testing reading from DWNs', () => { + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + }); + + test('readFromDwn returns 200 status code', async () => { + const result = await readFromDwn(web5); + expect(result.status.code).toBe(200); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/records.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/records.test.js new file mode 100644 index 000000000..0fa5b02b0 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/records.test.js @@ -0,0 +1,50 @@ +import { test, beforeAll, expect, describe } from 'vitest'; + +import { + readRecordFromId, + deleteRecordFromDid, +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/records'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; +let record; +let myDid; + +describe('records', () => { + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + myDid = globalThis.did; + }); + + describe('tests for /api/web5-js/dwn/records', async () => { + test('readRecordFromId reads a record', async () => { + const text = 'readRecordFromId'; + const { record: textRecord } = await web5.dwn.records.create({ + data: text, + message: { + dataFormat: 'text/plain', + }, + }); + const returnedText = await readRecordFromId(web5, textRecord.id); + expect(returnedText).toBe(text); + }); + + test('deleteRecordFromDid deletes a record', async () => { + const { record } = await web5.dwn.records.create({ + data: 'delete me', + message: { + dataFormat: 'text/plain', + }, + }); + /* + immediately send record to user's remote DWNs + so that we can read it from there in the below test + */ + await record.send(myDid); + + const result = await deleteRecordFromDid(web5, record, myDid); + expect(result.status.code).toBe(202); + }); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/send.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/send.test.js new file mode 100644 index 000000000..4938c5848 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/send.test.js @@ -0,0 +1,88 @@ +import { test, beforeAll, expect, describe } from 'vitest'; + +import { + createLocalRecord, + createLocalProtocol, + sendLocalRecordToTarget, + sendRecordToRemoteDWNs, + sendProtocolToRemoteDWNs, + sendRecordToDWNOfRecipient, +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/send'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; +let did; + +describe('send', () => { + // connect to web5 beforeAll tests and assign it to web5 variable + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + }); + + test('createLocalRecord creates a record', async () => { + const record = await createLocalRecord(web5); + expect(record).toBeDefined(); + }); + + test('createLocalProtocol installs a protocol', async () => { + const protocolDefinition = { + protocol: 'example:protocol', + published: true, + types: { + myobject: { + schema: 'example:protocol/myobject', + dataFormats: ['application/json'], + }, + }, + structure: { + myobject: { + $actions: [{ who: 'anyone', can: ['create', 'read'] }], + }, + }, + }; + const response = await createLocalProtocol(web5, protocolDefinition); + expect(response.status.code).toBe(202); + }); + + //blocked by https://github.com/TBD54566975/dwn-sdk-js/issues/550 + test.todo('sendLocalRecordToTarget creates record', async () => { + const record = await sendLocalRecordToTarget(web5, did); + expect(record).toBeDefined(); + }); + + test('sendRecordToRemoteDWNs sends record', async () => { + const status = await sendRecordToRemoteDWNs(web5, did); + expect(status.code).toBe(202); + }); + + test('sendProtocolToRemoteDWNs sends a protocol', async () => { + const protocolDefinition = { + protocol: 'example:remoteprotocol', + published: true, + types: { + myobject: { + schema: 'example:protocol/myobject', + dataFormats: ['application/json'], + }, + }, + structure: { + myobject: { + $actions: [{ who: 'anyone', can: ['create', 'read'] }], + }, + }, + }; + const status = await sendProtocolToRemoteDWNs( + web5, + protocolDefinition, + did, + ); + expect(status.code).toBe(202); + }); + + test('sendRecordToDWNOfRecipient can be configured', async () => { + const status = await sendRecordToDWNOfRecipient(web5, did); + expect(status.code).toBe(202); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/update-dwn.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/update-dwn.test.js new file mode 100644 index 000000000..b898e1613 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/update-dwn.test.js @@ -0,0 +1,29 @@ +import { test, beforeAll, expect, describe } from 'vitest'; +import { updateDwnRecord } from '../../../../code-snippets/web5/build/decentralized-web-nodes/update-dwn'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; + +describe('update-dwn-test', () => { + // connect to web5 beforeAll tests and assign it to web5 variable + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + }); + + test('updateDwnRecord updates an existing record', async () => { + //Create record to update + const { record: createdRecord } = await web5.dwn.records.create({ + data: 'test record', + message: { + dataFormat: 'text/plain', + }, + }); + + //Call code snippet to update record + const updateStatus = await updateDwnRecord(web5, createdRecord); + + //Assert that status code is 202 + expect(updateStatus.code).toBe(202); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/use-identity-agents.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/use-identity-agents.test.js new file mode 100644 index 000000000..4712ccb3b --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/use-identity-agents.test.js @@ -0,0 +1,23 @@ +import { test, expect, describe } from 'vitest'; +import { getDwnEndpoints } from '../../../../code-snippets/web5/build/decentralized-web-nodes/use-identity-agents'; +import { setUpIdentityManager } from '../../../test-utils/setup-web5'; + +let agent; + +describe('create identity agent', () => { + test('createIdentityAgent', async () => { + agent = await setUpIdentityManager(); + }); + test('createDidOptions returns an object with service endpoints', async () => { + const didOptions = await getDwnEndpoints(); + + expect(didOptions).toHaveProperty('services'); + expect(Array.isArray(didOptions.services)).toBe(true); + didOptions.services.forEach((service) => { + expect(service).toHaveProperty('id'); + expect(service).toHaveProperty('serviceEndpoint'); + expect(service).toHaveProperty('type'); + expect(service.type).toBe('DecentralizedWebNode'); + }); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/write-to-dwn.test.js b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/write-to-dwn.test.js new file mode 100644 index 000000000..ffec6c274 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/decentralized-web-nodes/write-to-dwn.test.js @@ -0,0 +1,186 @@ +import { test, beforeAll, expect, describe } from 'vitest'; + +import { + createTextRecord, + createJsonRecord, + uploadImage, + uploadFile, + createMixedRecord, +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/write-to-dwn'; +import { setUpWeb5 } from '../../../test-utils/setup-web5'; + +let web5; +let did; + +describe('write-to-dwn', () => { + // connect to web5 beforeAll tests and assign it to web5 variable + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + did = globalThis.did; + }); + + test('createTextRecord creates a text record', async () => { + // :snippet-start: createTextRecord + // Create a plain text record + const { record } = await web5.dwn.records.create({ + data: { + content: 'Hello Web5', + description: 'Keep Building!', + }, + message: { + dataFormat: 'application/json', + }, + }); + // :snippet-end: + expect(record).toBeDefined(); + }); + + test('createJsonRecord creates a JSON record', async () => { + // :snippet-start: createJsonRecord + // Create a JSON record + const { record } = await web5.dwn.records.create({ + data: { + content: 'Hello Web5', + description: 'Keep Building!', + }, + message: { + dataFormat: 'application/json', + }, + }); + // :snippet-end: + expect(record).toBeDefined(); + }); + + test('uploadImage uploads an image', async () => { + const mockEvent = { + currentTarget: { + files: [new Blob(['fake image data'], { type: 'image/png' })], + }, + }; + // :snippet-start: uploadImage + // Create a blob record + async function upload(event) { + const blob = new Blob(event.currentTarget.files, { type: 'image/png' }); + const { record } = await web5.dwn.records.create({ + data: blob, + message: { + dataFormat: 'image/png', + }, + }); + return record; + } + //:snippet-end: + const record = await upload(mockEvent); + expect(record).toBeDefined(); + }); + + test('uploadFile uploads a file', async () => { + const mockEvent = { + currentTarget: { + files: [ + new Blob(['fake file data'], { type: 'application/octet-stream' }), + ], + }, + }; + // :snippet-start: uploadFile + // Create a file record + async function upload(event) { + const file = event.currentTarget.files[0]; + const { status: fileStatus, record } = await web5.dwn.records.create({ + data: file, + message: { + schema: 'https://schema.org/path/to/schema', + dataFormat: 'application/octet-stream', + }, + }); + return record; + } + //:snippet-end: + + const record = await upload(mockEvent); + expect(record).toBeDefined(); + }); + test('createMixedRecord creates a message with an image and file', async () => { + const username = 'testUser'; + const messageText = 'testMessage'; + const imageFile = new Blob(['fake image data'], { type: 'image/png' }); + // :snippet-start: createMixedRecord + // Create a mixed record + async function createMessage(username, messageText, imageFile) { + let base64Image = null; + + if (imageFile) { + const binaryImage = await imageFile.arrayBuffer(); + base64Image = btoa( + new Uint8Array(binaryImage).reduce( + (data, byte) => data + String.fromCharCode(byte), + '', + ), + ); + } + + const messageData = { + username, + message: messageText, + image: base64Image, + }; + + const { record } = await web5.dwn.records.create({ + data: messageData, + message: { + schema: 'http://schema-registry.org/message', + dataFormat: 'application/json', + }, + }); + return record; + } + //:snippet-end: + + const record = await createMessage(username, messageText, imageFile); + expect(record).toBeDefined(); + }); +}); + +test('createRecordFrom creates a record from an existing record', async () => { + const { record: originalRecord } = await web5.dwn.records.create({ + data: 'Hello, Web5!', + message: { + dataFormat: 'text/plain', + }, + }); + // :snippet-start: createRecordFrom + // Create a new version of the record based on the original record + const { record: newVersionRecord } = await web5.dwn.records.createFrom({ + record: originalRecord, + data: 'I am a new version of the original record!', + message: { + dataFormat: 'text/plain', + published: true, + }, + }); + // :snippet-end: + const newRecordDataText = await newVersionRecord.data.text(); + expect(newRecordDataText).toBe('I am a new version of the original record!'); +}); + +test('createRecordWithTags creates a record with tags', async () => { + // :snippet-start: createRecordWithTags + // Creates a record with tags + const { record } = await web5.dwn.records.create({ + data: 'Chocolate Chip Cookies', + message: { + dataFormat: 'application/json', + tags: { + dishType: 'Dessert', + dietaryRestriction: 'Contains Gluten', + }, + }, + }); + // :snippet-end: + expect(record.tags).to.exist; + expect(record.tags).to.deep.equal({ + dishType: 'Dessert', + dietaryRestriction: 'Contains Gluten', + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/quickstart.test.js b/site/web5_versioned_docs/version-1.0.1/test/quickstart.test.js new file mode 100644 index 000000000..b598c5b85 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/quickstart.test.js @@ -0,0 +1,192 @@ +import { test, beforeAll, expect, describe } from 'vitest'; +import { setUpWeb5 } from '../../test-utils/setup-web5'; +import { VerifiableCredential } from '@web5/credentials'; + +// This is the web5 instance that will be referred to for all tests. This comes back as a result from Web5.connect() being used in the didCreate function. +let web5; +// This is the decentralized ID that will be referred to for all tests. This comes back as a result from Web5.connect() being used in the didCreate function. +let aliceDid; +// This record result is what comes back from the createTextRecord function. This is used to test the record's attributes and methods. + +describe('/site/tests/quickstart.test.js', async () => { + // This is where we create a DID, assign the web5 and aliceDid variables, and then use the aliceDid to write a text record. + beforeAll(async () => { + await setUpWeb5(); + web5 = globalThis.web5; + aliceDid = globalThis.did; + }); + + test('didCreate returns a decentralized ID', async () => { + expect(typeof aliceDid).toBe('string'); + const didRegex = /^did:[a-z0-9]+:.+/i; + expect(didRegex.test(aliceDid)).toBe(true); + }); + + test('getBearerDid returns a bearer identity', async () => { + // :snippet-start: getBearerDid + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); + // :snippet-end: + expect(aliceBearerDid.uri).toBe(aliceDid); + }); + + test('createQuickstartVc returns a vc', async () => { + // :snippet-start: createQuickstartVc + const vc = await VerifiableCredential.create({ + type: 'Web5QuickstartCompletionCredential', + issuer: aliceDid, + subject: aliceDid, + data: { + name: 'Alice Smith', + completionDate: new Date().toISOString(), + expertiseLevel: 'Beginner', + }, + }); + // :snippet-end: + expect(vc.vcDataModel.issuer).toBe(aliceDid); + }); + + test('signQuickstartVc returns a jwt', async () => { + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); + class Web5QuickstartCompletionCredential { + constructor(name, completionDate, expertiseLevel) { + this.name = name; + this.completionDate = completionDate; + this.expertiseLevel = expertiseLevel; + } + } + + const vc = await VerifiableCredential.create({ + type: 'Web5QuickstartCompletionCredential', + issuer: aliceDid, + subject: aliceDid, + data: new Web5QuickstartCompletionCredential( + 'Alice Smith', + '2024-05-22', + 'Beginner', + ), + }); + // :snippet-start: signQuickstartVc + const signedVc = await vc.sign({ did: aliceBearerDid }); + // :snippet-end: + expect(typeof signedVc).toBe('string'); + }); + + test('writeQuickstartVcToDwn writes a signed vc to dwn', async () => { + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); + class Web5QuickstartCompletionCredential { + constructor(name, completionDate, expertiseLevel) { + this.name = name; + this.completionDate = completionDate; + this.expertiseLevel = expertiseLevel; + } + } + + const vc = await VerifiableCredential.create({ + type: 'Web5QuickstartCompletionCredential', + issuer: aliceDid, + subject: aliceDid, + data: new Web5QuickstartCompletionCredential( + 'Alice Smith', + '2024-05-22', + 'Beginner', + ), + }); + const signedVc = await vc.sign({ did: aliceBearerDid }); + // :snippet-start: writeQuickstartVcToDwn + const { record } = await web5.dwn.records.create({ + data: signedVc, + message: { + schema: 'Web5QuickstartCompletionCredential', + dataFormat: 'application/vc+jwt', + published: true, + }, + }); + // :snippet-end: + expect(record.author).toBe(aliceDid); + }); + + test('readQuickstartVc reads jwt from DWN', async () => { + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); + class Web5QuickstartCompletionCredential { + constructor(name, completionDate, expertiseLevel) { + this.name = name; + this.completionDate = completionDate; + this.expertiseLevel = expertiseLevel; + } + } + + const vc = await VerifiableCredential.create({ + type: 'Web5QuickstartCompletionCredential', + issuer: aliceDid, + subject: aliceDid, + data: new Web5QuickstartCompletionCredential( + 'Alice Smith', + '2024-05-22', + 'Beginner', + ), + }); + const signedVc = await vc.sign({ did: aliceBearerDid }); + + const { record } = await web5.dwn.records.create({ + data: signedVc, + message: { + schema: 'Web5QuickstartCompletionCredential', + dataFormat: 'application/vc+jwt', + published: true, + }, + }); + // :snippet-start: readQuickstartVc + const readSignedVc = await record.data.text(); + // :snippet-end: + expect(typeof readSignedVc).toBe('string'); + }); + + test('parseQuickstartVc reads jwt from DWN', async () => { + const { did: aliceBearerDid } = await web5.agent.identity.get({ + didUri: aliceDid, + }); + class Web5QuickstartCompletionCredential { + constructor(name, completionDate, expertiseLevel) { + this.name = name; + this.completionDate = completionDate; + this.expertiseLevel = expertiseLevel; + } + } + + const vc = await VerifiableCredential.create({ + type: 'Web5QuickstartCompletionCredential', + issuer: aliceDid, + subject: aliceDid, + data: new Web5QuickstartCompletionCredential( + 'Alice Smith', + '2024-05-22', + 'Beginner', + ), + }); + + const signedVc = await vc.sign({ did: aliceBearerDid }); + + const { record } = await web5.dwn.records.create({ + data: signedVc, + message: { + schema: 'Web5QuickstartCompletionCredential', + dataFormat: 'application/vc+jwt', + published: true, + }, + }); + + const readSignedVc = await record.data.text(); + // :snippet-start: parseQuickstartVc + const parsedVc = VerifiableCredential.parseJwt({ vcJwt: readSignedVc }); + // :snippet-end: + expect(parsedVc.vcDataModel.issuer).toBe(aliceDid); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/fan-club-vc.test.js b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/fan-club-vc.test.js new file mode 100644 index 000000000..f5815bb40 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/fan-club-vc.test.js @@ -0,0 +1,174 @@ +import { test, expect, vi, describe, beforeAll } from 'vitest'; +// :snippet-start: fanClubVcImportsJs +import { DidDht } from '@web5/dids'; +import { VerifiableCredential, PresentationExchange, VerifiablePresentation } from '@web5/credentials'; +// :snippet-end: + +describe('fan-club-vc', () => { + let fanClubIssuerDid, aliceDid, SwiftiesFanClub, vc, signedVcJwt, presentationDefinition; + + beforeAll(async () => { + fanClubIssuerDid = await DidDht.create(); + aliceDid = await DidDht.create(); + + SwiftiesFanClub = class { + constructor(level, legit) { + this.level = level; + this.legit = legit; + } + }; + + vc = await VerifiableCredential.create({ + type: 'SwiftiesFanClub', + issuer: fanClubIssuerDid.uri, + subject: aliceDid.uri, + data: new SwiftiesFanClub('Stan', true) + }); + + signedVcJwt = await vc.sign({ did: fanClubIssuerDid }); + + presentationDefinition = { + 'id': 'presDefId123', + 'name': 'Swifties Fan Club Presentation Definition', + 'purpose': 'for proving membership in the fan club', + 'input_descriptors': [ + { + 'id': 'legitness', + 'purpose': 'are you legit or not?', + 'constraints': { + 'fields': [ + { + 'path': [ + '$.credentialSubject.legit', + ] + } + ] + } + } + ] + }; + + vi.mock('@web5/api', () => ({ + Web5: { + connect: vi.fn(() => ({ web5, did })), + }, + })); + }); + test('createDids creates an issuer DID and alice DID with did:dht method', async () => { + // :snippet-start: createDids + const fanClubIssuerDid = await DidDht.create(); + const aliceDid = await DidDht.create(); + // :snippet-end: + expect(aliceDid.uri).toMatch(/^did:dht:/); + expect(fanClubIssuerDid.uri).toMatch(/^did:dht:/); + }); + + test('createFanClubVc creates a vc for fan club', async () => { + // :snippet-start: createFanClubVc + const vc = await VerifiableCredential.create({ + type: 'SwiftiesFanClub', + issuer: fanClubIssuerDid.uri, + subject: aliceDid.uri, + data: new SwiftiesFanClub('Stan', true) + }); + // :snippet-end: + expect(vc).toHaveProperty('vcDataModel'); + expect(vc.vcDataModel).toHaveProperty('credentialSubject'); + expect(vc.vcDataModel.credentialSubject.level).toBe('Stan'); + expect(vc.vcDataModel.credentialSubject.legit).toBe(true); + }); + + test('signFanClubVc signs a vc for fan club and returns jwt', async () => { + // :snippet-start: signFanClubVc + const signedVcJwt = await vc.sign({ did: fanClubIssuerDid }); + // :snippet-end: + expect(typeof signedVcJwt).toBe('string'); + expect(signedVcJwt).not.toBe(''); + }); + + test('createAndValidatePresentation creates and validates presentation definition', async () => { + // :snippet-start: createAndValidatePresentation + const presentationDefinition = { + 'id': 'presDefId123', + 'name': 'Swifties Fan Club Presentation Definition', + 'purpose': 'for proving membership in the fan club', + 'input_descriptors': [ + { + 'id': 'legitness', + 'purpose': 'are you legit or not?', + 'constraints': { + 'fields': [ + { + 'path': [ + '$.credentialSubject.legit', + ] + } + ] + } + } + ] + }; + + const definitionValidation = PresentationExchange.validateDefinition({ presentationDefinition }); + // :snippet-end: + + expect(Array.isArray(definitionValidation)).toBe(true); + expect.soft(definitionValidation[0]).toHaveProperty('status', 'info'); + expect.soft(definitionValidation[0]).toHaveProperty('message', 'ok'); + }); + + test('satisfiesPresentationDefinitionFanClubVc checks if VC satisfies the presentation definition', async () => { + const logSpy = vi.spyOn(console, 'log'); + // :snippet-start: satisfiesPresentationDefinitionFanClubVc + // Does VC Satisfy the Presentation Definition + try { + PresentationExchange.satisfiesPresentationDefinition({ vcJwts: [signedVcJwt], presentationDefinition: presentationDefinition }); + } catch (err) { + console.log('VC does not satisfy Presentation Definition: ' + err.message); + } + // :snippet-end: + expect(logSpy).not.toHaveBeenCalled(); + logSpy.mockRestore(); + }); + + test('createPresentationFromCredentialsFanClubVc creates presentation from credentials and checks the presentation result', async () => { + // :snippet-start: createPresentationFromCredentialsFanClubVc + // Create Presentation Result that contains a Verifiable Presentation and Presentation Submission + const presentationResult = PresentationExchange.createPresentationFromCredentials({ vcJwts: [signedVcJwt], presentationDefinition: presentationDefinition }); + + const vp = await VerifiablePresentation.create({ + holder: aliceDid.uri, + vcJwts: [signedVcJwt], + additionalData: { presentationResult } + }); + // :snippet-end: + expect(presentationResult.presentation).toHaveProperty('@context'); + expect(presentationResult.presentation).toHaveProperty('type'); + expect(presentationResult.presentation).toHaveProperty('presentation_submission'); + expect(presentationResult).toHaveProperty('presentationSubmissionLocation'); + expect(presentationResult).toHaveProperty('presentationSubmission'); + expect(vp.vpDataModel.verifiableCredential[0]).toEqual(signedVcJwt); + }); + test('verifyFanClubVc checks if VC verification is successful', async () => { + const logSpy = vi.spyOn(console, 'log'); + // :snippet-start: verifyFanClubVc + try { + await VerifiableCredential.verify({ vcJwt: signedVcJwt }); + } catch (err) { + console.log('\nVC Verification failed: ' + err.message + '\n'); + } + // :snippet-end: + expect(logSpy).not.toHaveBeenCalled(); + logSpy.mockRestore(); + }); + + test('parseFanClubJwt parses the signed VC JWT', async () => { + // :snippet-start: parseFanClubJwt + const parsedVC = await VerifiableCredential.parseJwt({ vcJwt: signedVcJwt }); + // :snippet-end: + expect(parsedVC).toHaveProperty('vcDataModel'); + expect(parsedVC.vcDataModel).toHaveProperty('credentialSubject'); + expect(parsedVC.vcDataModel.credentialSubject.level).toBe('Stan'); + expect(parsedVC.vcDataModel.credentialSubject.legit).toBe(true); + }); +}); \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/fan.club.sdks.bash b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/fan.club.sdks.bash new file mode 100644 index 000000000..b3b05dfc8 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/fan.club.sdks.bash @@ -0,0 +1,4 @@ +# :snippet-start: fanClubSdkInstallJs +npm i @web5/credentials +npm i @web5/dids +# :snippet-end: \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/jwt-to-vc.test.js b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/jwt-to-vc.test.js new file mode 100644 index 000000000..5f748b0e7 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/jwt-to-vc.test.js @@ -0,0 +1,27 @@ +import { test, expect, describe } from 'vitest'; +import { VerifiableCredential } from '@web5/credentials'; + +describe('decode JWT into VC', () => { + test('jwt string is correct', async () => { + + // :snippet-start: jwtToVcVar + const signedVcJwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6a2ZkdGJjbTl6Z29jZjVtYXRmOWZ4dG5uZmZoaHp4YzdtZ2J3cjRrM3gzcXppYXVjcHA0eSMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRW1wbG95bWVudENyZWRlbnRpYWwiXSwiaWQiOiJ1cm46dXVpZDo4ZmQ1MjAzMC0xY2FmLTQ5NzgtYTM1ZC1kNDE3ZWI4ZTAwYjIiLCJpc3N1ZXIiOiJkaWQ6ZGh0OmtmZHRiY205emdvY2Y1bWF0ZjlmeHRubmZmaGh6eGM3bWdid3I0azN4M3F6aWF1Y3BwNHkiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTIxVDE3OjAyOjAxWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmRodDp5MzltNDhvem9ldGU3ejZmemFhbmdjb3M4N2ZodWgxZHppN2Y3andiamZ0N290c2toOXRvIiwicG9zaXRpb24iOiJTb2Z0d2FyZSBEZXZlbG9wZXIiLCJzdGFydERhdGUiOiIyMDIxLTA0LTAxVDEyOjM0OjU2WiIsImVtcGxveW1lbnRTdGF0dXMiOiJDb250cmFjdG9yIn0sImV4cGlyYXRpb25EYXRlIjoiMjAyMi0wOS0zMFQxMjozNDo1NloifSwiaXNzIjoiZGlkOmRodDprZmR0YmNtOXpnb2NmNW1hdGY5Znh0bm5mZmhoenhjN21nYndyNGszeDNxemlhdWNwcDR5Iiwic3ViIjoiZGlkOmRodDp5MzltNDhvem9ldGU3ejZmemFhbmdjb3M4N2ZodWgxZHppN2Y3andiamZ0N290c2toOXRvIn0.ntcgPOdXOatULWo-q6gkuhKmi5X3bzCONQY38t_rsC1hVhvvdAtmiz-ccoLIYUkjECRHIxO_UZbOKgn0EETBCA"; + // :snippet-end: + + // :snippet-start: jwtToVcParse + const vc = VerifiableCredential.parseJwt({ vcJwt: signedVcJwt }); + // :snippet-end: + + expect(typeof signedVcJwt).toBe('string'); + expect(vc).toBeDefined(); + expect(vc.issuer).toMatch(/^did:dht:/); + expect.soft(vc).toHaveProperty('type', 'EmploymentCredential'); + expect.soft(vc.vcDataModel).toHaveProperty('id', 'urn:uuid:8fd52030-1caf-4978-a35d-d417eb8e00b2'); + expect.soft(vc.vcDataModel).toHaveProperty('expirationDate', '2022-09-30T12:34:56Z'); + expect.soft(vc.vcDataModel).toHaveProperty('credentialSubject'); + expect.soft(vc.vcDataModel.credentialSubject).toHaveProperty('startDate', '2021-04-01T12:34:56Z'); + expect.soft(vc.vcDataModel.credentialSubject).toHaveProperty('position', 'Software Developer'); + expect.soft(vc.vcDataModel.credentialSubject).toHaveProperty('employmentStatus', 'Contractor'); + + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/presentation-definition.test.js b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/presentation-definition.test.js new file mode 100644 index 000000000..e92edb232 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/presentation-definition.test.js @@ -0,0 +1,82 @@ +import { test, expect, describe, vi } from 'vitest'; +import { PresentationExchange } from '@web5/credentials'; + +const pd = { + id: 'PD_JobApplication_123456', + name: 'Credentials Verification for Ethical Hacker Job Application', + purpose: "To verify the applicant's employment history, and either their academic degree or Certified Ethical Hacker certification", + input_descriptors: [ + { + id: 'employmentHistoryVerification', + name: 'Employment History', + purpose: "Verify the applicant's previous employment experiences", + constraints: { + fields: [ + { + path: ['$.type[*]'], + filter: { + type: 'string', + pattern: 'Employment', + }, + }, + ], + }, + }, + { + id: 'degreeVerification', + name: 'Degree', + purpose: "Confirm the applicant's academic qualification", + constraints: { + fields: [ + { + path: ['$.credentialSubject.degree.type'], + filter: { + type: 'string', + pattern: '(Engineering|Computer|Cyber|Security)', + }, + }, + ], + }, + }, + { + id: 'CEH_CertificationVerification', + name: 'Certified Ethical Hacker Certification', + purpose: 'Confirm the applicant holds a Certified Ethical Hacker certification', + constraints: { + fields: [ + { + path: ['$.credentialSubject.certifications[*].name'], + filter: { + type: 'string', + pattern: 'Certified Ethical Hacker', + }, + }, + { + path: ['$.issuer'], + filter: { + type: 'string', + const: 'did:example:123456789abcdefghi', + }, + }, + ], + }, + }, + ], +}; + +describe('Presentation Definition Validation', () => { + test('validate presentation definition', async () => { + const consoleSpy = vi.spyOn(console, 'log'); + + // :snippet-start: validatePresentationDefinition + try { + PresentationExchange.validateDefinition({ presentationDefinition: pd }); + } catch (e) { + console.log(e); + } + // :snippet-end: + + expect(consoleSpy).not.toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/presentation-exchange.test.js b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/presentation-exchange.test.js new file mode 100644 index 000000000..8c42d9ff2 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/presentation-exchange.test.js @@ -0,0 +1,248 @@ +import { test, describe, expect, beforeAll } from 'vitest'; + +import { + pex_createPresentationFromCredentials, + pex_getLoanAppPresentationDefinition, +} from '../../../../code-snippets/web5/build/verifiable-credentials/presentation-exchange'; +import { + PresentationExchange, + VerifiablePresentation, +} from '@web5/credentials'; +import { DidDht } from '@web5/dids'; + +const pd = await pex_getLoanAppPresentationDefinition(); + +describe('Presentation Exchange Process', () => { + let signedEmploymentVcJwt; + let signedNameAndDobVcJwt; + let credentials; + let holderDid; + let selectedCredentials; + let presentationResult; + let verifiablePresentation; + + beforeAll(async () => { + signedEmploymentVcJwt = + 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyI3o2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJFbXBsb3ltZW50Q3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjcyNDhiOTkyLTkwOTYtNDk2NS1hMGVjLTc3ZDhhODNhMWRmYiIsImlzc3VlciI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDoxMToyNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlEMTR4UmY0cTJNWlh1ZWY2X2ZXYnBGbVlTUG94dGFxTkp1SmdEMG96Wl84UTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWVubGFNbVYzTlhKeVVXdFVjbUV3WlZsVk16WlBTblJzTURCbFJWZHhhalZhV0dkNmNEZFpSVTVKUVNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpQZDJZMFQyMUViamxKWm5SNFdYWnBkRTFHWm1jMVVXeDVMVVV6VWs1b1dsUkdPVlpFTWtnNVQzVjNJaXdpZVNJNkltUnZjVmxtV2s1c1NtRlRNVll4U201bU9HdEZObEF6VkRsd2QzaDNla3hFVTJWc1ZqTlRUa2s1U2xFaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xEWm05bVFUQkpVbU5uY2tWdVVHZHdRbU5RV1ZsV2VFWlliR0pTYjJRd2RVNWZRVkJwTkVrNUxVRmZRU0o5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFtd3pWWG80VldGT2REZGxlREJKYjJJMFJFNXNhbFJGVmpaelQwTmtjbFJ3TWxvNE5FTkJPVFJPUWtFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVOWk9WRldZbWRKYkUxemRraEZYMVJtTld4a1MxQjBkR3d3WVV4blNrdHNSbmt6Vms0d2QzQTJhVFpSSW4xOSIsImVtcGxveW1lbnRTdGF0dXMiOiJlbXBsb3llZCJ9fX0.Sazc8Ndhs-NKjxvtVMKeC9dxjEkI26fVsp2kFNWM-SYLtxMzKvl5ffeWd81ysHgPmBBSk2ar4dMqGgUsyM4gAQ'; + signedNameAndDobVcJwt = + 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0I3o2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJOYW1lQW5kRG9iQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjliZjM2YzY5LTI0ODAtNDllZC1iMTYyLTRlZDEwOWE3MTc3NyIsImlzc3VlciI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0IiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDowNjowMVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlDS2o2M0FyZlBGcEpsb2lTd3gxQUhxVWtpWlNoSDZGdnZoSzRaTl9fZDFtQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWNscFdXbTVJVkVrNWFEWkJUVmxVV0dwT01HcFhTVkYwTTI5ak4xTnJTeTF4Y2kxcVVuSTBUalEzUlNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpaVDFwRE5WSmlUMHQ1T0dadVVUWTJVWEZPUkc5aldFMXZPVXhUZEdNNVYyOWthMHd0ZFZCZlExQnZJaXdpZVNJNklsWnZZM0UxVERodFozQlhXVTFrYjFwS1JrWlJUa1ZDT0hsR0xXTndkRWQzZFdkcFRWVm5hR2t6Y21jaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCTXpSMlMzb3llVmswZVV4dGRDMUdabkJuYWpWbGFFRm1ZWFI1YzFOa2MwNVNWbVpMYkhwUWRqTjVkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFF6ZGZjMXBzTW1wMVVXNUdhRVJIV1RSb2NFVTRiMlF4YVU5MWRuZG1PVFJ5TVVkbk9HMWFWbVJCVmxFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVKdU5sTnJiSEpWYzNKdVFuaFJPVXBqVXkxTlNVaGtWelYwTXpRM1MxWjNaMXBwVEZwMFQwcDRRVkYzSW4xOSIsIm5hbWUiOiJhbGljZSBib2IiLCJkYXRlT2ZCaXJ0aCI6IjEwLTAxLTE5OTAifX19.mNCDv_JntH-wZpYONKNL58UbOWaYXCYJO_HPI_WVlSgwzo6dhYmV_9qtpFKd_exFb-aaEYPeSE43twWlrJeSBg'; + + credentials = [signedEmploymentVcJwt, signedNameAndDobVcJwt]; + selectedCredentials = credentials; + + holderDid = await DidDht.create(); + + presentationResult = PresentationExchange.createPresentationFromCredentials( + { + vcJwts: selectedCredentials, + presentationDefinition: presentationDefinition, + }, + ); + + verifiablePresentation = await VerifiablePresentation.create({ + holder: holderDid.uri, + vcJwts: [selectedCredentials], + additionalData: { presentationResult }, + }); + }); + + const presentationDefinition = { + id: 'presDefIdloanAppVerification123', + name: 'Loan Application Employment Verification', + purpose: 'To verify applicant’s employment, date of birth, and name', + input_descriptors: [ + // Employment Verification + { + id: 'employmentVerification', + purpose: 'Confirm current employment status', + constraints: { + fields: [ + { + path: ['$.credentialSubject.employmentStatus'], + filter: { + type: 'string', + pattern: 'employed', + }, + }, + ], + }, + }, + // Date of Birth Verification + { + id: 'dobVerification', + purpose: 'Confirm the applicant’s date of birth', + constraints: { + fields: [ + { + path: ['$.credentialSubject.dateOfBirth'], + filter: { + type: 'string', + format: 'date', + }, + }, + ], + }, + }, + // Name Verification + { + id: 'nameVerification', + purpose: 'Confirm the applicant’s legal name', + constraints: { + fields: [ + { + path: ['$.credentialSubject.name'], + filter: { + type: 'string', + }, + }, + ], + }, + }, + ], + }; + + test('getLoanAppPresentationDefinition returns a presentation definition', async () => { + // :snippet-start: getLoanAppPresentationDefinition + const presentationDefinition = { + id: 'presDefIdloanAppVerification123', + name: 'Loan Application Employment Verification', + purpose: 'To verify applicant’s employment, date of birth, and name', + input_descriptors: [ + // Employment Verification + { + id: 'employmentVerification', + purpose: 'Confirm current employment status', + constraints: { + fields: [ + { + path: ['$.credentialSubject.employmentStatus'], + filter: { + type: 'string', + pattern: 'employed', + }, + }, + ], + }, + }, + // Date of Birth Verification + { + id: 'dobVerification', + purpose: 'Confirm the applicant’s date of birth', + constraints: { + fields: [ + { + path: ['$.credentialSubject.dateOfBirth'], + filter: { + type: 'string', + format: 'date', + }, + }, + ], + }, + }, + // Name Verification + { + id: 'nameVerification', + purpose: 'Confirm the applicant’s legal name', + constraints: { + fields: [ + { + path: ['$.credentialSubject.name'], + filter: { + type: 'string', + }, + }, + ], + }, + }, + ], + }; + // :snippet-end: + expect(presentationDefinition).toBeDefined(); + expect(presentationDefinition).toHaveProperty('input_descriptors'); + expect(presentationDefinition.input_descriptors).toBeInstanceOf(Array); + expect(presentationDefinition.input_descriptors.length).toBe(3); + }); + test('selectCredentialsForPex selects VCs that match presentation defintion', async () => { + const allCredentials = credentials; + // :snippet-start: selectCredentialsForPex + const selectedCredentials = PresentationExchange.selectCredentials({ + vcJwts: allCredentials, + presentationDefinition: presentationDefinition, + }); + // :snippet-end: + + expect(selectedCredentials).toBeDefined(); + expect(selectedCredentials).toBeInstanceOf(Array); + expect.soft(selectedCredentials.length).toBe(2); + expect.soft(selectedCredentials).toContain(signedEmploymentVcJwt); + expect.soft(selectedCredentials).toContain(signedNameAndDobVcJwt); + }); + + test('satisfiesPresentationDefinitionForPex checks if VCs satisfy PD', async () => { + const selectedCredentials = credentials; + expect(() => { + // :snippet-start: satisfiesPresentationDefinitionForPex + try { + PresentationExchange.satisfiesPresentationDefinition({ + vcJwts: selectedCredentials, + presentationDefinition: presentationDefinition, + }); + } catch (err) { + //Handle errors here + } + // :snippet-end: + }).not.toThrow(); + }); + + test('createPresentationFromCredentialsForPex creates a presentation result', async () => { + // :snippet-start: createPresentationFromCredentialsForPex + const presentationResult = + PresentationExchange.createPresentationFromCredentials({ + vcJwts: selectedCredentials, + presentationDefinition: presentationDefinition, + }); + + const vp = await VerifiablePresentation.create({ + holder: holderDid.uri, + vcJwts: [selectedCredentials], + additionalData: { presentationResult }, + }); + // :snippet-end: + expect(presentationResult).toBeDefined(); + expect.soft(presentationResult).toHaveProperty('presentation'); + expect + .soft(presentationResult.presentation) + .toHaveProperty('presentation_submission'); + expect + .soft(presentationResult.presentation) + .toHaveProperty('verifiableCredential'); + expect + .soft(presentationResult.presentation.type) + .toContain('VerifiablePresentation'); + expect.soft(vp).toHaveProperty('vpDataModel'); + }); + + test('validPresentationSubmissionForPex check if the presention submission is valid', async () => { + const presentationResult = await pex_createPresentationFromCredentials( + credentials, + pd, + ); + // :snippet-start: validPresentationSubmissionForPex + const submissionCheck = PresentationExchange.validateSubmission({ + presentationSubmission: presentationResult.presentationSubmission, + }); + // :snippet-end: + expect(submissionCheck.length).toBe(1); + expect.soft(submissionCheck[0]).toHaveProperty('tag', 'root'); + expect.soft(submissionCheck[0]).toHaveProperty('status', 'info'); + expect.soft(submissionCheck[0]).toHaveProperty('message', 'ok'); + }); + + test('validVerifiablePresentationForPex creates a valid VP', async () => { + // :snippet-start: validVerifiablePresentationForPex + const vpJwt = await verifiablePresentation.sign({ did: holderDid }); + // :snippet-end: + expect.soft(typeof vpJwt).toBe('string'); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/required.sdks.bash b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/required.sdks.bash new file mode 100644 index 000000000..c676d2e52 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/required.sdks.bash @@ -0,0 +1,3 @@ +# :snippet-start: vcSdkInstallJs +npm i @web5/credentials +# :snippet-end: \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/revoke-credentials.test.js b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/revoke-credentials.test.js new file mode 100644 index 000000000..41a3d948e --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/revoke-credentials.test.js @@ -0,0 +1,83 @@ +import { test, expect, describe, it } from 'vitest'; +// :prepend-start: createStatusListCredentialJs +import { VerifiableCredential, StatusListCredential, StatusPurpose } from '@web5/credentials'; +// :prepend-end: +import { DidDht } from '@web5/dids'; + + +const issuerDid = await DidDht.create({ publish: true }); +const subjectDid = await DidDht.create({ publish: true }); +const subjectDidUri = subjectDid.uri; +let statusListCredential; +let revocableVC; + +describe('Revoke Credentials', () => { + + it('create Status List Credential', async () => { + // :snippet-start: createStatusListCredentialJs + statusListCredential = StatusListCredential.create({ + statusListCredentialId : 'https://example.com/credentials/status/1', + issuer : issuerDid.uri, + statusPurpose : StatusPurpose.revocation, + credentialsToDisable : [] + }); + // :snippet-end: + + expect(statusListCredential).toBeDefined(); + + const credentialSubject = statusListCredential.vcDataModel.credentialSubject; + expect.soft(credentialSubject.type).to.equal('StatusList2021'); + expect.soft(credentialSubject.statusPurpose).to.equal(StatusPurpose.revocation); + }); + + it('create revocable VC', async () => { + // :snippet-start: createRevocableVerifiableCredentialJs + revocableVC = await VerifiableCredential.create({ + type : 'StreetCred', + issuer : issuerDid.uri, + subject : subjectDidUri, + data : { + streetCred : 'high', + legit : true + }, + + // highlight-start + credentialStatus : { + id : 'https://example.com/credentials/status/1#94567', + type : 'StatusList2021Entry', + statusPurpose : StatusPurpose.revocation, + statusListIndex : '94567', + statusListCredential : 'https://example.com/credentials/status/1', + } + // highlight-end + + }); + // :snippet-end: + + const credentialStatus = revocableVC.vcDataModel.credentialStatus; + expect(credentialStatus).toBeDefined(); + expect(credentialStatus['type']).to.equal('StatusList2021Entry'); + expect(credentialStatus['statusPurpose']).to.equal('revocation'); + }); + + it('revoke credential', async () => { + // :snippet-start: revokeCredentialJs + statusListCredential = StatusListCredential.create({ + statusListCredentialId : 'https://example.com/credentials/status/1', + issuer : issuerDid.uri, + statusPurpose : StatusPurpose.revocation, + // highlight-next-line + credentialsToDisable : [revocableVC] + }); + // :snippet-end: + + // :snippet-start: checkIfCredentialIsRevokedJs + const isRevoked = StatusListCredential.validateCredentialInStatusList( + revocableVC, statusListCredential + ); + // :snippet-end: + + expect(isRevoked).toBe(true); + }); + +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/vc-issuance.test.js b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/vc-issuance.test.js new file mode 100644 index 000000000..a73ce639f --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/vc-issuance.test.js @@ -0,0 +1,53 @@ +import { test, expect, describe } from 'vitest'; +import { DidDht } from '@web5/dids'; +import { VerifiableCredential } from '@web5/credentials'; +import { signCredential } from '../../../../code-snippets/web5/build/verifiable-credentials/vc-issuance'; + +const employer = await DidDht.create(); +const employee = await DidDht.create(); + +describe('issue a credential', () => { + test('createEmploymentCredential creates a VC and signEmploymentCredential returns a jwt', async () => { + // :snippet-start: createEmploymentCredential + const vc = await VerifiableCredential.create({ + type: 'EmploymentCredential', + issuer: employer.uri, + subject: employee.uri, + expirationDate: '2023-09-30T12:34:56Z', + data: { + position: 'Software Developer', + startDate: '2023-04-01T12:34:56Z', + employmentStatus: 'Contractor', + }, + }); + // :snippet-end: + + // :snippet-start: signEmploymentCredential + const vc_jwt_employment = await vc.sign({ did: employer }); + // :snippet-end: + + expect(vc).toBeDefined(); + expect.soft(vc).toHaveProperty('type', 'EmploymentCredential'); + expect.soft(vc).toHaveProperty('issuer', employer.uri); + expect.soft(vc).toHaveProperty('subject', employee.uri); + expect.soft(vc.vcDataModel).toHaveProperty('id'); + expect + .soft(vc.vcDataModel) + .toHaveProperty('expirationDate', '2023-09-30T12:34:56Z'); + expect.soft(vc.vcDataModel.credentialSubject).toHaveProperty('id'); + expect + .soft(vc.vcDataModel.credentialSubject) + .toHaveProperty('position', 'Software Developer'); + expect + .soft(vc.vcDataModel.credentialSubject) + .toHaveProperty('startDate', '2023-04-01T12:34:56Z'); + expect + .soft(vc.vcDataModel.credentialSubject) + .toHaveProperty('employmentStatus', 'Contractor'); + + expect(vc_jwt_employment).toBeDefined(); + expect(vc_jwt_employment).toMatch( + /^[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+$/, + ); + }); +}); diff --git a/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/verify-vc.test.js b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/verify-vc.test.js new file mode 100644 index 000000000..d8249a044 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/test/verifiable-credentials/verify-vc.test.js @@ -0,0 +1,247 @@ +import { test, expect, describe, vi } from 'vitest'; +import { VerifiableCredential, PresentationExchange, VerifiablePresentation } from '@web5/credentials'; +import { DidDht } from '@web5/dids'; + +const expectedVerificationResults = [ + { + "jwt":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRW1wbG95bWVudENyZWRlbnRpYWwiXSwiaWQiOiJ1cm46dXVpZDpiNzJmNTM5Zi1mZjcyLTRhNDYtYmIyOS1iYWI4ZGVjYjMxZTYiLCJpc3N1ZXIiOiJkaWQ6ZGh0OmJvNGRtYXdlNGI2ajFyN203cGZ5bmVoYmg2bzViemE4aTgxZTVmOWVxbXI0YWdmb3lyeW8iLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA3LTEwVDE4OjQ4OjIyWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmRodDpqeWtjNzFvZzMzbWdlejR0eGZwajk4aXpwY205eXN5dTMxcjVoZDQ2YmVmN2I5NHpoaGR5IiwiZW1wbG95bWVudFN0YXR1cyI6ImVtcGxveWVkIn19LCJuYmYiOjE3MjA2MzczMDIsImp0aSI6InVybjp1dWlkOmI3MmY1MzlmLWZmNzItNGE0Ni1iYjI5LWJhYjhkZWNiMzFlNiIsImlzcyI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byIsInN1YiI6ImRpZDpkaHQ6anlrYzcxb2czM21nZXo0dHhmcGo5OGl6cGNtOXlzeXUzMXI1aGQ0NmJlZjdiOTR6aGhkeSIsImlhdCI6MTcyMDYzNzMwMn0.sE1JKPNeONA4eSGEl_xzyiqZHuHaXtJ4LRqppNPmLJbJdxzD3FnajQAd8Co1lCoHkD_LoQxdkqfPTY5Os1rdCQ","isValid":true, + "error":null + }, + { + "jwt":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiUElJQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjBiMjAxMTA1LWQyZGUtNDhiYi04ZjVjLTUwZGEwNmZmZjc2NSIsImlzc3VlciI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byIsImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDctMTBUMTg6NDg6MjJaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZGh0Omp5a2M3MW9nMzNtZ2V6NHR4ZnBqOThpenBjbTl5c3l1MzFyNWhkNDZiZWY3Yjk0emhoZHkiLCJuYW1lIjoiQWxpY2UgU21pdGgiLCJkYXRlT2ZCaXJ0aCI6IjEyLzIxLzIwMDEifX0sIm5iZiI6MTcyMDYzNzMwMiwianRpIjoidXJuOnV1aWQ6MGIyMDExMDUtZDJkZS00OGJiLThmNWMtNTBkYTA2ZmZmNzY1IiwiaXNzIjoiZGlkOmRodDpibzRkbWF3ZTRiNmoxcjdtN3BmeW5laGJoNm81YnphOGk4MWU1ZjllcW1yNGFnZm95cnlvIiwic3ViIjoiZGlkOmRodDpqeWtjNzFvZzMzbWdlejR0eGZwajk4aXpwY205eXN5dTMxcjVoZDQ2YmVmN2I5NHpoaGR5IiwiaWF0IjoxNzIwNjM3MzAyfQ.y2h7DH3EiwDBgh6RE9ICRbyJ1JWHN3jeiEF_zmgh4sTKON756-9Dx1GpOmt5eOgBze_anWdgYUg13ol72f2WDg", + "isValid":true, + "error":null + } +]; + +const expectedEvaluationResults = { + areRequiredCredentialsPresent: 'info', + verifiableCredential: [ + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRW1wbG95bWVudENyZWRlbnRpYWwiXSwiaWQiOiJ1cm46dXVpZDpiNzJmNTM5Zi1mZjcyLTRhNDYtYmIyOS1iYWI4ZGVjYjMxZTYiLCJpc3N1ZXIiOiJkaWQ6ZGh0OmJvNGRtYXdlNGI2ajFyN203cGZ5bmVoYmg2bzViemE4aTgxZTVmOWVxbXI0YWdmb3lyeW8iLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA3LTEwVDE4OjQ4OjIyWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmRodDpqeWtjNzFvZzMzbWdlejR0eGZwajk4aXpwY205eXN5dTMxcjVoZDQ2YmVmN2I5NHpoaGR5IiwiZW1wbG95bWVudFN0YXR1cyI6ImVtcGxveWVkIn19LCJuYmYiOjE3MjA2MzczMDIsImp0aSI6InVybjp1dWlkOmI3MmY1MzlmLWZmNzItNGE0Ni1iYjI5LWJhYjhkZWNiMzFlNiIsImlzcyI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byIsInN1YiI6ImRpZDpkaHQ6anlrYzcxb2czM21nZXo0dHhmcGo5OGl6cGNtOXlzeXUzMXI1aGQ0NmJlZjdiOTR6aGhkeSIsImlhdCI6MTcyMDYzNzMwMn0.sE1JKPNeONA4eSGEl_xzyiqZHuHaXtJ4LRqppNPmLJbJdxzD3FnajQAd8Co1lCoHkD_LoQxdkqfPTY5Os1rdCQ', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiUElJQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjBiMjAxMTA1LWQyZGUtNDhiYi04ZjVjLTUwZGEwNmZmZjc2NSIsImlzc3VlciI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byIsImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDctMTBUMTg6NDg6MjJaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZGh0Omp5a2M3MW9nMzNtZ2V6NHR4ZnBqOThpenBjbTl5c3l1MzFyNWhkNDZiZWY3Yjk0emhoZHkiLCJuYW1lIjoiQWxpY2UgU21pdGgiLCJkYXRlT2ZCaXJ0aCI6IjEyLzIxLzIwMDEifX0sIm5iZiI6MTcyMDYzNzMwMiwianRpIjoidXJuOnV1aWQ6MGIyMDExMDUtZDJkZS00OGJiLThmNWMtNTBkYTA2ZmZmNzY1IiwiaXNzIjoiZGlkOmRodDpibzRkbWF3ZTRiNmoxcjdtN3BmeW5laGJoNm81YnphOGk4MWU1ZjllcW1yNGFnZm95cnlvIiwic3ViIjoiZGlkOmRodDpqeWtjNzFvZzMzbWdlejR0eGZwajk4aXpwY205eXN5dTMxcjVoZDQ2YmVmN2I5NHpoaGR5IiwiaWF0IjoxNzIwNjM3MzAyfQ.y2h7DH3EiwDBgh6RE9ICRbyJ1JWHN3jeiEF_zmgh4sTKON756-9Dx1GpOmt5eOgBze_anWdgYUg13ol72f2WDg', + ], + warnings: [], + errors: [], + value: { + id: 'bPLV_jMdN5XJengbX4M-l', + definition_id: 'presDefIdloanAppVerification123', + descriptor_map: [ + { + id: 'employmentVerification', + format: 'jwt_vc', + path: '$.verifiableCredential[0]', + }, + { + id: 'dobVerification', + format: 'jwt_vc', + path: '$.verifiableCredential[1]', + }, + { + id: 'nameVerification', + format: 'jwt_vc', + path: '$.verifiableCredential[1]', + }, + ], + }, +}; + +const presentationDefinition = { + 'id': 'presDefIdloanAppVerification123', + 'name': 'Loan Application Employment Verification', + 'purpose': 'To verify applicant’s employment, date of birth, and name', + 'input_descriptors': [ + // Employment Verification + { + 'id': 'employmentVerification', + 'purpose': 'Confirm current employment status', + 'constraints': { + 'fields': [ + { + 'path': ['$.credentialSubject.employmentStatus'], + 'filter': { + 'type': 'string', + 'pattern': 'employed' + } + } + ] + } + }, + // Date of Birth Verification + { + 'id': 'dobVerification', + 'purpose': 'Confirm the applicant’s date of birth', + 'constraints': { + 'fields': [ + { + 'path': ['$.credentialSubject.dateOfBirth'], + 'filter': { + 'type': 'string', + 'format': 'date' + } + } + ] + } + }, + // Name Verification + { + 'id': 'nameVerification', + 'purpose': 'Confirm the applicant’s legal name', + 'constraints': { + 'fields': [ + { + 'path': ['$.credentialSubject.name'], + 'filter': { + 'type': 'string' + } + } + ] + } + } + ] +}; + +const presentationResult = { + "presentation": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://identity.foundation/presentation-exchange/submission/v1" + ], + "type": [ + "VerifiablePresentation", + "PresentationSubmission" + ], + "presentation_submission": { + "id": "bPLV_jMdN5XJengbX4M-l", + "definition_id": "presDefIdloanAppVerification123", + "descriptor_map": [ + { + "id": "employmentVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[0]" + }, + { + "id": "dobVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[1]" + }, + { + "id": "nameVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[1]" + } + ] + }, + "verifiableCredential": [ + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRW1wbG95bWVudENyZWRlbnRpYWwiXSwiaWQiOiJ1cm46dXVpZDpiNzJmNTM5Zi1mZjcyLTRhNDYtYmIyOS1iYWI4ZGVjYjMxZTYiLCJpc3N1ZXIiOiJkaWQ6ZGh0OmJvNGRtYXdlNGI2ajFyN203cGZ5bmVoYmg2bzViemE4aTgxZTVmOWVxbXI0YWdmb3lyeW8iLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA3LTEwVDE4OjQ4OjIyWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmRodDpqeWtjNzFvZzMzbWdlejR0eGZwajk4aXpwY205eXN5dTMxcjVoZDQ2YmVmN2I5NHpoaGR5IiwiZW1wbG95bWVudFN0YXR1cyI6ImVtcGxveWVkIn19LCJuYmYiOjE3MjA2MzczMDIsImp0aSI6InVybjp1dWlkOmI3MmY1MzlmLWZmNzItNGE0Ni1iYjI5LWJhYjhkZWNiMzFlNiIsImlzcyI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byIsInN1YiI6ImRpZDpkaHQ6anlrYzcxb2czM21nZXo0dHhmcGo5OGl6cGNtOXlzeXUzMXI1aGQ0NmJlZjdiOTR6aGhkeSIsImlhdCI6MTcyMDYzNzMwMn0.sE1JKPNeONA4eSGEl_xzyiqZHuHaXtJ4LRqppNPmLJbJdxzD3FnajQAd8Co1lCoHkD_LoQxdkqfPTY5Os1rdCQ', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiUElJQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjBiMjAxMTA1LWQyZGUtNDhiYi04ZjVjLTUwZGEwNmZmZjc2NSIsImlzc3VlciI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byIsImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDctMTBUMTg6NDg6MjJaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZGh0Omp5a2M3MW9nMzNtZ2V6NHR4ZnBqOThpenBjbTl5c3l1MzFyNWhkNDZiZWY3Yjk0emhoZHkiLCJuYW1lIjoiQWxpY2UgU21pdGgiLCJkYXRlT2ZCaXJ0aCI6IjEyLzIxLzIwMDEifX0sIm5iZiI6MTcyMDYzNzMwMiwianRpIjoidXJuOnV1aWQ6MGIyMDExMDUtZDJkZS00OGJiLThmNWMtNTBkYTA2ZmZmNzY1IiwiaXNzIjoiZGlkOmRodDpibzRkbWF3ZTRiNmoxcjdtN3BmeW5laGJoNm81YnphOGk4MWU1ZjllcW1yNGFnZm95cnlvIiwic3ViIjoiZGlkOmRodDpqeWtjNzFvZzMzbWdlejR0eGZwajk4aXpwY205eXN5dTMxcjVoZDQ2YmVmN2I5NHpoaGR5IiwiaWF0IjoxNzIwNjM3MzAyfQ.y2h7DH3EiwDBgh6RE9ICRbyJ1JWHN3jeiEF_zmgh4sTKON756-9Dx1GpOmt5eOgBze_anWdgYUg13ol72f2WDg' + ] + }, + "presentationSubmissionLocation": 1, + "presentationSubmission": { + "id": "bPLV_jMdN5XJengbX4M-l", + "definition_id": "presDefIdloanAppVerification123", + "descriptor_map": [ + { + "id": "employmentVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[0]" + }, + { + "id": "dobVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[1]" + }, + { + "id": "nameVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[1]" + } + ] + } +}; + + +describe('verify presentation of credentials', () => { + test('verifyVCs verifies a verifiable credential', async () => { + // :snippet-start: verifyVcs + //highlight-next-line + const vcJwtArray = presentationResult.presentation.verifiableCredential; + const verificationResults = []; + let errorsFound = false; + + for (let vcJwt of vcJwtArray) { + try { + //highlight-next-line + await VerifiableCredential.verify({ vcJwt: vcJwt }); + + // No error thrown, verification successful + verificationResults.push({ + jwt: vcJwt, + isValid: true, + error: null + }); + } catch (error) { + errorsFound = true; + verificationResults.push({ + jwt: vcJwt, + isValid: false, + error: error.message + }); + } + } + // :snippet-end: + + // :snippet-start: errorsFound + if (errorsFound) { + verificationResults.forEach(result => { + if (!result.isValid) { + console.error(`Verification Error: ${result.error} for JWT ${result.jwt}`); + } + }); + } + else { + //no errors are found. continue processing + } + // :snippet-end: + expect(verificationResults).toEqual(expectedVerificationResults); + }); + + test('verifyVpJs signs and verifies a verifiable presenation', async () => { + const vcJwt1 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRW1wbG95bWVudENyZWRlbnRpYWwiXSwiaWQiOiJ1cm46dXVpZDpiNzJmNTM5Zi1mZjcyLTRhNDYtYmIyOS1iYWI4ZGVjYjMxZTYiLCJpc3N1ZXIiOiJkaWQ6ZGh0OmJvNGRtYXdlNGI2ajFyN203cGZ5bmVoYmg2bzViemE4aTgxZTVmOWVxbXI0YWdmb3lyeW8iLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA3LTEwVDE4OjQ4OjIyWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmRodDpqeWtjNzFvZzMzbWdlejR0eGZwajk4aXpwY205eXN5dTMxcjVoZDQ2YmVmN2I5NHpoaGR5IiwiZW1wbG95bWVudFN0YXR1cyI6ImVtcGxveWVkIn19LCJuYmYiOjE3MjA2MzczMDIsImp0aSI6InVybjp1dWlkOmI3MmY1MzlmLWZmNzItNGE0Ni1iYjI5LWJhYjhkZWNiMzFlNiIsImlzcyI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byIsInN1YiI6ImRpZDpkaHQ6anlrYzcxb2czM21nZXo0dHhmcGo5OGl6cGNtOXlzeXUzMXI1aGQ0NmJlZjdiOTR6aGhkeSIsImlhdCI6MTcyMDYzNzMwMn0.sE1JKPNeONA4eSGEl_xzyiqZHuHaXtJ4LRqppNPmLJbJdxzD3FnajQAd8Co1lCoHkD_LoQxdkqfPTY5Os1rdCQ' + const vcJwt2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiUElJQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjBiMjAxMTA1LWQyZGUtNDhiYi04ZjVjLTUwZGEwNmZmZjc2NSIsImlzc3VlciI6ImRpZDpkaHQ6Ym80ZG1hd2U0YjZqMXI3bTdwZnluZWhiaDZvNWJ6YThpODFlNWY5ZXFtcjRhZ2ZveXJ5byIsImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDctMTBUMTg6NDg6MjJaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZGh0Omp5a2M3MW9nMzNtZ2V6NHR4ZnBqOThpenBjbTl5c3l1MzFyNWhkNDZiZWY3Yjk0emhoZHkiLCJuYW1lIjoiQWxpY2UgU21pdGgiLCJkYXRlT2ZCaXJ0aCI6IjEyLzIxLzIwMDEifX0sIm5iZiI6MTcyMDYzNzMwMiwianRpIjoidXJuOnV1aWQ6MGIyMDExMDUtZDJkZS00OGJiLThmNWMtNTBkYTA2ZmZmNzY1IiwiaXNzIjoiZGlkOmRodDpibzRkbWF3ZTRiNmoxcjdtN3BmeW5laGJoNm81YnphOGk4MWU1ZjllcW1yNGFnZm95cnlvIiwic3ViIjoiZGlkOmRodDpqeWtjNzFvZzMzbWdlejR0eGZwajk4aXpwY205eXN5dTMxcjVoZDQ2YmVmN2I5NHpoaGR5IiwiaWF0IjoxNzIwNjM3MzAyfQ.y2h7DH3EiwDBgh6RE9ICRbyJ1JWHN3jeiEF_zmgh4sTKON756-9Dx1GpOmt5eOgBze_anWdgYUg13ol72f2WDg' + + const holderDid = await DidDht.create(); + + const vp = await VerifiablePresentation.create({ + holder: holderDid.uri, + vcJwts: [vcJwt1, vcJwt2], + additionalData: { presentationResult } + }); + + const logSpy = vi.spyOn(console, 'log'); + const vpJwt = await vp.sign({ did: holderDid }); + // :snippet-start: verifyVpJs + try { + await VerifiablePresentation.verify({ vpJwt: vpJwt }); + } catch (err) { + console.log('\nVP Verification failed: ' + err.message + '\n'); + } + + // :snippet-end: + expect(logSpy).not.toHaveBeenCalled(); + logSpy.mockRestore(); + + }); + + test('PresentationExchange.evaluatePresentation() evaluates presentation against definition', async () => { + // :snippet-start: evaluatePresentation + const evaluationResults = PresentationExchange.evaluatePresentation({ + presentationDefinition: presentationDefinition, + presentation: presentationResult.presentation + }); + // :snippet-end: + expect(evaluationResults).toEqual(expectedEvaluationResults); + + }); + +}); diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/_category_.json b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/_category_.json new file mode 100644 index 000000000..4a5b1dd65 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "VCs", + "position": 3 +} \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/fan-club-vc.mdx b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/fan-club-vc.mdx new file mode 100644 index 000000000..f34839978 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/fan-club-vc.mdx @@ -0,0 +1,459 @@ +--- +title: VC Workflow +hide_title: true +sidebar_position: 8 +--- + + + +# Verifiable Credentials for Fan Club Membership + +Alice is a long-time fan of one of the biggest pop stars in the world! +In this tutorial, we'll learn how to use [verifiable credentials](/docs/web5/verifiable-credentials/what-are-vcs) (VC) to ensure Alice is recognized as an official member of the exclusive Swifties Fan Club. + +![Fan Club Membership](/img/vc-fanclub.png) + +## Setting the Stage +Before we start issuing and verifying credentials, let's set up our environment by installing the and packages: + + + +### Import packages + +After installing the packages, we'll import them into our project as shown below: + + + + +## The Cast +There are a few characters involved in our story: + +* **The Issuer (Fan Club)**: This is the entity (us, in this case) responsible for issuing the verifiable credentials. Think of it as the guardian of the Swifties Fan Club. +* **The Holder (Alice)**: A dedicated fan who wants to join the club. She will be the recipient of the credential, and she uses her Wallet app to show her credentials. +* **The Verifier (Swag Supplier)**: This company provides free swag to Swiftie Fan Club members, but they have to verify that the person is indeed an official member. + +## Act 1: Issuing the Credential + +In this section, we'll walk through all of the steps required to create and issue a verifiable credential. + +### Create DIDs +As a prerequisite, both the Fan Club and Alice need [decentralized identitifiers](/docs/web5/decentralized-identifiers/what-are-dids). +This is because verifiable credentials use DIDs to identify issuers and holders. + +If you need to create DIDs, you can do so with the package: + + + +### Model Credential + +Now, let's create the **SwiftiesFanClub** credential. +We'll first model it with a class with some basic properties, `level` and `legit`: + + + +:::note +Note that credentials can be customized to fit various needs and scenarios. In our example, we used `level` and `legit`, but you could add more properties like `memberSince`, `favoriteAlbum`, or any other relevant data that suits your use case. +::: + +### Create Credential + +Next we can create the verifiable credential with an instance of the class: + + + + + +### Credential Properties + +Let's break down the `VerifiableCredential.create()` method to understand what each property means and how it contributes to our credential: + +* **type**: The type of credential being created. In our case, it's `SwiftiesFanClub`. This helps in categorizing and understanding the purpose of the credential. + +* **issuer**: The DID of the issuer of the credential. It's crucial for establishing trust and verifying the origin of the credential. In our narrative, the issuer is the fan club itself. + +* **subject**: The DID of the person or entity the credential is about - in our case, Alice, the fan. + +* **data**: An instance of our `SwiftiesFanClub` class. This instance contains the specific information or claims about the subject, such as their fan level and legitimacy as a fan. + + +### Credential JSON + +`VerifiableCredential.create()` returns a VC as a JSON object: + + + + +### Sign Credential + +Last but not least, we must cryptographically sign the VC to make it official: + + + + +Signing the credential returns a [VC JSON Web Token](/docs/web5/verifiable-credentials/jwt-to-vc#what-is-a-jwt), ideal for secure transmission of the credential. + + +## Act 2: Presentation Exchange +Now that Alice has her credential, she'd like to present it in order to get free merchandise. She can do this via a **presentation exchange**. + +A presentation exchange is the process where the holder of VCs presents them to a verifier in order to prove certain claims or information. + +### Components of Presentation Exchange + +* **Holder**: The individual or entity that possesses one or more verifiable credentials. In our Swifties Fan Club example, Alice is the holder. + +* **Verifier**: The party requesting to verify the information contained in the holder's credentials. This could be a website, service, or another individual who needs proof of certain claims. In our example, this would be the Swag Supplier. + +* **Presentation**: A package of information, compiled by the holder, that typically includes the verifiable credentials and possibly additional data required by the verifier. This presentation is what the holder submits to the verifier. + +* **Presentation Definition**: A set of criteria defined by the verifier detailing what they need to see in the presentation. This can include specific types of credentials, claims, or formats. + +### Create Presentation Definition + +To kick off the presentation exchange, the Swag Supplier (verifier) will send a request to Alice, which includes a presentation definition, specifying what credentials or claims are needed. + + +To create and validate this presentation definition, the verifier first needs to import packages from the library. + + + +Here's how the verifier can create the presentation definition, and validate that it is properly formed: + + + + + + + +### Satisfy Presentation Definition + +Once a presentation definition is presented to Alice, she'd want to make sure that the credentials she has satifies the requirements before submitting them. +This can be done by passing the JWT(s) and the presentation definition to `satisfiesPresentationDefinition()` method. +Typically this step is done by the app that Alice is using to store and present her credentials: + + + +### Create Presentation + +Once Alice's app is confident that her credential satisfies the presentation definition, the presentation would be created: + + + +The result of the presentation: + + + + +## Act 3: Verifying the Credential + +After receiving the credential, the verifier will want to verify it to ensure that it is valid and has not been tampered with. +They can do this with `VerifiableCredential.verify()`. If verification is unsuccessful, an error will be thrown: + + + + +### Parse JWT into VC + +The signed VC JWT is a secure representation of the credential, ideal for transmission or storage. + +However, the verifier will likely need to inspect or use the data within the credential. +They can use `parseJwt()` to convert the JWT back into a `VerifiableCredential` object. + + + + +This returns: + + + + +Given Alice's verified credential, she's all set to receive her free merch for being a proud member of the Swifties Fan Club! \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/jwt-to-vc.mdx b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/jwt-to-vc.mdx new file mode 100644 index 000000000..9d09b92e0 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/jwt-to-vc.mdx @@ -0,0 +1,93 @@ +--- +title: Parse JWT into VC +hide_title: true +sidebar_position: 6 +--- + + + +# Parse JWT into VC + +When [signing a Verifiable Credential (VC)](/docs/web5/verifiable-credentials/vc-issuance#sign-vc), a [JSON Web Token (JWT)](https://jwt.io/) is returned. +This guide demonstrates how to parse the JWT to get the corresponding VC object. + +## What is a JWT +A VC JWT is a secure URL-safe string representation of a credential, ideal for storage or transmission between two parties. +JWT format is used to encode the credentials, making them both secure and easily transmissible. + +**Example JWT** + + + + +## Decoding JWT + +Once a VC JWT is received, the credential verifier will likely need to inspect or use the data that's contained within the credential. +They can use `parseJwt()` to decode the JWT and convert it back into a `VerifiableCredential` object. +This object format is more convenient for accessing and manipulating the credential's data within their application. + + + +This returns the VC in JSON: + + \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/presentation-definition.mdx b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/presentation-definition.mdx new file mode 100644 index 000000000..c33610426 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/presentation-definition.mdx @@ -0,0 +1,543 @@ +--- +sidebar_position: 4 +hide_title: true +title: Presentation Definition +--- + + + +# Presentation Definition + +If you accept verifiable credentials, you'll want to define what sort of information is necessary to meet the requirements. +This specification of expected claims is a **Presentation Definition** (PD). + +For example, assume an Employer accepts verifiable credentials as part of a job application. +The presentation definition would specify exactly what the Employer will look for in those credentials. +This could include things like proof of a degree, past employment history, and maybe a specific certification. + +They're essentially saying, "To consider your application, we need to see these specific pieces of information." + +## Designing a Presentation Definition + +An employer decides that an applicant must provide proof of: + +* employment history +* degree in a field related to Engineering, Computer Science, or Security +* Certified Ethical Hacker certification issued by a specific trusted organization + + +### Example Presentation Definition +To represent these requirements, the employer creates the PD. + +
+Presentation Definition + +```js +{ + "id": "PD_JobApplication_123456", + "name": "Credentials Verification for Ethical Hacker Job Application", + "purpose": "To verify the applicant's employment history, and either their academic degree or Certified Ethical Hacker certification", + "input_descriptors": [ + { + "id": "employmentHistoryVerification", + "name": "Employment History", + "purpose": "Verify the applicant's previous employment experiences", + "constraints": { + "fields": [ + { + "path": ["$.type[*]"], + "filter": { + "type": "string", + "pattern": "Employment" + } + } + ] + } + }, + { + "id": "degreeVerification", + "name": "Degree", + "purpose": "Confirm the applicant's academic qualification", + "constraints": { + "fields": [ + { + "path": ["$.credentialSubject.degree.type"], + "filter": { + "type": "string", + "pattern": "(Engineering|Computer|Cyber|Security)" + } + } + ] + } + }, + { + "id": "CEH_CertificationVerification", + "name": "Certified Ethical Hacker Certification", + "purpose": "Confirm the applicant holds a Certified Ethical Hacker certification", + "constraints": { + "fields": [ + { + "path": ["$.credentialSubject.certifications[*].name"], + "filter": { + "type": "string", + "pattern": "Certified Ethical Hacker" + } + }, + { + "path": ["$.issuer"], + "filter": { + "type": "string", + "const": "did:example:123456789abcdefghi" + } + } + ] + } + } + ] +} +``` + +
+ + +### Properties + +The following are the top-level properties of a PD: + +* [id](#id): A required unique identifier that distinguishes this PD from others +* [name](#name-and-purpose): An optional, human-friendly name for easier identification of the PD +* [purpose](#name-and-purpose): An optional description outlining why the information requested by the PD is needed +* [format](https://identity.foundation/presentation-exchange/spec/v1.0.0/#claim-format-designations): The optional technical format requirements for the data to be submitted. *Example: \{ "jwt_vc": \{"alg": ["ES256K", "ES384"]} }* +* [input_descriptors](#input_selectors): This is the most important property of the PD. It details the exact data requirements + +:::info +For more in-depth information on the schema and options, refer to the [Presentation Definition spec](https://identity.foundation/presentation-exchange/spec/v1.0.0/#json-schema-2). +::: + +### Breakdown of PD + +Let's break down the creation of each part: + +#### id + +To keep the IDs unique, the Employer uses a format within their system: 'PD_JobApplication' followed by the ID of the job req. This allows them to easily match PDs with their open roles. + +```js +{ + "id": "PD_JobApplication_123456", +} +``` + +#### name and purpose + +The `name` and `purpose` are clear and human-readable, as they may be presented to the applicant when they need to decide if to share their credentials with the employer. + +```js +{ + "id": "PD_JobApplication_123456", + //highlight-start + "name": "Credentials Verification for Ethical Hacker Job Application", + "purpose": "To verify the applicant's employment history, and either their academic degree or Certified Ethical Hacker certification", + //highlight-end +} +``` + +#### input_selectors + +The `input_selectors` property is an array of required claims and specifications on exactly how they will be evaluated. + +```js +{ + "input_descriptors": [ + + ] +} +``` + +In this first input descriptor, the Employer provides an `id` for this descriptor. + +```js +{ + "input_descriptors": [ + { + "id": "employmentHistoryVerification", + } + ] +} +``` + + +The `name` and `purpose` should be human-readable as they may be shown to the user. + +```js +{ + "input_descriptors": [ + { + "id": "employmentHistoryVerification", + //highlight-start + "name": "Employment History", + "purpose": "Verify the applicant's previous employment experiences", + //highlight-end + } + ] +} +``` + +#### constraints +The `constraints` object contains the exact test that the VC will need to pass in order to meet the requirement. + +```js +{ + "input_descriptors": [ + { + "id": "employmentHistoryVerification", + "name": "Employment History", + "purpose": "Verify the applicant's previous employment experiences", + //highlight-start + "constraints": { + + } + //highlight-end + }, + ] +} +``` + +#### fields +Under `constraints` is an array of `fields` objects, each object representing a specific piece of data that the Employer is interested in. Each field can have its own constraints. + +```js +{ + "input_descriptors": [ + { + "id": "employmentHistoryVerification", + "name": "Employment History", + "purpose": "Verify the applicant's previous employment experiences", + "constraints": { + //highlight-start + "fields": [ + + ] + //highlight-end + } + }, + ] +} +``` + +#### path +The `path` key specifies the location within the VC where the data is expected to be found. +Here, `$.type[*]` indicates that the Employer will verify the `type` attribute of the submitted VCs to see if any of its values match the `filter`. + +```js +{ + "input_descriptors": [ + { + "id": "employmentHistoryVerification", + "name": "Employment History", + "purpose": "Verify the applicant's previous employment experiences", + "constraints": { + "fields": [ + { + //highlight-start + "path": ["$.type[*]"], + "filter": { + + } + //highlight-end + } + ] + } + }, + ] +} +``` + +#### filter + +The `filter` provides the specific conditions that the data in the specified path must satisfy. +Here, it's saying that the expected data `type` of the field is a string, and the `pattern` property indicates that the value should match the provided regular expression. + +In this case, the Employer expects the applicant to submit a VC whose type includes the word "Employment". + +```js +{ + "input_descriptors": [ + { + "id": "employmentHistoryVerification", + "name": "Employment History", + "purpose": "Verify the applicant's previous employment experiences", + "constraints": { + "fields": [ + { + "path": ["$.type[*]"], + //highlight-start + "filter": { + "type": "string", + "pattern": "Employment" + } + //highlight-end + } + ] + } + }, + ] +} +``` + +:::info +The `filter` property offers a lot of flexibility. Refer to the [Presentation Definition spec](https://identity.foundation/presentation-exchange/spec/v1.0.0/#json-schema-2) for more examples. +::: + + +The next input descriptor describes that if a degree VC is provided, it must be a degree with one of the following terms in the title: Engineering, Computer, Cyber, or Security. + +If someone has a degree in Biology, for example, that won't satisfy this requirement. + +```js + { + "id": "degreeVerification", + "name": "Degree", + "purpose": "Confirm the applicant's academic qualification", + "constraints": { + "fields": [ + { + //highlight-start + "path": ["$.credentialSubject.degree.type"], + "filter": { + "type": "string", + "pattern": "(Engineering|Computer|Cyber|Security)" + } + //highlight-end + } + ] + } + }, +``` + +This Employer also requires a Ethical Hacking certification from a specific reputable institution. + +For this credential, there's two fields that the Employer wants to add constraints for: the name of the certification, and the organization that issued the credential. + +Notice the `filter` for the certification's name uses `pattern` so that it checks the name with a regular expression. +This gives flexibility in case the certification is for "Advanced Certified Ethical Hacker" or "Certified Ethical Hacker II", etc. + +But the `filter` for the issuer uses `const` to indicate that the issuer's DID must match this value exactly. + +```js + { + "id": "CEH_CertificationVerification", + "name": "Certified Ethical Hacker Certification", + "purpose": "Confirm the applicant holds a Certified Ethical Hacker certification", + //highlight-start + "constraints": { + "fields": [ + { + "path": ["$.credentialSubject.certifications[*].name"], + "filter": { + "type": "string", + "pattern": "Certified Ethical Hacker" + } + }, + { + "path": ["$.issuer"], + "filter": { + "type": "string", + "const": "did:example:123456789abcdefghi" + } + } + ] + } + //highlight-end + } +``` + +For the issuer, the Employer only accepts VCs from the top trusted source who issues these certifications, so they enter the organization's DID as a constraint. +If an applicant provides a Ethical Hacker Certification that was issued by anyone other than this organization, that VC will not meet the requirements. + +```js +//highlight-start + { + "path": ["$.issuer"], + "filter": { + "type": "string", + "const": "did:example:123456789abcdefghi" + } + } +//highlight-end +``` + +## Validate the Presentation Definition + +Once the PD is created, you can validate it to ensure there are no errors in its design. + +1. import classes needed for a Presentation Exchange + + + +2. call `validateDefinition()` +>This assumes the [presentation definition](/docs/web5/verifiable-credentials/presentation-definition#example-presentation-definition) is assigned to variable `pd` + + + +If there are errors with the PD, an error will be thrown: + +``` +This is not a valid PresentationDefinition +``` + +## Example Credentials + +For your reference, below are example VCs that could be used to satisfy the Employment Application PD. + +
+Employment at Tech Inc + +```js +{ + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": [ + "VerifiableCredential", + "EmploymentCredential" + ], + "id": "urn:uuid:d377379b-3699-44b7-b15c-0831a849ff7c", + "issuer": "did:example:techinc", + "issuanceDate": "2024-01-05T17:43:45Z", + "credentialSubject": { + "id": "did:example:alice", + "employmentHistory": { + "type": "Employment", + "position": "Software Engineer", + "company": "Tech Inc.", + "startDate": "2019-01-01", + "endDate": "2021-12-31" + } + } + }, + "iss": "did:example:techinc", + "sub": "did:example:alice" +} +``` + +
+ +
+Employment at Innovate LLC + +```js +{ + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": [ + "VerifiableCredential", + "EmploymentCredential" + ], + "id": "urn:uuid:2d80375b-834c-4817-a79b-33f1fe3cd80a", + "issuer": "did:example:innovatellc", + "issuanceDate": "2024-01-05T17:43:45Z", + "credentialSubject": { + "id": "did:example:alice", + "employmentHistory": { + "type": "Employment", + "position": "Project Manager", + "company": "Innovate LLC", + "startDate": "2022-01-03", + } + } + }, + "iss": "did:example:innovatellc", + "sub": "did:example:alice" +} +``` + +
+ + +
+Computer Science Degree + +```js +{ + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": [ + "VerifiableCredential", + "EducationalCredential" + ], + "id": "urn:uuid:a09b7d2d-c7ac-42de-9230-05e7fd894692", + "issuer": "did:example:stateuniversity", + "issuanceDate": "2024-01-05T17:43:45Z", + "credentialSubject": { + "id": "did:example:alice", + "degree": { + "type": "Bachelor of Computer Science", + "university": "State University", + "graduationYear": "2018" + } + } + }, + "iss": "did:example:stateuniversity", + "sub": "did:example:alice" +} +``` + +
+ +
+Certified Ethical Hacker + +```js +{ + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": [ + "VerifiableCredential", + "CertificationCredential" + ], + "id": "urn:uuid:7fba00eb-2f47-4113-aea6-4c5b5799f95f", + "issuer": "did:example:123456789abcdefghi", + "issuanceDate": "2023-05-01T16:06:00Z", + "credentialSubject": { + "id": "did:example:alice", + "certifications": [ + { + "name": "Certified Ethical Hacker", + "date": "2023-05-01" + } + ] + } + }, + "iss": "did:example:123456789abcdefghi", + "sub": "did:example:alice" +} +``` + +
diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/presentation-exchange.mdx b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/presentation-exchange.mdx new file mode 100644 index 000000000..ae0143db4 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/presentation-exchange.mdx @@ -0,0 +1,295 @@ +--- +title: Presentation Exchange +hide_title: true +sidebar_position: 5 +--- + + + +# Presentation Exchange + +In this guide, we'll cover the process of presenting [Verifiable Credentials (VCs)](/docs/web5/verifiable-credentials/what-are-vcs) to a verifier. +This process is known as a **Presentation Exchange (PEX)** and involves selecting credentials that satisfy a Presentation Definition, creating a presentation based on that definition, and submitting the presentation to a verifier. +PEX is typically executed by the user's Wallet application, acting on behalf of the user. + +:::note +Before presenting VCs in a PEX, it's important to note that the VCs would have already been [created](/docs/web5/verifiable-credentials/vc-issuance#create-vc), +[signed in a JWT format](/docs/web5/verifiable-credentials/vc-issuance#sign-vc), and [stored](/docs/web5/verifiable-credentials/vcs-in-dwn#storing-vc-in-dwn) +in the user's Wallet application. +::: + +## Presentation Definition + +The entity requesting credentials, aka the verifier, will have defined the presentation requirements using a [Presentation Definition](/docs/web5/verifiable-credentials/presentation-definition). +This definition specifies the criteria that the VCs need to meet. + +Assume a lender has an existing Presentation Definition specifying that applicants must provide VCs to prove their legal name, date of birth, and employment status: + +
+Presentation Definition + + + +
+ +We'll use the Presentation Definition above to present credentials that meet its requirements. + +## Import PresentationExchange + +All methods needed to perform a PEX are in the `PresentationExchange` class. +You can import this class from the `@web5/credentials` package: + + + +## Select Credentials + +A user may possess multiple credentials. +To preserve the user's privacy, when creating a Presentation, you'll want to present only the VCs that match the Presentation Definition requirements. +This is done with the `selectCredentials()` method: + + + +## Satisfy Presentation Definition + +To ensure that the selected credentials collectively satisfy all of the Presentation Definition's requirements, call the `satisfiesPresentationDefinition()` method: + + + +This method will throw an error if all requirements of the Presentation Definition are not satisfied. + +## Create Presentation + +Once you've confirmed that the VCs successfully satisfy the Presentation Definition, you can create a presentation using the `createPresentationFromCredentials()` method: + + + + + + + + +## Validate Presentation Submission + +Before submitting the presentation to a lender, you can validate the Presentation via the `validateSubmission()` method: + + + +:::note +The information regarding `Checked` objects is only applicable to the JavaScript SDK. +::: + + + + + +Each `Checked` object contains: + +- **tag**: the path, `root` of the submission that was checked +- **status**: the result of the check, which is `info` for successful checks or `error` for failed checks +- **message**: shows `ok` if successful, and if unsuccessful this is where you'll find error messages + + +## Verifiable Presentation + +Once the [Presentation](/docs/glossary#verifiable-presentation) is error-free and has passed the validation checks, you can submit the Verifiable Presentation as a signed and encoded JWT to the lender for [verification](/docs/web5/verifiable-credentials/verify-vc#verify-verifiable-presentations). + + diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/revoke-credentials.mdx b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/revoke-credentials.mdx new file mode 100644 index 000000000..9a55e2ff1 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/revoke-credentials.mdx @@ -0,0 +1,159 @@ +--- +title: Revoke Credentials +hide_title: true +sidebar_position: 5 +--- + + +# Revoking Verifiable Credentials + +Revoking verifiable credentials (VCs) is a crucial aspect of maintaining their integrity and trustworthiness. +In this guide, we will explore the mechanisms and best practices for revoking verifiable credentials, ensuring they remain accurate and reliable over time. + +## Create a Status List Credential + +A **Status List Credential** is a specialized type of verifiable credential created by VC issuers to maintain a list of the statuses of other credentials, specifically focusing on those that have been revoked. +This list is hosted online to allow verifiers to quickly determine the validity of a credential. + + + +### Example of Status List Credential + +```js +{ + "vcDataModel": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "https://example.com/credentials/status/1", + "issuer": "did:dht:9yiocuhw16grn1ityjcij3g4qrkwq65oh3x8qptbduybgaymbmoo", + "issuanceDate": "2024-07-26T01:41:06Z", + "credentialSubject": { + "id": "https://example.com/credentials/status/1", + "type": "StatusList2021", + "statusPurpose": "revocation", + "encodedList": "H4sIAAAAAAAAA-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA" + } + } +} +``` + +## Issue a Revocable Credential +Verifiable credentials are immutable, so an issuer cannot change the content of the VC after it is issued. +However, the issuer can include a `credentialStatus` property when issuing the credential. +This links the credential to its entry in the status list, allowing issuers to update its status at any time and verifiers to check the credential's status. + + + +### Properties of credentialStatus + +* **id**: URL that identifies the status information associated with the verifiable credential. It must not be the URL for the status list itself, but rather a unique identifier for the status entry within the status list​. + +* **type**: The type of status entry, which must be `StatusList2021Entry`, indicating it follows the [Status List 2021 standard](https://www.w3.org/community/reports/credentials/CG-FINAL-vc-status-list-2021-20230102/). + +* **statusPurpose**: The purpose of the status entry. Common values are `revocation` for permanently invalidating a credential and `suspension` for temporarily invalidating it. + +* **statusListIndex**: An integer value (expressed as a string) identifying the bit position in the [status list's bitstring](https://www.w3.org/community/reports/credentials/CG-FINAL-vc-status-list-2021-20230102/#bitstring-generation-algorithm) that corresponds to the credential's status. A bit value of 0 means the credential is valid, while a bit value of 1 indicates it has been revoked. This efficient encoding allows for quick status checks by verifiers. + +* **statusListCredential**: URL pointing to the Status List Credential, which contains the bitstring encoding the status of multiple credentials. This URL allows verifiers to retrieve and check the status list. + +### Example of Revocable VC + +```js +{ + "vcDataModel": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StreetCred" + ], + "id": "urn:uuid:fddff3ca-cdee-403c-95d0-70608fc0b3d9", + "issuer": "did:dht:9yiocuhw16grn1ityjcij3g4qrkwq65oh3x8qptbduybgaymbmoo", + "issuanceDate": "2024-07-26T01:41:06Z", + "credentialSubject": { + "id": "did:dht:phaphcrybzdkxttu74nsuu9hyjxe19bzwymn8g18gac7ci1ifuoo", + "streetCred": "high", + "legit": true + }, + "credentialStatus": { + "id": "https://example.com/credentials/status/1#94567", + "type": "StatusList2021Entry", + "statusPurpose": "revocation", + "statusListIndex": "94567", + "statusListCredential": "https://example.com/credentials/status/1" + } + } +} +``` + +## Revoking a VC +To revoke a Verifiable Credential, the issuer must add the credential to a `StatusListCredential`. +To do this, the issuer must create a new `StatusListCredential` with an updated list of their revoked credentials, then upload this updated `StatusListCredential` to the same URL as the previous one. + + + +Note the `encodedList` holds the compressed bitstring. + +### Example of Updated Status List Credential + +```js +{ + "vcDataModel": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "https://example.com/credentials/status/1", + "issuer": "did:dht:9yiocuhw16grn1ityjcij3g4qrkwq65oh3x8qptbduybgaymbmoo", + "issuanceDate": "2024-07-26T01:41:06Z", + "credentialSubject": { + "id": "https://example.com/credentials/status/1", + "type": "StatusList2021", + "statusPurpose": "revocation", + "encodedList": "H4sIAAAAAAAAA-3OMQ0AAAgDsOHfNBp2kZBWQRMAAAAAAAAAAAAAAL6Z6wAAAAAAtQVQdb5gAEAAAA" + } + } +} +``` + +## Check VC Status + +To check the status of a verifiable credential, the verifier can do the following: +1. Parse the presented verifiable credential to get the `credentialStatus` object. +2. Fetch the `StatusListCredential` from the URI in the object to retrieve the credential. +3. Pass the presented credential and the `StatusListCredential` to the `validateCredentialInStatusList()` method as shown below. It will return `true` if revoked. + + + diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/vc-issuance.mdx b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/vc-issuance.mdx new file mode 100644 index 000000000..a977eaad6 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/vc-issuance.mdx @@ -0,0 +1,146 @@ +--- +title: Issue a VC +hide_title: true +sidebar_position: 2 +--- + + + +# Issue a VC + +This guide provides a step-by-step process for issuing a [verifiable credential (VC)](/docs/web5/verifiable-credentials/what-are-vcs), covering everything from installing the SDK to signing the credential. + +
+Prerequisites + +**Install Packages** + + + +**Install Classes** + + + +**Obtain DIDs** + +VCs are issued to and from subjects of [Decentralized Identifiers (DID)](/docs/web5/decentralized-identifiers/what-are-dids). + +- If you don't already have one, you can [create a DID](/docs/web5/decentralized-identifiers/how-to-create-did). +- Also, you'll need the DID of the entity you'll like to issue the VC to. + +
+ +## VC Properties + +Credentials are created in JSON format and contain the following properties: + +- **type:** Type of the credential (e.g. EmploymentCredential, EducationCredential) +- **issuer:** DID of the entity who is issuing the credential +- **subject:** DID of the entity that the credential is being issued to +- **expirationDate:** (optional) The date when the credential expires (in ISO 8601 standard format) +- **data:** JSON containing claims that are being asserted + +## Create VC + +To create the VC, utilize the `VerifiableCredential.create()` method. Pass a JSON object containing the properties as shown below: + + + +This results in: + + + +## Sign VC + +The next step is signing the VC. This cryptographic process, performed by the issuer, ensures the integrity and authenticity of the VC: + + + +Signing the credential returns a [VC JWT](/docs/web5/verifiable-credentials/jwt-to-vc#what-is-a-jwt): + +``` +eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsI... +``` + +Giving this VC JWT to the holder completes the issuance of the credential. + +VC JWTs can be [converted back](/docs/web5/verifiable-credentials/jwt-to-vc#decoding-jwt) to a JSON object by applications that need to engage with the contents of the credential. \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/vcs-in-dwn.mdx b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/vcs-in-dwn.mdx new file mode 100644 index 000000000..7408de8df --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/vcs-in-dwn.mdx @@ -0,0 +1,47 @@ +--- +sidebar_position: 7 +--- + +# VCs in DWNs + +In this guide, we'll cover how to write a [Verifiable Credential (VC)](/docs/web5/verifiable-credentials/what-are-vcs) to a [Decentralized Web Node (DWN)](/docs/web5/decentralized-web-nodes/what-are-dwns), how to retrieve that stored VC by querying the DWN for it, and lastly how to parse the VC into a format that your application can work with. + +## Storing VC in DWN + +To store a VC in a DWN, you need to create a record with the VC data. The VC should already be [created](/docs/web5/verifiable-credentials/vc-issuance#create-vc) and in a [signed JWT format](/docs/web5/verifiable-credentials/vc-issuance#sign-vc). This is what that would look like: + +```js +const { record } = await web5.dwn.records.create({ + data: signedVcJwt, + message: { + schema: 'EmploymentCredential', + dataFormat: 'application/vc+jwt', + }, +}); + +// (optional) immediately send record to users remote DWNs +const { status } = await record.send(userDid); +``` + +`signedVcJwt` represents a signed VC JWT. The `schema` property should be the same as the VC type, and `dataFormat` specifies the format of the VC which is `application/vc+jwt`. + +## Querying VC From DWN + +To retrieve a stored VC from a DWN, you can use the `records.query()` method: + +```js +const response = await web5.dwn.records.query({ + from: userDid, + message: { + filter: { + schema: 'EmploymentCredential', + dataFormat: 'application/vc+jwt', + }, + }, +}); + +const signedVcJwt = await response[0].data.text(); +``` + +This will access the first record matching the filters you specified, `schema` and `dataFormat`. +Keep in mind that this will return a JWT string, which can be [converted to a readable JSON object](/docs/web5/verifiable-credentials/jwt-to-vc#decoding-jwt). \ No newline at end of file diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/verify-vc.mdx b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/verify-vc.mdx new file mode 100644 index 000000000..5c743b330 --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/verify-vc.mdx @@ -0,0 +1,273 @@ +--- +title: Verify Credentials +hide_title: true +sidebar_position: 3 +--- + + + +# Verify Credentials + +When an entity, receives the presentation of verifiable credentials (VCs) or verifiable presentations (VPs), they must verify the integrity, authenticity, and content of the credentials. +The entity performing this verification is known as the **Verifier**. + +A verifiable credential (VC) is a digital proof related to an entity, known as the **Subject**. In contrast, a verifiable presentation (VP) is a collection of one or more VCs bundled together for presentation. + +## Verify Verifiable Credentials + +Upon receipt, the presentation of the VCs is encoded as [JWTs](/docs/web5/verifiable-credentials/jwt-to-vc#what-is-a-jwt) and can be verified via the `VerifiableCredentials.verify()` function. + +This function will: + +✅ Parse and validate the structure of the JWT + +✅ Ensure the presence of `alg` and `kid` in the JWT header + +✅ Resolve the issuer's Decentralized Identifier (DID) + +✅ Ensure the presence and validity of a verification method in the issuer's DID document + +✅ Verify the integrity and authenticity of the issuer's signature using the public key associated with the verification method + +✅ Ensure that the credential has not expired + +If any of these steps fail, the function will throw an error with a message indicating the nature of the failure. + +### Extracting VC JWTs from Presentation + +A Presentation object includes multiple things including the presentation submission as well as the verifiable credential JWTs themselves. + +
+Example Presentation Result + + + + +
+ +Since the VC JWTs are part of the larger [Presentation JSON object](/docs/web5/verifiable-credentials/presentation-exchange#create-presentation), they must be extracted and verified individually. +Below is an example of how this might be implemented: + + + + +The code snippet above shows how to extract the VC JWTs from the Presentation and verify each one, aggregating the results. + +The Verifier would then examine the results of the verifications to determine the errors: + + + +## Verify Verifiable Presentations + +Upon receipt, a Verifiable Presentation is encoded as a [JWT](/docs/web5/verifiable-credentials/jwt-to-vc#what-is-a-jwt) and can be verified via the `VerifiablePresentation.verify()` function. + + + +## Evaluate Presentation + +:::note +The information in this section is only applicable to the JavaScript SDK. +::: + +After verifying the authenticity of the VCs, the next step is to ensure that they actually satisfy the requirements outlined in the presentation definition. +This can be done via the `PresentationExchange.evaluatePresentation()` function: + + + + + +
+Example Evaluation Results + +```js +{ + "areRequiredCredentialsPresent": "info", + "verifiableCredential": [ + "/* JWT VC for EmploymentCredential */", + "/* JWT VC for PIICredential */", + ], + "warnings": [], + "errors": [], + "value": { + "id": "WabEjZrPQQOQw8YUg9kIN", + "definition_id": "presDefIdloanAppVerification123", + "descriptor_map": [ + { + "id": "employmentVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[0]" + }, + { + "id": "dobVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[1]" + }, + { + "id": "nameVerification", + "format": "jwt_vc", + "path": "$.verifiableCredential[1]" + } + ] + } +} +``` + +
+ +The `areRequiredCredentialsPresent` property of the evaluation results gives a status indication of whether the requirements were met: + +* **info** - all required credentials are present +* **warn** - more credentials were presented than were required +* **error** - all required credentials are not present + +If the status is `warn` or `error`, the details will be in the `warnings` or `error` array of the response. + + +## Revocation Check + +The `VerifiableCredential.verify()` function does not perform a revocation status check to determine whether the VCs have been revoked. +We strongly recommend that you verify this information by referencing the VC's `credentialStatus` property before honoring the VC as valid. + + +## Read VC Data + +Once you're confident that the VCs are valid, you can proceed to [parse the JWTs into VC objects](/docs/web5/verifiable-credentials/jwt-to-vc) such that you may work with the credentials' data. diff --git a/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/what-are-vcs.md b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/what-are-vcs.md new file mode 100644 index 000000000..ad4274c6c --- /dev/null +++ b/site/web5_versioned_docs/version-1.0.1/verifiable-credentials/what-are-vcs.md @@ -0,0 +1,109 @@ +--- +sidebar_position: 1 +title: What are VCs +hide_title: true +--- + +# Verifiable Credentials + + + +Alice and Bob have obtained [Decentralized Identifiers (DIDs)](https://developer.tbd.website/docs/web5/decentralized-identifiers/what-are-dids) and are now exploring Verifiable Credentials (VCs). + +## TL;DR + +VCs are digital credentials stating specific facts. They're similar to online badges. For example, the claims that "Alice has a degree from Decentralized University" or "Bob is an employee of Acme, Inc" can be represented via VCs. These credentials allow you to provide specific attributes of your identity or qualifications without relying on centralized authorities or revealing your entire identity. + +## What are Verifiable Credentials? + +![VC's three party model](/img/vc-three-party-model.png) + +> _Three party model diagram, from [Affinidi Pet. Ltd](https://affinidi.medium.com/what-are-verifiable-credentials-79f1846a7b9)_ + +A Verifiable Credential (VC) is a cryptographically-signed statement made by an issuer about a holder (or subject). VCs operate within a three-party model: the **Issuer**, the **Holder** **(or Subject)**, and the **Verifier**. + +An **issuer** is the party who is making the claim; while the **holder(subject)** is the party who the claim is made about. In the example VC where the claim is "Alice has a degree from Decentralized University", Decentralized University would be a credible issuer and Alice would be the subject. + +The beauty of VCs are that the claims made within them can be verified without needing a middleman. Since VCs are signed by the issuer with some fancy cryptography magic (we'll uncover this magic in a minute), anyone can become a **verifier** to confirm that the claims are indeed the real-deal. It’s all about being sure that a claim (like Alice's degree or Bob’s employment status) isn't just a made up story. + +## Examples of Verifiable Credentials + +After four years of long nights and early mornings, Alice graduates from Decentralized University and is instantly issued a VC from the university proving that she has earned her degree. + +When Alice begins her job search, she realizes the power of her VC. It acts as a digital diploma. Traditionally, potential employers would hire background check agencies to validate that she graduated—a process that could drag on for days if not weeks. But with her VC, they can now directly verify her diploma, bypassing traditional drawn-out methods. + +On a different note, Bob obtains a VC proving that he is an employee of Acme, Inc. When Bob wants to buy a house, he can provide the lender with his employment VC and they can rest assured that he is indeed employed and not a credit risk. Thus, speeding up the approval of his loan. + +## Why are VCs useful? + +Online transactions have become a part of our daily lives and with this comes the need to prove things like qualification and authorization. VCs provide a fast, secure approach to this while preserving your privacy. + +**Privacy** + +VCs are your personal pocket-sized portfolio, where you can share specific details when required. For example, Alice sharing her degree credential, gives potential employers the information she needs, without oversharing Alice's entire academic history (e.g. transcript of every course taken and every grade received) with them. So in this regard, VCs are privacy preserving. + +**Security** + +VCs are cryptographically signed by the issuer. This means that these credentials aren't easy to forge. Think of it as a tamper-proof seal on a medicine bottle, but for your digital identity. + +**Speed** + +Unlike traditional methods that might involve queues or waiting rooms, VCs allow for quick and instant validation. You don't have to wait for someone on the other end to give you the green light; it's immediate. + +## What makes up a VC? + +A VC typically contains the following components: + +- **Issuer:** The entity that creates and signs the VC. This could be a university, government agency, or any other trusted person/organization. In Alice's example, "Decentralized University" serves as the issuer. + +- **Subject:** The person or entity the VC is about. For instance, in Bob's employment VC, he is the subject. His unique DID ensures that the credential is tied specifically to him. + +- **Claim:** The specific assertion(s) the issuer is making about the subject. In Alice's degree VC, there could be a claim that states Alice has a Bachelor’s Degree in Decentralized Systems. + +A VC is represented as JSON objects so that they're easy to work with programmatically across various systems. Here's an example VC: + +```json +{ + "@context": "https://www.w3.org/2018/credentials/v1", + "type": "VerifiableCredential", + "issuer": "did:example:decuniversity", + "credentialSubject": { + "id": "did:example:alice123", + "name": "Alice", + "degree": "Bachelor’s in Decentralized Systems", + "completionDate": "2023-08-01" + } +} +``` + +In this VC, Decentralized University issued a credential verifying that Alice completed a Bachelor's degree in Decentralized Systems on August 1, 2023. + +## How do VCs actually work? + +Remember the cryptographic brilliance we encountered with DIDs and the pairing of [private & public keys](/docs/web5/decentralized-identifiers/what-are-dids#did-key-management)? It’s a critical part of VCs as well. + +There are different ways to secure VCs, let's look into securing them with JWT. JWT stands for [JSON Web Token](https://jwt.io/), a lightweight, URL-safe format that has three distinct components: a **header**, **payload**, and **signature**. + +1. **Header**: Tells you how the JWT was made. +1. **Payload**: This is the heart of the JWT. It holds all the important details like the issuer, subject, and claim. +1. **Signature**: This is like the seal on a medicine bottle, ensuring the information in the payload hasn't been touched. + +![Encoded JWT string](/img/jwt-encoded.png) + +> _Example of an encoded JWT string from [Ram Potabatti](https://medium.com/@rampotabatti)_ + +In our example, for Alice's potential employer to verify that she did indeed graduate from Decentralized University, Alice would need to give them a [Verifiable Presentation](/docs/glossary#verifiable-presentation) that contains her VC's JWT string. + +The employer(verifier) would then get the university's public key through the [Verifiable Data Registry](https://www.w3.org/TR/vc-data-model-2.0/#ecosystem-overview). In order for the university to create that JWT string, they need their pubic-private key pair. The employer would then verify the JWT using the university's public key and the JWT's encoded signature (the characters after the last period in the above string #3). + +A successful verification tells the employer that the VC is authentic. If this step fails, it's a sign that Alice's VC was forged. After verification, the employer can decode the JWT, as shown on [JWT.io](https://jwt.io/) to obtain it's payload. The payload contains all the information that is written inside of the VC that Alice has presented to them. The employer can trust that the information contained in the now verified payload was indeed issued by the university. + +:::note +Trust is still critical. A VC from “Bob’s Bogus Academy” might not carry the same credibility as one from a renowned institution. However, since public keys are public, the discovery of them presents a slight risk. How do you know that the public key you were given actually belongs to the issuer, such as Decentralized University? Although the Verifiable Data Registry gives you a way to search for public keys, it still requires a great amount of trust. Always consider the issuer's reputation. +::: + +## Storing and Sharing VCs + +VCs can be securely stored and managed via identity wallets. When the need arises to share them, it's simply a matter of presenting directly from the wallet, ensuring personal data remains private. + +Now, with their newfound knowledge of VCs, Alice and Bob are ready to further explore the decentralized web, presenting their credentials with confidence. Looking to play around with verifiable credentials yourself? Start by following our [Issue a VC](/docs/web5/verifiable-credentials/vc-issuance) guide. For additional resources, check out our [Web5/credentials](https://www.npmjs.com/package/@web5/credentials) npm package. diff --git a/site/web5_versioned_sidebars/version-1.0.1-sidebars.json b/site/web5_versioned_sidebars/version-1.0.1-sidebars.json new file mode 100644 index 000000000..51998422b --- /dev/null +++ b/site/web5_versioned_sidebars/version-1.0.1-sidebars.json @@ -0,0 +1,8 @@ +{ + "web5Sidebars": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/site/web5_versions.json b/site/web5_versions.json new file mode 100644 index 000000000..4b781f59b --- /dev/null +++ b/site/web5_versions.json @@ -0,0 +1,3 @@ +[ + "1.0.1" +] diff --git a/vite.config.ts b/vite.config.ts index 080c1971d..659380e39 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,15 +1,12 @@ -import { defineConfig, configDefaults } from 'vitest/config'; +import { defineConfig, configDefaults } from "vitest/config"; export default defineConfig({ test: { testTimeout: 40000, hookTimeout: 40000, teardownTimeout: 40000, - exclude: [ - ...configDefaults.exclude, - 'apps/**', - '**/*.spec.{js,ts,jsx,tsx}', - ], + include: ["./site/docs/**/*.{test,spec}.{js,ts,jsx,tsx}"], // Include all test files in site/docs and its subdirectories + exclude: [...configDefaults.exclude, "apps/**"], //TODO: Investigate coverage options later for output files // coverage: { // provider: 'istanbul', @@ -21,8 +18,6 @@ export default defineConfig({ // enabled: true, // headless: true, // }, - setupFiles: [ - './site/testsuites/testsuite-javascript/__tests__/setup-web5.js', - ], + setupFiles: ["./site/docs/test-utils/setup-web5.js"], }, });