├── .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 | }
--------------------------------------------------------------------------------