├── .npmrc ├── src ├── lib │ ├── protocols │ │ ├── routing │ │ │ ├── index.ts │ │ │ └── 2.0 │ │ │ │ └── forward.ts │ │ ├── out-of-band │ │ │ ├── index.ts │ │ │ └── 2.0 │ │ │ │ └── invitation.ts │ │ ├── basic-message │ │ │ ├── index.ts │ │ │ └── 1.0 │ │ │ │ └── message.ts │ │ ├── issue-credential │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ └── 3.0 │ │ │ │ ├── issue-credential.ts │ │ │ │ ├── ack.ts │ │ │ │ ├── propose-credential.ts │ │ │ │ ├── offer-credential.ts │ │ │ │ └── request-credential.ts │ │ ├── trust-ping │ │ │ ├── index.ts │ │ │ └── 2.0 │ │ │ │ ├── ping-response.ts │ │ │ │ └── ping.ts │ │ ├── present-proof │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ └── 3.0 │ │ │ │ ├── propose-presentation.ts │ │ │ │ ├── presentation.ts │ │ │ │ └── request-presentation.ts │ │ ├── shorten-url │ │ │ ├── index.ts │ │ │ └── 1.0 │ │ │ │ ├── invalidate-shortened-url.ts │ │ │ │ ├── shortened-url.ts │ │ │ │ └── request-shortened-url.ts │ │ └── index.ts │ ├── index.ts │ ├── constants.ts │ ├── threads.ts │ ├── event-bus.ts │ ├── core.ts │ ├── mapped-resolvers.ts │ ├── interfaces.ts │ └── messaging.ts ├── app.html ├── app.d.ts └── routes │ └── +page.svelte ├── static └── favicon.png ├── .gitignore ├── tests ├── fixtures │ ├── document.json │ ├── keys │ │ ├── bob.json │ │ └── alice.json │ ├── didDocs │ │ ├── did-no-kak.json │ │ ├── bob.json │ │ └── alice.json │ └── jwes │ │ └── jwe0.json ├── event-bus.spec.ts └── didcomm.spec.ts ├── vite.config.ts ├── .eslintignore ├── .prettierignore ├── .vscode ├── settings.json └── launch.json ├── playwright.config.ts ├── .prettierrc ├── svelte.config.js ├── .eslintrc.cjs ├── tsconfig.json ├── Readme.md └── package.json /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/lib/protocols/routing/index.ts: -------------------------------------------------------------------------------- 1 | export * from "$lib/protocols/routing/2.0/forward.js" 2 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviarytech/didcomm/HEAD/static/favicon.png -------------------------------------------------------------------------------- /src/lib/protocols/out-of-band/index.ts: -------------------------------------------------------------------------------- 1 | export * from "$lib/protocols/out-of-band/2.0/invitation.js" -------------------------------------------------------------------------------- /src/lib/protocols/basic-message/index.ts: -------------------------------------------------------------------------------- 1 | export * from "$lib/protocols/basic-message/1.0/message.js" -------------------------------------------------------------------------------- /src/lib/protocols/issue-credential/README.md: -------------------------------------------------------------------------------- 1 | - [ ] Propose 2 | - [ ] Offer 3 | - [ ] Request 4 | - [ ] Issue 5 | - [x] Ack 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | old 10 | -------------------------------------------------------------------------------- /src/lib/protocols/trust-ping/index.ts: -------------------------------------------------------------------------------- 1 | export * from "$lib/protocols/trust-ping/2.0/ping.js" 2 | export * from "$lib/protocols/trust-ping/2.0/ping-response.js" -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as protocols from "$lib/protocols/index.js" 3 | import { DIDComm } from "$lib/messaging.js" 4 | 5 | export { 6 | protocols, 7 | DIDComm 8 | } 9 | -------------------------------------------------------------------------------- /tests/fixtures/document.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "123", 3 | "to": ["did:example:bob"], 4 | "from": "did:example:alice", 5 | "type": "https://didcomm.org/test", 6 | "body": { "msg": "test" } 7 | } 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()] 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const enum DIDCOMM_MESSAGE_MEDIA_TYPE { 2 | PLAIN = "application/didcomm-plain+json", 3 | SIGNED = "application/didcomm-signed+json", 4 | ENCRYPTED = "application/didcomm-encrypted+json", 5 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /src/lib/protocols/present-proof/index.ts: -------------------------------------------------------------------------------- 1 | export * from "$lib/protocols/present-proof/3.0/presentation.js" 2 | export * from "$lib/protocols/present-proof/3.0/propose-presentation.js" 3 | export * from "$lib/protocols/present-proof/3.0/request-presentation.js" -------------------------------------------------------------------------------- /src/lib/protocols/shorten-url/index.ts: -------------------------------------------------------------------------------- 1 | export * from "$lib/protocols/shorten-url/1.0/shortened-url.js" 2 | export * from "$lib/protocols/shorten-url/1.0/request-shortened-url.js" 3 | export * from "$lib/protocols/shorten-url/1.0/invalidate-shortened-url.js" 4 | -------------------------------------------------------------------------------- /tests/fixtures/keys/bob.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "did:example:bob#key-1", 3 | "type": "X25519KeyAgreementKey2019", 4 | "publicKeyBase58": "3zSE11h82KtPYPj8p9cTgzr6yDWFYEsfM19xc1K5vjKY", 5 | "privateKeyBase58": "66pGmEHd7fBfQa9ap27vWSouHAmipbmmw6GduBwNRY6y" 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.background": "#053607", 4 | "titleBar.activeBackground": "#074B0A", 5 | "titleBar.activeForeground": "#F2FEF3" 6 | }, 7 | "jest.jestCommandLine": "npx jest" 8 | } -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | } 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs/types#app 4 | // for information about these interfaces 5 | // and what to do when importing types 6 | declare namespace App { 7 | // interface Locals {} 8 | // interface PageData {} 9 | // interface Error {} 10 | // interface Platform {} 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/protocols/issue-credential/index.ts: -------------------------------------------------------------------------------- 1 | export * from "$lib/protocols/issue-credential/3.0/ack.js" 2 | export * from "$lib/protocols/issue-credential/3.0/issue-credential.js" 3 | export * from "$lib/protocols/issue-credential/3.0/offer-credential.js" 4 | export * from "$lib/protocols/issue-credential/3.0/propose-credential.js" 5 | export * from "$lib/protocols/issue-credential/3.0/request-credential.js" -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess(), 9 | 10 | kit: { 11 | adapter: adapter() 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /tests/event-bus.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventBus } from "$lib/event-bus"; 2 | import { expect, test, vi } from "vitest"; 3 | 4 | const mockCallback = vi.fn((x) => 42 + x); 5 | 6 | test("event-bus registers and handles", () => { 7 | const bus = new EventBus(); 8 | 9 | bus.register("foo", { 10 | handle: (e: any) => { 11 | mockCallback(0); 12 | }, 13 | }); 14 | 15 | bus.dispatch("foo"); 16 | expect(mockCallback.mock.calls.length).toBe(1); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/fixtures/keys/alice.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "did:example:alice#key-0", 3 | "controller": "did:example:alice", 4 | "type": "JsonWebKey2020", 5 | "publicKeyJwk": { 6 | "kty": "OKP", 7 | "crv": "X25519", 8 | "x": "tsc9iYfy4hv2Mz5Q-ztGjKXeXzWUDWl5DLpfepJg4Wc" 9 | }, 10 | "privateKeyJwk": { 11 | "kty": "OKP", 12 | "crv": "X25519", 13 | "x": "tsc9iYfy4hv2Mz5Q-ztGjKXeXzWUDWl5DLpfepJg4Wc", 14 | "d": "G1YdOhO_JFnDMVLgYXN0tnHjVgaQDaUp5Oxjl1obu8A" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/protocols/index.ts: -------------------------------------------------------------------------------- 1 | export * as BasicMessage from "$lib/protocols/basic-message/index.js" 2 | export * as OutOfBand from "$lib/protocols/out-of-band/index.js" 3 | export * as PresentProof from "$lib/protocols/present-proof/index.js" 4 | export * as TrustPing from "$lib/protocols/trust-ping/index.js" 5 | export * as IssueCredential from "$lib/protocols/issue-credential/index.js" 6 | export * as Routing from "$lib/protocols/routing/index.js" 7 | export * as ShortenURL from "$lib/protocols/shorten-url/index.js" -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "node", 5 | "name": "vscode-jest-tests", 6 | "request": "launch", 7 | "console": "integratedTerminal", 8 | "internalConsoleOptions": "neverOpen", 9 | "disableOptimisticBPs": true, 10 | "program": "${workspaceFolder}/node_modules/.bin/jest --debug", 11 | "cwd": "${workspaceFolder}", 12 | "args": [ 13 | "--runInBand", 14 | "--watchAll=false" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/didDocs/did-no-kak.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"], 3 | "id": "did:example:bad", 4 | "verificationMethod": [ 5 | { 6 | "id": "did:example:bad#key-1", 7 | "controller": "did:example:bad", 8 | "type": "X25519KeyAgreementKey2019", 9 | "publicKeyBase58": "3zSE11h82KtPYPj8p9cTgzr6yDWFYEsfM19xc1K5vjKY" 10 | } 11 | ], 12 | "authentication": ["did:example:bad#key-1"], 13 | "assertionMethod": ["did:example:bad#key-1"], 14 | "keyAgreement": [], 15 | "service": [ 16 | { 17 | "id": "did:example:bad#didcomm", 18 | "type": "DIDCommMessaging", 19 | "serviceEndpoint": "http://example.com/didcomm", 20 | "routingKeys": [] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/threads.ts: -------------------------------------------------------------------------------- 1 | export class DIDCommThreads { 2 | private threads: {id: string, me: string}[]; 3 | 4 | constructor() { 5 | this.threads = []; 6 | } 7 | 8 | get length() { 9 | return this.threads.length 10 | } 11 | 12 | get all() { 13 | return this.threads; 14 | } 15 | 16 | getThreadById = (id: string): {id: string, me: string} | null => { 17 | return this.threads.find(t => t.id === id) ?? null 18 | } 19 | 20 | addThread = (id: string, me: string) => { 21 | if (this.threads.findIndex(t => t.id === id) < 0) { 22 | this.threads = [...this.threads, {id, me}] 23 | } 24 | } 25 | 26 | removeThread = (id: string | undefined) => { 27 | this.threads = this.threads.filter(t => t.id !== id) 28 | } 29 | } -------------------------------------------------------------------------------- /tests/fixtures/jwes/jwe0.json: -------------------------------------------------------------------------------- 1 | { 2 | "protected": "eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLWVuY3J5cHRlZCtqc29uIiwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJlbmMiOiJYQzIwUCIsImFwdiI6IkZ3UllSalRiYVU2aDZVWVZwTkRvbkR4clAtQV93cExVbGJPRENDTy1IdDAiLCJlcGsiOnsiY3J2IjoiWDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6Ik5jbFNkamhVWGtMNTczZkZId1VSeFpYWFZtcmZtSHB4RGZaaXpCWXd1enMifX0", 3 | "recipients": [ 4 | { 5 | "header": { "kid": "did:example:bob#key-1" }, 6 | "encrypted_key": "_0yAiC_E8zDLA3QR8yuSEp951YeRTTC-bGJI5ZWTH4tXKZDLmMehLQ" 7 | } 8 | ], 9 | "iv": "liQFotPkQif4bpmY36rf3DxV2QPEAkVR", 10 | "ciphertext": "sz48JkZEVsv9kj7yeTOTziUNJ3KRYwtG7Eh1EYBWlcUYv-jX46_ZPnSlhjGYjM_JSQOeFsxvwqkLwcdBzpbG8YAxVPwwn-3Q_FXqHENW5iQDrgrPZx6BmzTUk--H0E6v8V7YszQUQ--RDwo0yxxIw3rUc0e6oIKxsbseiR89A-w0kR1X", 11 | "tag": "2mVPuR1zomGe99gUtnmx7A" 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/protocols/trust-ping/2.0/ping-response.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 2 | 3 | export const TRUST_PING_RESPONSE_PING_TYPE = 4 | "https://didcomm.org/trust-ping/2.0/ping-response"; 5 | 6 | export interface TrustPingResponseMessage extends IDIDCommMessage { 7 | payload: { 8 | id: string; 9 | type: string; 10 | created_time?: number; 11 | thid: string; 12 | from?: string; 13 | to?: string[]; 14 | body: any; 15 | }; 16 | } 17 | 18 | export class DefaultTrustPingResponseMessageHandler 19 | implements IDIDCommMessageHandler { 20 | type = TRUST_PING_RESPONSE_PING_TYPE; 21 | 22 | async handle(props: { 23 | message: IDIDCommMessage; 24 | didcomm: IDIDComm; 25 | }): Promise { 26 | 27 | props.didcomm.threads.removeThread(props.message.payload.thid) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/fixtures/didDocs/bob.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/ns/did/v1", 4 | "https://w3id.org/security/suites/jws-2020/v1" 5 | ], 6 | "id": "did:example:bob", 7 | "verificationMethod": [ 8 | { 9 | "id": "did:example:bob#key-1", 10 | "controller": "did:example:bob", 11 | "type": "X25519KeyAgreementKey2019", 12 | "publicKeyBase58": "3zSE11h82KtPYPj8p9cTgzr6yDWFYEsfM19xc1K5vjKY" 13 | } 14 | ], 15 | "authentication": [ 16 | "did:example:bob#key-1" 17 | ], 18 | "assertionMethod": [ 19 | "did:example:bob#key-1" 20 | ], 21 | "keyAgreement": [ 22 | "did:example:bob#key-1" 23 | ], 24 | "service": [ 25 | { 26 | "id": "did:example:bob#didcomm", 27 | "type": "DIDCommMessaging", 28 | "serviceEndpoint": { 29 | "uri": "http://example.com/didcomm", 30 | "accept": [ 31 | "didcomm/v2" 32 | ], 33 | "routingKeys": [] 34 | } 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /tests/fixtures/didDocs/alice.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/ns/did/v1", 4 | "https://w3id.org/security/suites/jws-2020/v1" 5 | ], 6 | "id": "did:example:alice", 7 | "verificationMethod": [ 8 | { 9 | "id": "did:example:alice#key-0", 10 | "controller": "did:example:alice", 11 | "type": "JsonWebKey2020", 12 | "publicKeyJwk": { 13 | "kty": "OKP", 14 | "crv": "X25519", 15 | "x": "tsc9iYfy4hv2Mz5Q-ztGjKXeXzWUDWl5DLpfepJg4Wc" 16 | } 17 | } 18 | ], 19 | "authentication": [ 20 | "did:example:alice#key-0" 21 | ], 22 | "assertionMethod": [ 23 | "did:example:alice#key-0" 24 | ], 25 | "keyAgreement": [ 26 | "did:example:alice#key-0" 27 | ], 28 | "service": [ 29 | { 30 | "id": "did:example:alice#didcomm", 31 | "type": "DIDCommMessaging", 32 | "serviceEndpoint": { 33 | "uri": "http://example.com/didcomm", 34 | "accept": [ 35 | "didcomm/v2" 36 | ], 37 | "routingKeys": [] 38 | } 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/lib/protocols/present-proof/interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDCommAttachment } from "$lib/interfaces.js"; 2 | import type { IPresentationDefinition } from "@sphereon/pex" 3 | import type { PresentationSubmission } from "@sphereon/pex-models" 4 | 5 | 6 | export interface IDIFPresentationExchangeDefinitionAttachment 7 | extends IDIDCommAttachment { 8 | id: string; 9 | media_type: "application/json"; 10 | format: "dif/presentation-exchange/definitions@v1.0"; 11 | data: { 12 | json: { 13 | dif: { 14 | options: { 15 | challenge: string; 16 | domain: string; 17 | }; 18 | presentation_definition: IPresentationDefinition; 19 | }; 20 | }; 21 | }; 22 | } 23 | 24 | export interface IDIFPresentationExchangeSubmissionAttachment 25 | extends IDIDCommAttachment { 26 | id: string; 27 | media_type: "application/ld+json"; 28 | format: "dif/presentation-exchange/submission@v1.0"; 29 | data: { 30 | json: { 31 | dif: PresentationSubmission; 32 | }; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/protocols/issue-credential/interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDCommAttachment } from "$lib/interfaces.js"; 2 | import type { IPresentationDefinition } from "@sphereon/pex" 3 | import type { PresentationSubmission } from "@sphereon/pex-models" 4 | 5 | 6 | export interface IDIFPresentationExchangeDefinitionAttachment 7 | extends IDIDCommAttachment { 8 | id: string; 9 | media_type: "application/json"; 10 | format: "dif/presentation-exchange/definitions@v1.0"; 11 | data: { 12 | json: { 13 | dif: { 14 | options: { 15 | challenge: string; 16 | domain: string; 17 | }; 18 | presentation_definition: IPresentationDefinition; 19 | }; 20 | }; 21 | }; 22 | } 23 | 24 | export interface IDIFPresentationExchangeSubmissionAttachment 25 | extends IDIDCommAttachment { 26 | id: string; 27 | media_type: "application/ld+json"; 28 | format: "dif/presentation-exchange/submission@v1.0"; 29 | data: { 30 | json: { 31 | dif: PresentationSubmission; 32 | }; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /src/lib/event-bus.ts: -------------------------------------------------------------------------------- 1 | export interface Registry { 2 | unregister: () => void; 3 | } 4 | 5 | export interface Callable { 6 | [key: string]: any; 7 | } 8 | 9 | export interface Subscriber { 10 | [key: string]: Callable; 11 | } 12 | 13 | export interface IEventBus { 14 | dispatch(event: string, arg?: T): void; 15 | register(event: string, callbackClass: any): Registry; 16 | } 17 | 18 | export class EventBus implements IEventBus { 19 | private subscribers: Subscriber; 20 | private static nextId = 0; 21 | 22 | constructor() { 23 | this.subscribers = {}; 24 | } 25 | 26 | public dispatch(event: string, arg?: T): void { 27 | const subscriber = this.subscribers[event]; 28 | 29 | if (subscriber === undefined) { 30 | return; 31 | } 32 | 33 | Object.keys(subscriber).forEach((key) => subscriber[key].handle(arg)); 34 | } 35 | 36 | public register(event: string, callbackClass: any): Registry { 37 | const id = this.getNextId(); 38 | if (!this.subscribers[event]) this.subscribers[event] = {}; 39 | 40 | this.subscribers[event][id] = callbackClass; 41 | 42 | return { 43 | unregister: () => { 44 | delete this.subscribers[event][id]; 45 | if (Object.keys(this.subscribers[event]).length === 0) 46 | delete this.subscribers[event]; 47 | }, 48 | }; 49 | } 50 | 51 | private getNextId(): number { 52 | return EventBus.nextId++; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/protocols/shorten-url/1.0/invalidate-shortened-url.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 2 | 3 | 4 | export const SHORTENED_URL_INVALIDATE_TYPE = "https://didcomm.org/shorten-url/1.0/invalidate-url"; 5 | 6 | export interface ShortenedURLInvalidateMessage extends IDIDCommMessage { 7 | payload: { 8 | id: string; 9 | type: string; 10 | from?: string; 11 | to?: string[]; 12 | created_time?: number; 13 | expires_time?: number; 14 | body: { 15 | shortened_url: string; 16 | }; 17 | }; 18 | } 19 | 20 | export const createShortenedURLInvalidateMessage = (msg: ShortenedURLInvalidateMessage) => msg; 21 | 22 | export class ShortenedURLInvalidateMessageHandler implements IDIDCommMessageHandler { 23 | type = SHORTENED_URL_INVALIDATE_TYPE; 24 | callback: (message: ShortenedURLInvalidateMessage, didcomm: IDIDComm) => Promise; 25 | 26 | constructor( 27 | callback: (message: ShortenedURLInvalidateMessage, didcomm: IDIDComm) => Promise 28 | ) { 29 | this.callback = callback; 30 | } 31 | 32 | async handle(props: { 33 | message: ShortenedURLInvalidateMessage; 34 | didcomm: IDIDComm; 35 | }): Promise { 36 | const { message, didcomm } = props; 37 | const { payload } = message; 38 | console.log( 39 | `Invalidate Shortened URL Received: ${payload.id}, sent at ${payload.created_time}` 40 | ); 41 | 42 | await this.callback(message, didcomm) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/protocols/shorten-url/1.0/shortened-url.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 2 | 3 | 4 | export const SHORTENED_URL_TYPE = "https://didcomm.org/shorten-url/1.0/shortened-url"; 5 | 6 | export interface ShortenedURLMessage extends IDIDCommMessage { 7 | payload: { 8 | id: string; 9 | type: string; 10 | from?: string; 11 | to?: string[]; 12 | thid?: string; 13 | created_time?: number; 14 | expires_time?: number; 15 | body: { 16 | shortened_url: string; 17 | expires_time?: number; 18 | }; 19 | }; 20 | } 21 | 22 | export const createShortenedURLMessage = (msg: ShortenedURLMessage) => msg; 23 | 24 | export class ShortenedURLMessageHandler implements IDIDCommMessageHandler { 25 | type = SHORTENED_URL_TYPE; 26 | callback: (msg: ShortenedURLMessage, didcomm: IDIDComm) => Promise; 27 | 28 | constructor( 29 | callback: (message: ShortenedURLMessage, didcomm: IDIDComm) => Promise 30 | ) { 31 | this.callback = callback; 32 | } 33 | 34 | async handle(props: { 35 | message: ShortenedURLMessage; 36 | didcomm: IDIDComm; 37 | }): Promise { 38 | const { message, didcomm } = props; 39 | const { payload } = message; 40 | console.log( 41 | `Shortened URL Received: ${payload.id}, sent at ${payload.created_time}` 42 | ); 43 | 44 | await this.callback(message, didcomm) 45 | didcomm.threads.removeThread(message.payload.thid) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/protocols/basic-message/1.0/message.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | 5 | export const BASIC_MESSAGE_TYPE = 6 | "https://didcomm.org/basicmessage/1.0/message"; 7 | export class BasicMessage implements IDIDCommMessage { 8 | payload: { 9 | id: string; 10 | type: string; 11 | from: string; 12 | thid: string; 13 | to?: string[]; 14 | created_time?: number; 15 | body: { 16 | content: string; 17 | }; 18 | }; 19 | repudiable = false; 20 | 21 | constructor(from: string, to: string[], content: string, thid?: string) { 22 | const id = sha256(nanoid()); 23 | this.payload = { 24 | id, 25 | from, 26 | type: BASIC_MESSAGE_TYPE, 27 | thid: thid ?? id, 28 | to, 29 | created_time: Math.floor(Date.now() / 1000), 30 | body: { 31 | content, 32 | }, 33 | }; 34 | } 35 | } 36 | 37 | export class BasicMessageHandler implements IDIDCommMessageHandler { 38 | type = BASIC_MESSAGE_TYPE; 39 | callback: (msg: IDIDCommMessage, didcomm: IDIDComm) => Promise; 40 | 41 | constructor( 42 | callback: (payload: IDIDCommMessage, didcomm: IDIDComm) => Promise 43 | ) { 44 | this.callback = callback; 45 | } 46 | 47 | async handle(props: { message: IDIDCommMessage, didcomm: IDIDComm}): Promise { 48 | console.log( 49 | `Basic Message Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 50 | ); 51 | await this.callback(props.message, props.didcomm); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/protocols/present-proof/3.0/propose-presentation.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | 5 | export const PROPOSE_PRESENTATION_TYPE = 6 | "https://didcomm.org/present-proof/3.0/propose-presentation"; 7 | 8 | export class ProposePresentationMessage implements IDIDCommMessage { 9 | payload: { 10 | id: string; 11 | type: string; 12 | from: string; 13 | pthid: string; 14 | to: string[]; 15 | created_time?: number; 16 | body: {}; 17 | }; 18 | repudiable = false; 19 | 20 | constructor(from: string, to: string[], pthid: string) { 21 | const id = sha256(nanoid()); 22 | this.payload = { 23 | id, 24 | from, 25 | type: PROPOSE_PRESENTATION_TYPE, 26 | pthid, 27 | to, 28 | created_time: Math.floor(Date.now() / 1000), 29 | body: {}, 30 | }; 31 | } 32 | } 33 | 34 | export class ProposePresentationMessageHandler 35 | implements IDIDCommMessageHandler { 36 | type = PROPOSE_PRESENTATION_TYPE; 37 | callback: (msg: ProposePresentationMessage, didcomm: IDIDComm) => Promise; 38 | 39 | constructor( 40 | callback: (msg: ProposePresentationMessage, didcomm: IDIDComm) => Promise 41 | ) { 42 | this.callback = callback; 43 | } 44 | 45 | async handle(props: { 46 | message: any; 47 | didcomm: IDIDComm; 48 | }): Promise { 49 | console.log( 50 | `Propose Presentation Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 51 | ); 52 | await this.callback(props.message, props.didcomm); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aviarytech/didcomm", 3 | "version": "0.4.77", 4 | "scripts": { 5 | "dev": "vite dev", 6 | "build": "svelte-kit sync && svelte-package", 7 | "prepublishOnly": "echo 'Did you mean to publish `./package/`, instead of `./`?' && exit 1", 8 | "test": "vitest", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 12 | "format": "prettier --plugin-search-dir . --write .", 13 | "shipit": "npm run build && pushd package && npm publish && popd" 14 | }, 15 | "publishConfig": { 16 | "registry": "https://npm.pkg.github.com/aviarytech" 17 | }, 18 | "devDependencies": { 19 | "@aviarytech/crypto": "^0.0.46", 20 | "@aviarytech/did-peer": "^0.0.21", 21 | "@sveltejs/adapter-auto": "next", 22 | "@sveltejs/kit": "next", 23 | "@sveltejs/package": "^1.0.0-next.5", 24 | "@typescript-eslint/eslint-plugin": "^5.27.0", 25 | "@typescript-eslint/parser": "^5.27.0", 26 | "eslint": "^8.16.0", 27 | "eslint-config-prettier": "^8.3.0", 28 | "eslint-plugin-svelte3": "^4.0.0", 29 | "prettier": "^2.6.2", 30 | "prettier-plugin-svelte": "^2.7.0", 31 | "svelte": "^3.44.0", 32 | "svelte-check": "^2.7.1", 33 | "svelte-preprocess": "^4.10.6", 34 | "tslib": "^2.3.1", 35 | "typescript": "^4.7.4", 36 | "vite": "^3.1.0", 37 | "vitest": "^0.24.5" 38 | }, 39 | "type": "module", 40 | "dependencies": { 41 | "@aviarytech/dids": "^0.1.23", 42 | "@sphereon/pex": "^1.1.4", 43 | "@sphereon/pex-models": "^1.1.0", 44 | "axios": "^1.1.3", 45 | "cross-fetch": "^3.1.5", 46 | "didcomm": "^0.4.1", 47 | "didcomm-node": "^0.4.1", 48 | "nanoid": "^4.0.0" 49 | } 50 | } -------------------------------------------------------------------------------- /src/lib/protocols/issue-credential/3.0/issue-credential.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | import type { IDIDComm, IDIDCommAttachment, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | import type { Attachment } from "didcomm-node"; 5 | 6 | export const ISSUE_CREDENTIAL_ISSUE_TYPE = 7 | "https://didcomm.org/issue-credential/3.0/issue-credential"; 8 | 9 | export class IssueCredentialIssueMessage implements IDIDCommMessage { 10 | payload: { 11 | id: string; 12 | type: string; 13 | from?: string; 14 | thid?: string; 15 | to?: string[]; 16 | created_time?: number; 17 | body: {}; 18 | attachments: Attachment[] 19 | }; 20 | repudiable = false; 21 | 22 | constructor( 23 | from: string, 24 | to: string[], 25 | thid: string, 26 | ) { 27 | const id = sha256(nanoid()) 28 | this.payload = { 29 | id, 30 | from, 31 | type: ISSUE_CREDENTIAL_ISSUE_TYPE, 32 | thid, 33 | to, 34 | created_time: Math.floor(Date.now() / 1000), 35 | body: {}, 36 | attachments: [] 37 | }; 38 | } 39 | } 40 | 41 | export class IssueCredentialIssueMessageHandler implements IDIDCommMessageHandler { 42 | type = ISSUE_CREDENTIAL_ISSUE_TYPE; 43 | callback: (msg: IssueCredentialIssueMessage, didcomm: IDIDComm) => Promise; 44 | 45 | constructor( 46 | callback: (msg: IssueCredentialIssueMessage, didcomm: IDIDComm) => Promise 47 | ) { 48 | this.callback = callback; 49 | } 50 | 51 | async handle(props: { 52 | message: any, 53 | didcomm: IDIDComm 54 | }): Promise { 55 | console.log( 56 | `Issue Credential - Issue Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 57 | ); 58 | await this.callback(props.message, props.didcomm); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/lib/protocols/shorten-url/1.0/request-shortened-url.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 2 | 3 | 4 | export const SHORTENED_URL_REQUEST_TYPE = "https://didcomm.org/shorten-url/1.0/request-shortened-url"; 5 | 6 | export interface RequestShortenedURLMessage extends IDIDCommMessage { 7 | payload: { 8 | id: string; 9 | type: string; 10 | from?: string; 11 | to?: string[]; 12 | created_time?: number; 13 | expires_time?: number; 14 | body: { 15 | url: string; 16 | requested_validity_seconds: number; 17 | goal_code: string; 18 | short_url_slug?: string; 19 | }; 20 | }; 21 | } 22 | 23 | export const createRequestShortenedURLMessage = (msg: RequestShortenedURLMessage) => msg; 24 | 25 | export class RequestShortenedURLMessageHandler implements IDIDCommMessageHandler { 26 | type = SHORTENED_URL_REQUEST_TYPE; 27 | callback: (message: RequestShortenedURLMessage, didcomm: IDIDComm) => Promise; 28 | 29 | constructor( 30 | callback: (message: RequestShortenedURLMessage, didcomm: IDIDComm) => Promise 31 | ) { 32 | this.callback = callback; 33 | } 34 | 35 | async sendingHook(props: { 36 | message: RequestShortenedURLMessage, 37 | didcomm: IDIDComm 38 | }): Promise { 39 | if(props.message.payload.from) { 40 | props.didcomm.threads.addThread(props.message.payload.id, props.message.payload.from) 41 | } 42 | } 43 | 44 | async handle(props: { 45 | message: RequestShortenedURLMessage; 46 | didcomm: IDIDComm; 47 | }): Promise { 48 | const { message, didcomm } = props; 49 | const { payload } = message; 50 | console.log( 51 | `Request Shortened URL Received: ${payload.id}, sent at ${payload.created_time}` 52 | ); 53 | 54 | await this.callback(message, didcomm) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/protocols/present-proof/3.0/presentation.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | import type { IDIFPresentationExchangeSubmissionAttachment }from "../interfaces.js"; 5 | 6 | export const PRESENTATION_TYPE = 7 | "https://didcomm.org/present-proof/3.0/presentation"; 8 | 9 | export class PresentationMessage implements IDIDCommMessage { 10 | payload: { 11 | id: string; 12 | type: string; 13 | from?: string; 14 | thid?: string; 15 | to?: string[]; 16 | created_time?: number; 17 | body: {}; 18 | attachments?: IDIFPresentationExchangeSubmissionAttachment[]; 19 | }; 20 | repudiable = false; 21 | 22 | constructor( 23 | from: string, 24 | to: string[], 25 | thid: string, 26 | attachments: IDIFPresentationExchangeSubmissionAttachment[] 27 | ) { 28 | const id = sha256(nanoid()) 29 | this.payload = { 30 | id, 31 | from, 32 | type: PRESENTATION_TYPE, 33 | thid, 34 | to, 35 | created_time: Math.floor(Date.now() / 1000), 36 | body: {}, 37 | attachments, 38 | }; 39 | } 40 | } 41 | 42 | export class PresentationMessageHandler implements IDIDCommMessageHandler { 43 | type = PRESENTATION_TYPE; 44 | callback: (msg: PresentationMessage, didcomm: IDIDComm) => Promise; 45 | 46 | constructor( 47 | callback: (msg: PresentationMessage, didcomm: IDIDComm) => Promise 48 | ) { 49 | this.callback = callback; 50 | } 51 | 52 | async handle(props: { 53 | message: any, 54 | didcomm: IDIDComm 55 | }): Promise { 56 | console.log( 57 | `Presentation Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 58 | ); 59 | await this.callback(props.message, props.didcomm); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/protocols/issue-credential/3.0/ack.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | 5 | export const ISSUE_CREDENTIAL_ACK_TYPE = 6 | "https://didcomm.org/issue-credential/3.0/ack"; 7 | 8 | export class IssueCredentialAckMessage implements IDIDCommMessage { 9 | payload: { 10 | id: string; 11 | type: string; 12 | from?: string; 13 | thid?: string; 14 | to?: string[]; 15 | created_time?: number; 16 | body: {}; 17 | }; 18 | repudiable = false; 19 | 20 | constructor( 21 | from: string, 22 | to: string[], 23 | thid: string, 24 | ) { 25 | const id = sha256(nanoid()) 26 | this.payload = { 27 | id, 28 | from, 29 | type: ISSUE_CREDENTIAL_ACK_TYPE, 30 | thid, 31 | to, 32 | created_time: Math.floor(Date.now() / 1000), 33 | body: {} 34 | }; 35 | } 36 | } 37 | 38 | export class IssueCredentialAckMessageHandler implements IDIDCommMessageHandler { 39 | type = ISSUE_CREDENTIAL_ACK_TYPE; 40 | callback: (msg: IssueCredentialAckMessage, didcomm: IDIDComm) => Promise; 41 | 42 | constructor( 43 | callback: (msg: IssueCredentialAckMessage, didcomm: IDIDComm) => Promise 44 | ) { 45 | this.callback = callback; 46 | } 47 | 48 | async sendingHook(props: { 49 | message: any, 50 | didcomm: IDIDComm 51 | }): Promise { 52 | props.didcomm.threads.removeThread(props.message.payload.thid) 53 | } 54 | 55 | async handle(props: { 56 | message: any, 57 | didcomm: IDIDComm 58 | }): Promise { 59 | console.log( 60 | `Issue Credential - Ack Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 61 | ); 62 | await this.callback(props.message, props.didcomm); 63 | props.didcomm.threads.removeThread(props.message.payload.thid) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/lib/protocols/routing/2.0/forward.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDComm, IDIDCommAttachment, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 2 | 3 | 4 | export const ROUTING_FORWARD_MESSAGE_TYPE = "https://didcomm.org/routing/2.0/forward"; 5 | 6 | export interface RoutingForwardMessage extends IDIDCommMessage { 7 | payload: { 8 | id: string; 9 | type: string; 10 | from?: string; 11 | to?: string[]; 12 | created_time?: number; 13 | expires_time?: number; 14 | body: { 15 | next: string; 16 | }; 17 | attachments?: IDIDCommAttachment[] 18 | }; 19 | } 20 | 21 | export const createRoutingForwardMessage = (msg: RoutingForwardMessage) => msg; 22 | 23 | export class RoutingForwardMessageHandler implements IDIDCommMessageHandler { 24 | type = ROUTING_FORWARD_MESSAGE_TYPE; 25 | callback: (msg: IDIDCommMessage, didcomm: IDIDComm) => Promise; 26 | 27 | constructor( 28 | callback: (payload: IDIDCommMessage, didcomm: IDIDComm) => Promise 29 | ) { 30 | this.callback = callback; 31 | } 32 | 33 | async handle(props: { 34 | message: RoutingForwardMessage; 35 | didcomm: IDIDComm; 36 | }): Promise { 37 | const { message, didcomm } = props; 38 | const { payload } = message; 39 | console.log( 40 | `Forward Message Received: ${payload.id}, sent at ${payload.created_time}` 41 | ); 42 | if (!payload.attachments) { 43 | console.error(`Forward Message missing attachments`) 44 | } else { 45 | for (let i = 0; i < payload.attachments.length; i++) { 46 | const msg = payload.attachments.at(i)?.data.json 47 | if (msg) { 48 | console.log(`Forwarding message to ${payload.body.next}`) 49 | await didcomm.sendPackedMessage(payload.body.next, JSON.stringify(msg)) 50 | await this.callback(msg as any, didcomm) 51 | } else { 52 | console.error(`Forward message attachment didn't include 'json' field`) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/protocols/issue-credential/3.0/propose-credential.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | 5 | export const ISSUE_CREDENTIAL_PROPOSE_TYPE = 6 | "https://didcomm.org/issue-credential/3.0/propose-credential"; 7 | 8 | export class IssueCredentialProposeMessage implements IDIDCommMessage { 9 | payload: { 10 | id: string; 11 | type: string; 12 | from?: string; 13 | thid?: string; 14 | to?: string[]; 15 | created_time?: number; 16 | body: {}; 17 | }; 18 | repudiable = false; 19 | 20 | constructor( 21 | from: string, 22 | to: string[], 23 | thid: string, 24 | ) { 25 | const id = sha256(nanoid()) 26 | this.payload = { 27 | id, 28 | from, 29 | type: ISSUE_CREDENTIAL_PROPOSE_TYPE, 30 | thid, 31 | to, 32 | created_time: Math.floor(Date.now() / 1000), 33 | body: {} 34 | }; 35 | } 36 | } 37 | 38 | export class IssueCredentialProposeMessageHandler implements IDIDCommMessageHandler { 39 | type = ISSUE_CREDENTIAL_PROPOSE_TYPE; 40 | callback: (msg: IssueCredentialProposeMessage, didcomm: IDIDComm) => Promise; 41 | 42 | constructor( 43 | callback: (msg: IssueCredentialProposeMessage, didcomm: IDIDComm) => Promise 44 | ) { 45 | this.callback = callback; 46 | } 47 | 48 | async sendingHook(props: { message: IDIDCommMessage; didcomm: IDIDComm; }) { 49 | const { payload } = props.message; 50 | if(!payload.thid && payload.from) { 51 | props.didcomm.threads.addThread(payload.id, payload.from) 52 | } 53 | } 54 | 55 | async handle(props: { 56 | message: any, 57 | didcomm: IDIDComm 58 | }): Promise { 59 | console.log( 60 | `Issue Credential - Propose Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 61 | ); 62 | await this.callback(props.message, props.didcomm); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib/protocols/present-proof/3.0/request-presentation.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid"; 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | import type { IDIFPresentationExchangeDefinitionAttachment }from "../interfaces.js"; 5 | 6 | export const REQUEST_PRESENTATION_TYPE = 7 | "https://didcomm.org/present-proof/3.0/request-presentation"; 8 | 9 | export class RequestPresentationMessage implements IDIDCommMessage { 10 | payload: { 11 | id: string; 12 | type: string; 13 | from: string; 14 | thid: string; 15 | to: string[]; 16 | created_time?: number; 17 | body: {}; 18 | attachments: IDIFPresentationExchangeDefinitionAttachment[]; 19 | }; 20 | repudiable = false; 21 | 22 | constructor( 23 | from: string, 24 | to: string[], 25 | thid: string, 26 | attachments: IDIFPresentationExchangeDefinitionAttachment[] 27 | ) { 28 | const id = sha256(nanoid()); 29 | this.payload = { 30 | id, 31 | from, 32 | type: REQUEST_PRESENTATION_TYPE, 33 | thid, 34 | to, 35 | created_time: Math.floor(Date.now() / 1000), 36 | body: {}, 37 | attachments, 38 | }; 39 | } 40 | } 41 | 42 | export class RequestPresentationMessageHandler 43 | implements IDIDCommMessageHandler { 44 | type = REQUEST_PRESENTATION_TYPE; 45 | callback: ( 46 | msg: RequestPresentationMessage, 47 | didcomm: IDIDComm 48 | ) => Promise; 49 | 50 | constructor( 51 | callback: ( 52 | msg: RequestPresentationMessage, 53 | didcomm: IDIDComm 54 | ) => Promise 55 | ) { 56 | this.callback = callback; 57 | } 58 | 59 | async handle(props: { 60 | message: any; 61 | didcomm: IDIDComm; 62 | }): Promise { 63 | console.log( 64 | `Request Presentation Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 65 | ); 66 | await this.callback(props.message, props.didcomm); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/lib/protocols/out-of-band/2.0/invitation.ts: -------------------------------------------------------------------------------- 1 | import { base64url, sha256 } from "@aviarytech/crypto" 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | 4 | export const INVITATION_MESSAGE_TYPE = 5 | "https://didcomm.org/out-of-band/2.0/invitation"; 6 | 7 | export class InvitationMessage implements IDIDCommMessage { 8 | url: string; 9 | payload: { 10 | id: string; 11 | type: string; 12 | from: string; 13 | created_time?: number; 14 | body: { 15 | goal_code?: string; 16 | goal?: string; 17 | accept: string[]; 18 | }; 19 | }; 20 | repudiable = false; 21 | 22 | constructor( 23 | from: string, 24 | basePath: string, 25 | goal_code?: string, 26 | goal?: string 27 | ) { 28 | const created = Math.floor(Date.now() / 1000); 29 | this.payload = { 30 | id: sha256(from + INVITATION_MESSAGE_TYPE + created), 31 | type: INVITATION_MESSAGE_TYPE, 32 | created_time: created, 33 | from, 34 | body: { 35 | accept: ["didcomm/v2"], 36 | }, 37 | }; 38 | if (goal_code) { 39 | this.payload.body.goal_code = goal_code; 40 | } 41 | if (goal) { 42 | this.payload.body.goal = goal; 43 | } 44 | 45 | const payload = JSON.stringify(this.payload); 46 | this.url = basePath + "?_oob=" + base64url.encode(payload); 47 | } 48 | } 49 | 50 | export class InvitationMessageHandler implements IDIDCommMessageHandler { 51 | type = INVITATION_MESSAGE_TYPE; 52 | callback: (msg: IDIDCommMessage, didcomm: IDIDComm) => Promise; 53 | 54 | constructor( 55 | callback: (payload: IDIDCommMessage, didcomm: IDIDComm) => Promise 56 | ) { 57 | this.callback = callback; 58 | } 59 | 60 | async handle(props: { 61 | message: IDIDCommMessage, 62 | didcomm: IDIDComm 63 | }): Promise { 64 | console.log( 65 | `Out of Band Invitation Message Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 66 | ); 67 | await this.callback(props.message, props.didcomm); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/protocols/issue-credential/3.0/offer-credential.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | import type { Attachment } from "didcomm-node"; 5 | 6 | export const ISSUE_CREDENTIAL_OFFER_TYPE = 7 | "https://didcomm.org/issue-credential/3.0/offer-credential"; 8 | 9 | export class IssueCredentialOfferMessage implements IDIDCommMessage { 10 | payload: { 11 | id: string; 12 | type: string; 13 | from?: string; 14 | thid?: string; 15 | to?: string[]; 16 | created_time?: number; 17 | body: {}; 18 | attachments: Attachment[] 19 | }; 20 | repudiable = false; 21 | 22 | constructor( 23 | from: string, 24 | to: string[], 25 | thid: string, 26 | ) { 27 | const id = sha256(nanoid()) 28 | this.payload = { 29 | id, 30 | from, 31 | type: ISSUE_CREDENTIAL_OFFER_TYPE, 32 | thid, 33 | to, 34 | created_time: Math.floor(Date.now() / 1000), 35 | body: {}, 36 | attachments: [] 37 | }; 38 | } 39 | } 40 | 41 | export class IssueCredentialOfferMessageHandler implements IDIDCommMessageHandler { 42 | type = ISSUE_CREDENTIAL_OFFER_TYPE; 43 | callback: (msg: IssueCredentialOfferMessage, didcomm: IDIDComm) => Promise; 44 | 45 | constructor( 46 | callback: (msg: IssueCredentialOfferMessage, didcomm: IDIDComm) => Promise 47 | ) { 48 | this.callback = callback; 49 | } 50 | 51 | async sendingHook(props: { message: IDIDCommMessage; didcomm: IDIDComm; }) { 52 | const { payload } = props.message; 53 | if(!payload.thid && payload.from) { 54 | props.didcomm.threads.addThread(payload.id, payload.from) 55 | } 56 | } 57 | 58 | async handle(props: { 59 | message: any, 60 | didcomm: IDIDComm 61 | }): Promise { 62 | console.log( 63 | `Issue Credential - Offer Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 64 | ); 65 | await this.callback(props.message, props.didcomm); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/lib/protocols/trust-ping/2.0/ping.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 2 | import { sha256 } from "@aviarytech/crypto"; 3 | import { TRUST_PING_RESPONSE_PING_TYPE, type TrustPingResponseMessage } from "$lib/protocols/trust-ping/2.0/ping-response.js"; 4 | 5 | 6 | export const TRUST_PING_PING_TYPE = "https://didcomm.org/trust-ping/2.0/ping"; 7 | 8 | export interface TrustPingMessage extends IDIDCommMessage { 9 | payload: { 10 | id: string; 11 | type: string; 12 | created_time?: number; 13 | from?: string; 14 | to?: string[]; 15 | body: { 16 | response_requested: boolean; 17 | }; 18 | }; 19 | } 20 | 21 | export class DefaultTrustPingMessageHandler implements IDIDCommMessageHandler { 22 | type = TRUST_PING_PING_TYPE; 23 | 24 | async sendingHook(props: { 25 | message: TrustPingMessage; 26 | didcomm: IDIDComm; 27 | }): Promise { 28 | if(props.message.payload.from && props.message.payload.body.response_requested) { 29 | props.didcomm.threads.addThread(props.message.payload.id, props.message.payload.from) 30 | } 31 | } 32 | 33 | async handle(props: { 34 | message: TrustPingMessage; 35 | didcomm: IDIDComm; 36 | }): Promise { 37 | const { payload } = props.message; 38 | if (payload.body.response_requested) { 39 | if (!payload.from) { 40 | console.error( 41 | `Error in Trust Ping Protocol: response requested but "from" property not included` 42 | ); 43 | } 44 | const responseMessage: TrustPingResponseMessage = { 45 | payload: { 46 | id: sha256(payload.id), 47 | thid: payload.id, 48 | created_time: Math.floor(Date.now() / 1000), 49 | type: TRUST_PING_RESPONSE_PING_TYPE, 50 | body: {} 51 | }, 52 | repudiable: false, 53 | }; 54 | if (!payload.from) { 55 | console.error(`No 'from' did found to to send trust ping response to`) 56 | } else { 57 | await props.didcomm.sendMessage(payload.from, responseMessage); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/protocols/issue-credential/3.0/request-credential.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler } from "$lib/interfaces.js"; 3 | import { sha256 } from "@aviarytech/crypto"; 4 | import type { Attachment } from "didcomm-node"; 5 | 6 | export const ISSUE_CREDENTIAL_REQUEST_TYPE = 7 | "https://didcomm.org/issue-credential/3.0/request-credential"; 8 | 9 | export class IssueCredentialRequestMessage implements IDIDCommMessage { 10 | payload: { 11 | id: string; 12 | type: string; 13 | from?: string; 14 | thid?: string; 15 | to?: string[]; 16 | created_time?: number; 17 | body: {}; 18 | attachments: Attachment[] 19 | }; 20 | repudiable = false; 21 | 22 | constructor( 23 | from: string, 24 | to: string[], 25 | thid: string, 26 | ) { 27 | const id = sha256(nanoid()) 28 | this.payload = { 29 | id, 30 | from, 31 | type: ISSUE_CREDENTIAL_REQUEST_TYPE, 32 | thid, 33 | to, 34 | created_time: Math.floor(Date.now() / 1000), 35 | body: {}, 36 | attachments: [] 37 | }; 38 | } 39 | } 40 | 41 | export class IssueCredentialRequestMessageHandler implements IDIDCommMessageHandler { 42 | type = ISSUE_CREDENTIAL_REQUEST_TYPE; 43 | callback: (msg: IssueCredentialRequestMessage, didcomm: IDIDComm) => Promise; 44 | 45 | constructor( 46 | callback: (msg: IssueCredentialRequestMessage, didcomm: IDIDComm) => Promise 47 | ) { 48 | this.callback = callback; 49 | } 50 | 51 | async sendingHook(props: { message: IDIDCommMessage; didcomm: IDIDComm; }) { 52 | const { payload } = props.message; 53 | if(!payload.thid && payload.from) { 54 | props.didcomm.threads.addThread(payload.id, payload.from) 55 | } 56 | } 57 | 58 | async handle(props: { 59 | message: any, 60 | didcomm: IDIDComm 61 | }): Promise { 62 | console.log( 63 | `Issue Credential - Request Received: ${props.message.payload.id}, sent at ${props.message.payload.created_time}` 64 | ); 65 | await this.callback(props.message, props.didcomm); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/lib/core.ts: -------------------------------------------------------------------------------- 1 | import { type IJWE, JsonWebEncryptionSuite, X25519KeyAgreementKey2019 } from "@aviarytech/crypto"; 2 | import type { 3 | IDIDCommPayload, 4 | IDIDResolver, 5 | ISecretResolver, 6 | } from "$lib/interfaces.js"; 7 | import { DIDCOMM_MESSAGE_MEDIA_TYPE } from "$lib/constants.js"; 8 | import { DIDDocumentVerificationMethod } from "@aviarytech/dids"; 9 | 10 | export class DIDCommCore { 11 | constructor( 12 | private didResolver: IDIDResolver, 13 | private secretResolver: ISecretResolver 14 | ) {} 15 | 16 | async packMessage(did: string, payload: IDIDCommPayload): Promise { 17 | try { 18 | // get the key agreement keys 19 | const didDoc = await this.didResolver.resolve(did); 20 | let kaks = didDoc.getAllKeyAgreements(); 21 | if (kaks.length === 0) throw new Error(`No keyAgreement found for ${did}`) 22 | 23 | const encrypter = new JsonWebEncryptionSuite().createEncrypter(); 24 | const publicKeyResolver = async (id: string) => { 25 | const key = kaks.find((k: any) => id === k.id); 26 | if (key) { 27 | return await new DIDDocumentVerificationMethod(key).asJsonWebKey() 28 | } 29 | throw new Error( 30 | "publicKeyResolver does not suppport IRI " + JSON.stringify(id) 31 | ); 32 | }; 33 | 34 | const recipients = kaks.map((k: any) => { 35 | return { 36 | header: { 37 | kid: k.id, 38 | alg: "ECDH-ES+A256KW", 39 | }, 40 | }; 41 | }); 42 | 43 | const jwe = await encrypter.encrypt({ 44 | data: payload, 45 | recipients, 46 | publicKeyResolver, 47 | }); 48 | 49 | return jwe; 50 | } catch (e) { 51 | throw e; 52 | } 53 | } 54 | 55 | async unpackMessage( 56 | jwe: IJWE, 57 | mediaType: DIDCOMM_MESSAGE_MEDIA_TYPE 58 | ): Promise { 59 | if (mediaType === DIDCOMM_MESSAGE_MEDIA_TYPE.ENCRYPTED) { 60 | const decrypter = new JsonWebEncryptionSuite().createDecrypter(); 61 | let keys = (await Promise.all( 62 | jwe.recipients?.map((r) => this.secretResolver.resolve(r.header.kid)) ?? [] 63 | )).filter((k) => k); 64 | if (keys.length === 0) { 65 | throw new Error(`No matching keys found in the recipients list`); 66 | } 67 | const jwk = await keys[0].asJsonWebKey(); 68 | const keyAgreementKey = await X25519KeyAgreementKey2019.fromJWK(jwk); 69 | try { 70 | return decrypter.decrypt({ jwe, keyAgreementKey }); 71 | } catch (e) { 72 | // console.log(e); 73 | } 74 | } else if (mediaType === DIDCOMM_MESSAGE_MEDIA_TYPE.SIGNED) { 75 | // not yet supported. 76 | throw new Error(`${mediaType} not yet supported`); 77 | } 78 | throw Error(`DIDComm media type not supported: ${mediaType}`); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/lib/mapped-resolvers.ts: -------------------------------------------------------------------------------- 1 | import type { IDIDResolver, ISecretResolver } from "@aviarytech/dids" 2 | import type { DIDDoc, Secret } from "didcomm-node" 3 | 4 | export const DIDCommDIDResolver = (didResolver: IDIDResolver) => ({ 5 | resolve: async (did: string): Promise => { 6 | const doc = await didResolver.resolve(did) 7 | if (doc) { 8 | const newDoc = { 9 | id: doc.id, 10 | keyAgreement: doc.keyAgreement?.map(k => typeof k === 'string' ? k : k.id) ?? [], 11 | authentication: doc.authentication?.map(k => typeof k === 'string' ? k :k.id) ?? [], 12 | service: doc.service?.map(s => ({ 13 | id: typeof s === 'string' ? s : s.id, 14 | type: typeof s === 'string' ? s : s.type, 15 | serviceEndpoint: typeof s === 'string' ? s : { 16 | uri: s.serviceEndpoint.uri, 17 | accept: s.serviceEndpoint.accept, 18 | routingKeys: s.serviceEndpoint.routingKeys ?? [] 19 | }})) ?? [], 20 | verificationMethod: doc.verificationMethod?.map(v => { 21 | const type = v.type; 22 | const format = type === 'JsonWebKey2020' ? 'JWK' : type === 'X25519KeyAgreementKey2019' || type === 'Ed25519VerificationKey2018' ? 'Base58' : type === 'X25519KeyAgreementKey2020' || type === 'Ed25519VerificationKey2020' ? 'Multibase' : type; 23 | const value = format === 'JWK' ? v.publicKeyJwk : format === 'Base58' ? v.publicKeyBase58 : format === 'Multibase' ? v.publicKeyMultibase : null; 24 | const key = type === 'JsonWebKey2020' ? 'publicKeyJwk' : type === 'X25519KeyAgreementKey2019' || type === 'Ed25519VerificationKey2018' ? 'publicKeyBase58' : type === 'X25519KeyAgreementKey2020' || type === 'Ed25519VerificationKey2020' ? 'publicKeyMultibase' : type; 25 | return { 26 | id: v.id, 27 | type, 28 | controller: v.controller, 29 | [key] : value 30 | } 31 | }) ?? [] 32 | }; 33 | console.log(`transformed did doc for sicpa`, JSON.stringify(newDoc, null, 2)); 34 | return newDoc 35 | } 36 | return null; 37 | } 38 | }) 39 | 40 | export class DIDCommSecretResolver { 41 | constructor(private secretResolver: ISecretResolver) {} 42 | 43 | async get_secret(id: string): Promise { 44 | const doc = await this.secretResolver.resolve(id) 45 | if (doc) { 46 | const type = doc.type; 47 | const format = type === 'JsonWebKey2020' ? 'JWK' : type === 'X25519KeyAgreementKey2019' || type === 'Ed25519VerificationKey2018' ? 'Base58' : type === 'X25519KeyAgreementKey2020' || type === 'Ed25519VerificationKey2020' ? 'Multibase' : type; 48 | const value = format === 'JWK' ? doc.privateKeyJwk : format === 'Base58' ? doc.privateKeyBase58 : format === 'Multibase' ? doc.privateKeyMultibase : null; 49 | const key = type === 'JsonWebKey2020' ? 'privateKeyJwk' : type === 'X25519KeyAgreementKey2019' || type === 'Ed25519VerificationKey2018' ? 'privateKeyBase58' : type === 'X25519KeyAgreementKey2020' || type === 'Ed25519VerificationKey2020' ? 'privateKeyMultibase' : type; 50 | 51 | return { 52 | id: doc.id, 53 | type, 54 | [key]: value 55 | } 56 | } 57 | return null; 58 | } 59 | 60 | async find_secrets(ids: string[]): Promise { 61 | const secrets = []; 62 | for (let i = 0; i < ids.length; i++) { 63 | const secret = await this.get_secret(ids[i]) 64 | if (secret) { 65 | secrets.push(secret.id) 66 | } 67 | } 68 | return secrets; 69 | } 70 | } -------------------------------------------------------------------------------- /src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { IJWE, IJWS, JsonWebKey2020 } from "@aviarytech/crypto"; 2 | import type { DIDCOMM_MESSAGE_MEDIA_TYPE } from "$lib/constants.js"; 3 | import type { IDIDDocument } from "@aviarytech/dids"; 4 | import type { Attachment } from "didcomm-node"; 5 | import type { PackEncryptedMetadata } from 'didcomm-node'; 6 | import type { DIDCommThreads } from "./threads"; 7 | 8 | export interface IDIDCommAttachment { 9 | id?: string; 10 | description?: string; 11 | filename?: string; 12 | media_type?: string; 13 | format?: string; 14 | lastmod_time?: string; 15 | byte_count?: number; 16 | data: { 17 | jws?: IJWS; 18 | hash?: string; 19 | links?: string[]; 20 | base64?: string; 21 | json?: any; 22 | }; 23 | } 24 | 25 | export interface IDIDCommPayload { 26 | id: string; 27 | type: string; 28 | from?: string; 29 | to?: string[]; 30 | thid?: string; 31 | pthid?: string; 32 | expires_time?: number; 33 | created_time?: number; 34 | next?: string; 35 | from_prior?: string; 36 | body: any; 37 | attachments?: Attachment[]; 38 | } 39 | 40 | export interface IDIDCommCore { 41 | packMessage(did: string, payload: IDIDCommPayload): Promise; 42 | unpackMessage( 43 | jwe: IJWE, 44 | mediaType: DIDCOMM_MESSAGE_MEDIA_TYPE 45 | ): Promise; 46 | } 47 | 48 | 49 | /** 50 | * A secret. 51 | */ 52 | export interface ISecret { 53 | id: string; 54 | type: string; 55 | /** The value of the private key in PEM format. Only one value field will be present. */ 56 | privateKeyPem?: string; 57 | 58 | /** The value of the private key in JWK format. Only one value field will be present. */ 59 | privateKeyJwk?: any; 60 | 61 | /** The value of the private key in hex format. Only one value field will be present. */ 62 | privateKeyHex?: string; 63 | 64 | /** The value of the private key in Base64 format. Only one value field will be present. */ 65 | privateKeyBase64?: string; 66 | 67 | /** The value of the private key in Base58 format. Only one value field will be present. */ 68 | privateKeyBase58?: string; 69 | 70 | /** The value of the private key in Multibase format. Only one value field will be present. */ 71 | privateKeyMultibase?: string; 72 | 73 | asJsonWebKey(): Promise; 74 | } 75 | 76 | export interface IJWK { 77 | alg?: string; 78 | crv: string; 79 | d?: string; 80 | dp?: string; 81 | dq?: string; 82 | e?: string; 83 | ext?: boolean; 84 | k?: string; 85 | key_ops?: string[]; 86 | kid?: string; 87 | kty: string; 88 | n?: string; 89 | oth?: Array<{ 90 | d?: string; 91 | r?: string; 92 | t?: string; 93 | }>; 94 | p?: string; 95 | q?: string; 96 | qi?: string; 97 | use?: string; 98 | x?: string; 99 | y?: string; 100 | x5c?: string[]; 101 | x5t?: string; 102 | 'x5t#S256'?: string; 103 | x5u?: string; 104 | [propName: string]: unknown 105 | } 106 | 107 | export interface ISecretResolver { 108 | resolve(id: string): Promise; 109 | } 110 | 111 | export interface IDIDResolver { 112 | resolve(id: string): Promise; 113 | } 114 | 115 | export interface IDIDCommMessage { 116 | payload: IDIDCommPayload; 117 | repudiable: boolean; 118 | signature?: string; 119 | } 120 | 121 | export interface IDIDCommMessageHandler { 122 | type: string; 123 | sendingHook?: (props: { message: IDIDCommMessage, didcomm: IDIDComm}) => Promise; 124 | handle: (props: { message: IDIDCommMessage, didcomm: IDIDComm}) => Promise; 125 | } 126 | 127 | export interface IDIDComm { 128 | threads: DIDCommThreads; 129 | handleMessage(message: IDIDCommMessage): void; 130 | sendMessage: (did: string, message: IDIDCommMessage) => Promise; 131 | packMessage: (did: string, message: IDIDCommMessage) => Promise; 132 | sendPackedMessage: (did: string, jwe: string, serviceId?: string) => Promise; 133 | receiveMessage( 134 | msg: string, 135 | mediaType: string 136 | ): Promise; 137 | } 138 | -------------------------------------------------------------------------------- /tests/didcomm.spec.ts: -------------------------------------------------------------------------------- 1 | import { DIDComm } from "$lib"; 2 | import { DIDCOMM_MESSAGE_MEDIA_TYPE } from "$lib/constants"; 3 | import { DIDDocument, JSONSecretResolver } from "@aviarytech/dids"; 4 | import { afterEach, beforeAll, beforeEach, expect, test, vi } from "vitest" 5 | const aliceDidDoc = require("./fixtures/didDocs/alice.json"); 6 | const bobDidDoc = require("./fixtures/didDocs/bob.json"); 7 | const didDocNoKAK = require("./fixtures/didDocs/did-no-kak.json"); 8 | const alice = require("./fixtures/keys/alice.json"); 9 | const bob = require("./fixtures/keys/bob.json"); 10 | const document = require("./fixtures/document.json"); 11 | const jwe0 = require("./fixtures/jwes/jwe0.json"); 12 | 13 | const mockDidResolver = { 14 | resolve: async (id: string) => id === 'did:example:alice' ? new DIDDocument(aliceDidDoc) : new DIDDocument(bobDidDoc) 15 | } 16 | 17 | vi.mock('cross-fetch', () => { 18 | return { 19 | default: vi 20 | .fn() 21 | .mockResolvedValueOnce({ data: "OK", status: 200 }) 22 | .mockResolvedValueOnce({ data: "NOT OK", status: 400 }) 23 | } 24 | }) 25 | 26 | test.only("didcomm can send message to did", async () => { 27 | const secretResolver = new JSONSecretResolver([alice, bob]); 28 | const didcomm = new DIDComm([], mockDidResolver, secretResolver, "http://example.com"); 29 | const spy = vi.spyOn(didcomm, 'sendPackedMessage') 30 | 31 | const res = await didcomm.sendMessage("did:example:bob", { 32 | payload: document, 33 | repudiable: false, 34 | }); 35 | 36 | expect(res).toBeTruthy(); 37 | expect(spy).toHaveBeenCalled() 38 | }); 39 | 40 | // test.only("didcomm can not send message to did with 400", async () => { 41 | // const secretResolver = new JSONSecretResolver(key1); 42 | // const didcomm = new DIDComm([], didResolver, secretResolver, 'http://example.com'); 43 | // const spy = vi.spyOn(didcomm, 'sendPackedMessage') 44 | 45 | // const res = await didcomm.sendMessage("did:example:bob", { 46 | // payload: document, 47 | // repudiable: false, 48 | // }); 49 | 50 | // expect(spy).toHaveBeenCalled() 51 | // expect(res).toBeFalsy(); 52 | // }); 53 | 54 | test("didcomm can't send message to did w/ no kaks", async () => { 55 | const secretResolver = new JSONSecretResolver(alice); 56 | const mockedDoc = new DIDDocument(didDocNoKAK); 57 | const didcomm = new DIDComm([], { resolve: vi.fn().mockResolvedValue(mockedDoc) }, secretResolver, 'http://example.com'); 58 | 59 | const res = await didcomm.sendMessage("did:web:example.com", { 60 | payload: document, 61 | repudiable: false, 62 | }); 63 | 64 | expect(res).toBeFalsy() 65 | }); 66 | 67 | test("didcomm can receive message w/ handler (success)", async () => { 68 | const secretResolver = new JSONSecretResolver(bob); 69 | const spy = vi.spyOn(secretResolver, 'resolve') 70 | const mockCallback = vi.fn(async (m) => {}); 71 | const didcomm = new DIDComm( 72 | [ 73 | { 74 | type: "https://didcomm.org/test", 75 | handle: mockCallback, 76 | } 77 | ], 78 | mockDidResolver, 79 | secretResolver, 80 | 'http://example.com' 81 | ); 82 | const result = await didcomm.receiveMessage( 83 | JSON.stringify(jwe0), 84 | DIDCOMM_MESSAGE_MEDIA_TYPE.ENCRYPTED 85 | ); 86 | 87 | expect(spy).toHaveBeenCalledWith('did:example:bob#key-1') 88 | expect(mockCallback.mock.calls.length).toBe(1); 89 | expect(result).toBeTruthy(); 90 | }); 91 | 92 | test("didcomm can receive message w/ handler & wildcard handler (success)", async () => { 93 | const secretResolver = new JSONSecretResolver(bob); 94 | const mockCallback = vi.fn(async (m) => {}); 95 | const otherMockCallback = vi.fn(async (m) => {}); 96 | const didcomm = new DIDComm( 97 | [ 98 | { 99 | type: "https://didcomm.org/test", 100 | handle: mockCallback, 101 | }, 102 | { 103 | type: "*", 104 | handle: otherMockCallback 105 | } 106 | ], 107 | mockDidResolver, 108 | secretResolver, 109 | 'http://example.com' 110 | ); 111 | 112 | const result = await didcomm.receiveMessage( 113 | JSON.stringify(jwe0), 114 | DIDCOMM_MESSAGE_MEDIA_TYPE.ENCRYPTED 115 | ); 116 | 117 | expect(result).toBeTruthy(); 118 | expect(mockCallback.mock.calls.length).toBe(1); 119 | expect(otherMockCallback.mock.calls.length).toBe(1); 120 | }); 121 | 122 | test("didcomm can receive message w/o handler", async () => { 123 | const secretResolver = new JSONSecretResolver(bob); 124 | const didcomm = new DIDComm([], mockDidResolver, secretResolver, 'http://example.com'); 125 | 126 | const result = await didcomm.receiveMessage( 127 | JSON.stringify(jwe0), 128 | DIDCOMM_MESSAGE_MEDIA_TYPE.ENCRYPTED 129 | ); 130 | 131 | expect(result).toBeTruthy(); 132 | }); 133 | 134 | test("didcomm can receive plaintext message w/ handler (success)", async () => { 135 | const secretResolver = new JSONSecretResolver(bob); 136 | const mockCallback = vi.fn(async (m) => {}); 137 | const didcomm = new DIDComm( 138 | [ 139 | { 140 | type: "https://didcomm.org/test", 141 | handle: mockCallback, 142 | }, 143 | ], 144 | mockDidResolver, 145 | secretResolver, 146 | 'http://example.com' 147 | ); 148 | 149 | const result = await didcomm.receiveMessage( 150 | JSON.stringify({ type: "https://didcomm.org/test", id: "123", body: {} }), 151 | DIDCOMM_MESSAGE_MEDIA_TYPE.PLAIN 152 | ); 153 | 154 | expect(result).toBeTruthy(); 155 | expect(mockCallback.mock.calls.length).toBe(1); 156 | }); 157 | -------------------------------------------------------------------------------- /src/lib/messaging.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'cross-fetch'; 2 | import { DIDCOMM_MESSAGE_MEDIA_TYPE } from "$lib/constants.js"; 3 | import { EventBus } from "$lib/event-bus.js"; 4 | import type { IDIDComm, IDIDCommMessage, IDIDCommMessageHandler, IDIDCommPayload, IDIDResolver, ISecretResolver } from "$lib/interfaces.js"; 5 | import type { DIDDoc, DIDResolver, SecretsResolver, Service } from 'didcomm-node'; 6 | import { DIDCommDIDResolver, DIDCommSecretResolver } from '$lib/mapped-resolvers.js'; 7 | import { DIDCommThreads } from './threads.js'; 8 | 9 | export class DIDComm implements IDIDComm { 10 | public threads: DIDCommThreads; 11 | private messageBus: EventBus; 12 | private sendingHooksBus: EventBus; 13 | private myURL: string; 14 | private didResolver: DIDResolver; 15 | private secretResolver: SecretsResolver; 16 | private messagesReceived: string[]; 17 | private messagesSent: string[]; 18 | 19 | constructor( 20 | private messageHandlers: IDIDCommMessageHandler[], 21 | _didResolver: IDIDResolver, 22 | _secretResolver: ISecretResolver, 23 | _myURL: string 24 | ) { 25 | this.threads = new DIDCommThreads(); 26 | this.myURL = _myURL; 27 | this.didResolver = DIDCommDIDResolver(_didResolver); 28 | this.secretResolver = new DIDCommSecretResolver(_secretResolver); 29 | this.messageBus = new EventBus(); 30 | this.sendingHooksBus = new EventBus(); 31 | this.messagesReceived = []; 32 | this.messagesSent = []; 33 | messageHandlers.forEach((handler) => { 34 | this.messageBus.register(handler.type, handler); 35 | if (handler.sendingHook) { 36 | this.sendingHooksBus.register(handler.type, { handle: handler.sendingHook }) 37 | } 38 | }); 39 | } 40 | 41 | handleMessage(message: IDIDCommMessage): void { 42 | if (this.messageHandlers.find((h) => h.type === "*")) { 43 | // Always handle wildcard handlers 44 | this.messageBus.dispatch("*", { 45 | message, 46 | didcomm: this 47 | }) 48 | } 49 | if (this.messageHandlers.find((h) => h.type === message.payload.type)) { 50 | if (!this.messagesReceived.includes(message.payload.id)) { 51 | this.messagesReceived = [message.payload.id, ...this.messagesReceived]; 52 | this.messageBus.dispatch(message.payload.type, { 53 | message, 54 | didcomm: this, 55 | }); 56 | } else { 57 | console.error(`attempted to handle duplicate message: ${message.payload.id}`) 58 | } 59 | } 60 | } 61 | 62 | async sendPackedMessage(did: string, jwe: string, serviceId?: string): Promise { 63 | let didDoc: DIDDoc | null; 64 | let service: Service | undefined; 65 | let serviceEndpoint: string; 66 | try { 67 | didDoc = await this.didResolver.resolve(did); 68 | } catch (e: any) { 69 | console.error(`Failed to resolve did ${did}:`, e.message) 70 | return false; 71 | } 72 | try { 73 | service = serviceId 74 | ? didDoc?.service.find(s => s.id === serviceId) 75 | : didDoc?.service && didDoc.service.length > 0 ? didDoc?.service[0] : undefined 76 | serviceEndpoint = service?.serviceEndpoint?.uri; 77 | if (!serviceEndpoint) 78 | throw new Error("service endpoint not found"); 79 | console.log(serviceEndpoint, this.myURL) 80 | if (serviceEndpoint === this.myURL) { 81 | console.log('Not sending didcomm message to my own endpoint') 82 | return true; 83 | } 84 | const resp = await fetch(serviceEndpoint, { 85 | method: 'POST', 86 | mode: 'cors', 87 | redirect: 'manual', 88 | headers: { 89 | 'Content-Type': DIDCOMM_MESSAGE_MEDIA_TYPE.ENCRYPTED 90 | }, 91 | body: jwe 92 | }); 93 | if(resp.status.toString().at(0) === '2') { 94 | return true; 95 | } 96 | console.error(`Failed to send message to ${serviceEndpoint}, status: ${resp.status}`) 97 | console.error(resp.statusText) 98 | return false; 99 | } catch (e: any) { 100 | console.error(e.message) 101 | return false; 102 | } 103 | } 104 | 105 | async sendMessage( 106 | did: string, 107 | message: IDIDCommMessage, 108 | serviceId?: string 109 | ): Promise { 110 | try { 111 | this.messagesSent = [message.payload.id, ...this.messagesSent]; 112 | if (this.messageHandlers.find((h) => h.type === message.payload.type)) { 113 | this.sendingHooksBus.dispatch(message.payload.type, { 114 | message, 115 | didcomm: this, 116 | }); 117 | } 118 | const encryptedMsg = await this.packMessage(did, message) 119 | return await this.sendPackedMessage(did, encryptedMsg, serviceId) 120 | } catch (e: any) { 121 | console.error(e.message) 122 | return false; 123 | } 124 | } 125 | 126 | async packMessage( 127 | did: string, 128 | message: IDIDCommMessage 129 | ): Promise { 130 | let didcomm = typeof self === 'undefined' ? await import('didcomm-node') : await import('didcomm') 131 | const msg = new didcomm.Message({ 132 | typ: 'application/didcomm-plain+json', 133 | ...message.payload 134 | }); 135 | 136 | const [encryptedMsg, encryptMetadata] = await msg.pack_encrypted( 137 | did, 138 | null, 139 | null, 140 | this.didResolver, 141 | this.secretResolver, 142 | { 143 | forward: true 144 | } 145 | ); 146 | return encryptedMsg 147 | } 148 | 149 | async receiveMessage( 150 | msg: string, 151 | mediaType: string, 152 | allowReplay = false 153 | ): Promise { 154 | let didcomm = typeof self === 'undefined' ? await import('didcomm-node') : await import('didcomm') 155 | let finalMessage: IDIDCommPayload; 156 | msg = typeof msg === 'object' ? JSON.stringify(msg) : msg; 157 | try { 158 | if (mediaType === DIDCOMM_MESSAGE_MEDIA_TYPE.ENCRYPTED) { 159 | const [unpackedMsg, unpackMetadata] = await didcomm.Message.unpack(msg, this.didResolver, this.secretResolver, {}) 160 | finalMessage = unpackedMsg.as_value() 161 | } else if (mediaType === DIDCOMM_MESSAGE_MEDIA_TYPE.PLAIN) { 162 | finalMessage = JSON.parse(msg); 163 | } else { 164 | throw new Error(`Unsupported Media Type: ${mediaType}`); 165 | } 166 | if (allowReplay) { 167 | this.messagesReceived = [...this.messagesReceived.filter(m => m !== finalMessage.id)] 168 | } 169 | console.log(`DIDComm received ${finalMessage.type} message`); 170 | this.handleMessage({ payload: finalMessage, repudiable: false }); 171 | return true; 172 | } catch (e: any) { 173 | console.error(e); 174 | return false; 175 | } 176 | } 177 | } --------------------------------------------------------------------------------