generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 56
/
Copy pathrpc-client.ts
162 lines (127 loc) · 4.83 KB
/
rpc-client.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { CryptoUtils } from '@web5/crypto';
import type { DwnRpc, DwnRpcRequest, DwnRpcResponse } from './prototyping/clients/dwn-rpc-types.js';
import type { DwnServerInfoRpc, ServerInfo } from './prototyping/clients/server-info-types.js';
import type { JsonRpcResponse } from './prototyping/clients/json-rpc.js';
import { createJsonRpcRequest } from './prototyping/clients/json-rpc.js';
import { HttpDwnRpcClient } from './prototyping/clients/http-dwn-rpc-client.js';
import { WebSocketDwnRpcClient } from './prototyping/clients/web-socket-clients.js';
/**
* Interface that can be implemented to communicate with {@link Web5Agent | Web5 Agent}
* implementations via JSON-RPC.
*/
export interface DidRpc {
get transportProtocols(): string[]
sendDidRequest(request: DidRpcRequest): Promise<DidRpcResponse>
}
export enum DidRpcMethod {
Create = 'did.create',
Resolve = 'did.resolve'
}
export type DidRpcRequest = {
data: string;
method: DidRpcMethod;
url: string;
}
export type DidRpcResponse = {
data?: string;
ok: boolean;
status: RpcStatus;
}
export type RpcStatus = {
code: number;
message: string;
};
export interface Web5Rpc extends DwnRpc, DidRpc, DwnServerInfoRpc {}
/**
* Client used to communicate with Dwn Servers
*/
export class Web5RpcClient implements Web5Rpc {
private transportClients: Map<string, Web5Rpc>;
constructor(clients: Web5Rpc[] = []) {
this.transportClients = new Map();
// include http and socket clients as default.
// can be overwritten for 'http:', 'https:', 'ws: or ':wss' if instantiated with other clients.
clients = [new HttpWeb5RpcClient(), new WebSocketWeb5RpcClient(), ...clients];
for (let client of clients) {
for (let transportScheme of client.transportProtocols) {
this.transportClients.set(transportScheme, client);
}
}
}
get transportProtocols(): string[] {
return Array.from(this.transportClients.keys());
}
async sendDidRequest(request: DidRpcRequest): Promise<DidRpcResponse> {
// URL() will throw if provided `url` is invalid.
const url = new URL(request.url);
const transportClient = this.transportClients.get(url.protocol);
if (!transportClient) {
const error = new Error(`no ${url.protocol} transport client available`);
error.name = 'NO_TRANSPORT_CLIENT';
throw error;
}
return transportClient.sendDidRequest(request);
}
sendDwnRequest(request: DwnRpcRequest): Promise<DwnRpcResponse> {
// will throw if url is invalid
const url = new URL(request.dwnUrl);
const transportClient = this.transportClients.get(url.protocol);
if (!transportClient) {
const error = new Error(`no ${url.protocol} transport client available`);
error.name = 'NO_TRANSPORT_CLIENT';
throw error;
}
return transportClient.sendDwnRequest(request);
}
async getServerInfo(dwnUrl: string): Promise<ServerInfo> {
// will throw if url is invalid
const url = new URL(dwnUrl);
const transportClient = this.transportClients.get(url.protocol);
if(!transportClient) {
const error = new Error(`no ${url.protocol} transport client available`);
error.name = 'NO_TRANSPORT_CLIENT';
throw error;
}
return transportClient.getServerInfo(dwnUrl);
}
}
export class HttpWeb5RpcClient extends HttpDwnRpcClient implements Web5Rpc {
async sendDidRequest(request: DidRpcRequest): Promise<DidRpcResponse> {
const requestId = CryptoUtils.randomUuid();
const jsonRpcRequest = createJsonRpcRequest(requestId, request.method, {
data: request.data
});
const httpRequest = new Request(request.url, {
method : 'POST',
headers : {
'Content-Type': 'application/json',
},
body: JSON.stringify(jsonRpcRequest),
});
let jsonRpcResponse: JsonRpcResponse;
try {
const response = await fetch(httpRequest);
if (response.ok) {
jsonRpcResponse = await response.json();
// If the response is an error, throw an error.
if (jsonRpcResponse.error) {
const { code, message } = jsonRpcResponse.error;
throw new Error(`JSON RPC (${code}) - ${message}`);
}
} else {
throw new Error(`HTTP (${response.status}) - ${response.statusText}`);
}
} catch (error: any) {
throw new Error(`Error encountered while processing response from ${request.url}: ${error.message}`);
}
return jsonRpcResponse.result as DidRpcResponse;
}
}
export class WebSocketWeb5RpcClient extends WebSocketDwnRpcClient implements Web5Rpc {
async sendDidRequest(_request: DidRpcRequest): Promise<DidRpcResponse> {
throw new Error(`not implemented for transports [${this.transportProtocols.join(', ')}]`);
}
async getServerInfo(_dwnUrl: string): Promise<ServerInfo> {
throw new Error(`not implemented for transports [${this.transportProtocols.join(', ')}]`);
}
}