diff --git a/docs/api/daf-express.agentrouteroptions.md b/docs/api/daf-express.agentrouteroptions.md
index a7823944a..abfbb7e71 100644
--- a/docs/api/daf-express.agentrouteroptions.md
+++ b/docs/api/daf-express.agentrouteroptions.md
@@ -18,4 +18,5 @@ export interface AgentRouterOptions
| [exposedMethods](./daf-express.agentrouteroptions.exposedmethods.md) | Array<string> | List of exposed methods |
| [extraMethods](./daf-express.agentrouteroptions.extramethods.md) | Array<string> | List of extra methods |
| [getAgentForRequest](./daf-express.agentrouteroptions.getagentforrequest.md) | (req: Request) => Promise<[IAgent](./daf-core.iagent.md)> | Function that returns configured agent for specific request |
+| [serveSchema](./daf-express.agentrouteroptions.serveschema.md) | boolean | If set to true
, router will serve OpenAPI schema JSON on /
route |
diff --git a/docs/api/daf-express.agentrouteroptions.serveschema.md b/docs/api/daf-express.agentrouteroptions.serveschema.md
new file mode 100644
index 000000000..9268f1372
--- /dev/null
+++ b/docs/api/daf-express.agentrouteroptions.serveschema.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [daf-express](./daf-express.md) > [AgentRouterOptions](./daf-express.agentrouteroptions.md) > [serveSchema](./daf-express.agentrouteroptions.serveschema.md)
+
+## AgentRouterOptions.serveSchema property
+
+If set to `true`, router will serve OpenAPI schema JSON on `/` route
+
+Signature:
+
+```typescript
+serveSchema?: boolean;
+```
diff --git a/packages/daf-cli/package.json b/packages/daf-cli/package.json
index d2b9a031b..87db17dd6 100644
--- a/packages/daf-cli/package.json
+++ b/packages/daf-cli/package.json
@@ -36,6 +36,7 @@
"date-fns": "^2.8.1",
"debug": "^4.1.1",
"dotenv": "^8.2.0",
+ "express": "^4.17.1",
"graphql": "^15.0.0",
"graphql-tools": "^6.0.0",
"inquirer": "^7.0.0",
diff --git a/packages/daf-cli/src/cli.ts b/packages/daf-cli/src/cli.ts
index bfb1176b6..184b2f85f 100644
--- a/packages/daf-cli/src/cli.ts
+++ b/packages/daf-cli/src/cli.ts
@@ -9,6 +9,7 @@ import './msg'
import './version'
import './crypto'
import './execute'
+import './server'
import './setup'
if (!process.argv.slice(2).length) {
diff --git a/packages/daf-cli/src/graphql.ts b/packages/daf-cli/src/graphql.ts
index f791e3b4b..4b6e18537 100644
--- a/packages/daf-cli/src/graphql.ts
+++ b/packages/daf-cli/src/graphql.ts
@@ -7,7 +7,6 @@ program
.command('graphql')
.description('GraphQL server')
.option('-p, --port ', 'Port')
- .option('-i, --interval ', 'Poll for new messages with interval of ')
.action(async (cmd) => {
const agent = getAgent(program.config)
const { typeDefs, resolvers } = createSchema({
diff --git a/packages/daf-cli/src/server.ts b/packages/daf-cli/src/server.ts
new file mode 100644
index 000000000..686c12eb6
--- /dev/null
+++ b/packages/daf-cli/src/server.ts
@@ -0,0 +1,100 @@
+import express from 'express'
+import program from 'commander'
+import { AgentRouter } from 'daf-express'
+import { getAgent } from './setup'
+
+program
+ .command('server')
+ .description('Launch OpenAPI server')
+ .option('--port ', 'Port', '3332')
+ .option('--hostname ', 'Server hostname', 'localhost')
+ .option('--https ', 'Use https instead of http', true)
+ .option('--createDefaultIdentity ', 'Should the agent create default web did', true)
+ .option('--messagingServiceEndpoint ', 'Path of the messaging service endpoint', '/messaging')
+ .option(
+ '--exposedMethods ',
+ 'Comma separated list of exposed agent methods (example: "resolveDid,handleMessage")',
+ )
+ .action(async (options) => {
+ const app = express()
+ const agent = getAgent(program.config)
+
+ const exposedMethods = options.exposedMethods
+ ? options.exposedMethods.split(',')
+ : agent.availableMethods()
+
+ const agentRouter = AgentRouter({
+ getAgentForRequest: async (req) => agent,
+ exposedMethods,
+ serveSchema: true,
+ })
+
+ app.use('/', agentRouter)
+
+ if (options.createDefaultIdentity) {
+ let serverIdentity = await agent.identityManagerGetOrCreateIdentity({
+ provider: 'did:web',
+ alias: options.hostname,
+ })
+ console.log('Created:', serverIdentity.did)
+
+ if (options.messagingServiceEndpoint) {
+ const serviceEndpoint =
+ (options.https ? 'https://' : 'http://') + options.hostname + options.messagingServiceEndpoint
+
+ await agent.identityManagerAddService({
+ did: serverIdentity.did,
+ service: {
+ id: 'msg',
+ type: 'Messaging',
+ serviceEndpoint,
+ },
+ })
+ console.log('Added endpoint', serviceEndpoint)
+
+ app.post(serviceEndpoint, express.text({ type: '*/*' }), async (req, res) => {
+ try {
+ const message = await agent.handleMessage({ raw: req.body, save: true })
+ res.json({ id: message.id })
+ } catch (e) {
+ console.log(e)
+ res.send(e.message)
+ }
+ })
+ }
+
+ app.get('/.well-known/did.json', async (req, res) => {
+ serverIdentity = await agent.identityManagerGetOrCreateIdentity({
+ provider: 'did:web',
+ alias: options.hostname,
+ })
+
+ const didDoc = {
+ '@context': 'https://w3id.org/did/v1',
+ id: serverIdentity.did,
+ publicKey: serverIdentity.keys.map((key) => ({
+ id: serverIdentity.did + '#' + key.kid,
+ type: key.type === 'Secp256k1' ? 'Secp256k1VerificationKey2018' : 'Ed25519VerificationKey2018',
+ owner: serverIdentity.did,
+ publicKeyHex: key.publicKeyHex,
+ })),
+ authentication: serverIdentity.keys.map((key) => ({
+ type:
+ key.type === 'Secp256k1'
+ ? 'Secp256k1SignatureAuthentication2018'
+ : 'Ed25519SignatureAuthentication2018',
+ publicKey: serverIdentity.did + '#' + key.kid,
+ })),
+ service: serverIdentity.services,
+ }
+
+ res.json(didDoc)
+ })
+ }
+
+ app.listen(options.port, () => {
+ console.log(`🚀 Server ready at http://${options.hostname}:${options.port}`)
+ console.log('Enabled agent methods', JSON.stringify(agent.availableMethods()))
+ console.log('Exposed methods', JSON.stringify(exposedMethods))
+ })
+ })
diff --git a/packages/daf-cli/tsconfig.json b/packages/daf-cli/tsconfig.json
index d787d0299..b69ba16f6 100644
--- a/packages/daf-cli/tsconfig.json
+++ b/packages/daf-cli/tsconfig.json
@@ -17,6 +17,7 @@
{ "path": "../daf-resolver-universal" },
{ "path": "../daf-resolver" },
{ "path": "../daf-rest" },
+ { "path": "../daf-express" },
{ "path": "../daf-selective-disclosure" },
{ "path": "../daf-typeorm" },
{ "path": "../daf-url" },
diff --git a/packages/daf-express/src/index.ts b/packages/daf-express/src/index.ts
index 927c3d03b..e845da9ab 100644
--- a/packages/daf-express/src/index.ts
+++ b/packages/daf-express/src/index.ts
@@ -22,7 +22,7 @@
import { IAgent } from 'daf-core'
import { Request, Response, NextFunction, Router, json } from 'express'
-import { supportedMethods } from 'daf-rest'
+import { supportedMethods, openApiSchema } from 'daf-rest'
import Debug from 'debug'
interface RequestWithAgent extends Request {
@@ -47,6 +47,11 @@ export interface AgentRouterOptions {
* List of extra methods
*/
extraMethods?: Array
+
+ /**
+ * If set to `true`, router will serve OpenAPI schema JSON on `/` route
+ */
+ serveSchema?: boolean
}
/**
@@ -65,10 +70,15 @@ export const AgentRouter = (options: AgentRouterOptions): Router => {
const allMethods: Array = supportedMethods.concat(options.extraMethods ? options.extraMethods : [])
+ const publicApi = { ...openApiSchema }
+ publicApi.paths = {}
+
for (const exposedMethod of options.exposedMethods) {
if (!allMethods.includes(exposedMethod)) throw Error('Method not supported: ' + exposedMethod)
Debug('daf:express:initializing')(exposedMethod)
+ publicApi.paths['/' + exposedMethod] = openApiSchema.paths['/' + exposedMethod]
+
router.post('/' + exposedMethod, async (req: RequestWithAgent, res: Response, next: NextFunction) => {
if (!req.agent) throw Error('Agent not available')
try {
@@ -80,5 +90,11 @@ export const AgentRouter = (options: AgentRouterOptions): Router => {
})
}
+ if (options.serveSchema) {
+ router.get('/', (req, res) => {
+ res.json(publicApi)
+ })
+ }
+
return router
}
diff --git a/report/daf-express.api.md b/report/daf-express.api.md
index 979b9e392..2a80254f9 100644
--- a/report/daf-express.api.md
+++ b/report/daf-express.api.md
@@ -16,6 +16,7 @@ export interface AgentRouterOptions {
exposedMethods: Array;
extraMethods?: Array;
getAgentForRequest: (req: Request_2) => Promise;
+ serveSchema?: boolean;
}