@covenant-rpc/server 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/lib/adapters/vanilla.ts +9 -0
- package/lib/index.ts +11 -0
- package/lib/interfaces/direct.ts +116 -0
- package/lib/interfaces/empty.ts +9 -0
- package/lib/interfaces/http.ts +111 -0
- package/lib/interfaces/mock.ts +32 -0
- package/lib/logger.ts +79 -0
- package/lib/server.ts +453 -0
- package/lib/sidekick/handlers.ts +173 -0
- package/lib/sidekick/index.ts +109 -0
- package/lib/sidekick/socket.ts +5 -0
- package/package.json +21 -0
- package/tests/channel-http.test.ts +481 -0
- package/tests/channel.test.ts +689 -0
- package/tests/procedure.test.ts +238 -0
- package/tests/sidekick.test.ts +23 -0
- package/tests/validation-types.test.ts +122 -0
- package/tests/validation.test.ts +144 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @covenant-rpc/server
|
|
2
|
+
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8061179: Initial publish
|
|
8
|
+
- Updated dependencies [8061179]
|
|
9
|
+
- @covenant-rpc/request-serializer@1.0.3
|
|
10
|
+
- @covenant-rpc/core@0.1.3
|
|
11
|
+
- @covenant-rpc/ion@1.0.3
|
|
12
|
+
|
|
13
|
+
## 0.1.2
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Fix all packages
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { CovenantServer } from "./server";
|
|
2
|
+
export { Logger } from "./logger";
|
|
3
|
+
export { vanillaAdapter } from "./adapters/vanilla";
|
|
4
|
+
export { Sidekick, type SidekickClient } from "./sidekick";
|
|
5
|
+
export { httpServerToSidekick, httpSidekickToServer } from "./interfaces/http";
|
|
6
|
+
export { emptyServerToSidekick } from "./interfaces/empty";
|
|
7
|
+
export { directClientToServer } from "./interfaces/direct";
|
|
8
|
+
export { mockClientToSidekick } from "./interfaces/mock";
|
|
9
|
+
|
|
10
|
+
// Re-export types from core for convenience
|
|
11
|
+
export type { LoggerLevel, Prefix } from "@covenant-rpc/core/logger";
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ClientToServerConnection, ServerToSidekickConnection, ClientToSidekickConnection } from "@covenant-rpc/core/interfaces";
|
|
2
|
+
import type { ProcedureRequestBody, ProcedureResponse } from "@covenant-rpc/core/procedure";
|
|
3
|
+
import type { CovenantServer } from "../server";
|
|
4
|
+
import { v } from "@covenant-rpc/core/validation";
|
|
5
|
+
import { procedureResponseSchema } from "@covenant-rpc/core/procedure";
|
|
6
|
+
import { channelConnectionRequestSchema, channelConnectionResponseSchema, type ChannelConnectionRequest, type ChannelConnectionResponse } from "@covenant-rpc/core/channel";
|
|
7
|
+
import ION from "@covenant-rpc/ion";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export function directClientToServer(
|
|
11
|
+
server: CovenantServer<any, any, any, any>,
|
|
12
|
+
extraHeaders: Record<string, string>
|
|
13
|
+
): ClientToServerConnection {
|
|
14
|
+
const getHeaders = () => {
|
|
15
|
+
const h = new Headers();
|
|
16
|
+
h.set("Content-Type", "application/json");
|
|
17
|
+
|
|
18
|
+
for (const k in extraHeaders) {
|
|
19
|
+
h.set(k, extraHeaders[k]!);
|
|
20
|
+
}
|
|
21
|
+
return h;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const getUrl = (type: string) => {
|
|
25
|
+
const url = new URL("localhost:3000");
|
|
26
|
+
url.searchParams.set("type", type);
|
|
27
|
+
return url.toString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
async sendConnectionRequest(body: ChannelConnectionRequest): Promise<ChannelConnectionResponse> {
|
|
32
|
+
try {
|
|
33
|
+
const request = new Request(getUrl("connect"), {
|
|
34
|
+
body: ION.stringify(body),
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: getHeaders(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const response = await server.handle(request);
|
|
40
|
+
const responseText = await response.text();
|
|
41
|
+
const responseBody = ION.parse(responseText);
|
|
42
|
+
const connectionResponse = v.parseSafe(responseBody, channelConnectionResponseSchema);
|
|
43
|
+
|
|
44
|
+
if (connectionResponse === null) {
|
|
45
|
+
return {
|
|
46
|
+
channel: body.channel,
|
|
47
|
+
params: body.params,
|
|
48
|
+
result: {
|
|
49
|
+
type: "ERROR",
|
|
50
|
+
error: {
|
|
51
|
+
channel: body.channel,
|
|
52
|
+
params: body.params,
|
|
53
|
+
fault: "server",
|
|
54
|
+
message: `Bad response from server: ${JSON.stringify(responseBody)}`,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return connectionResponse;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return {
|
|
63
|
+
channel: body.channel,
|
|
64
|
+
params: body.params,
|
|
65
|
+
result: {
|
|
66
|
+
type: "ERROR",
|
|
67
|
+
error: {
|
|
68
|
+
channel: body.channel,
|
|
69
|
+
params: body.params,
|
|
70
|
+
fault: "server",
|
|
71
|
+
message: `Unknown error connecting to channel: ${e}`,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async runProcedure(body: ProcedureRequestBody) {
|
|
78
|
+
try {
|
|
79
|
+
const request = new Request(getUrl("procedure"), {
|
|
80
|
+
body: ION.stringify(body),
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: getHeaders(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const response = await server.handle(request);
|
|
86
|
+
const responseText = await response.text();
|
|
87
|
+
const responseBody = ION.parse(responseText);
|
|
88
|
+
const procedureResponse = v.parseSafe(responseBody, procedureResponseSchema)
|
|
89
|
+
|
|
90
|
+
if (procedureResponse === null) {
|
|
91
|
+
return {
|
|
92
|
+
status: "ERR",
|
|
93
|
+
error: {
|
|
94
|
+
code: 500,
|
|
95
|
+
message: `Bad response from server: ${responseBody}`,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return procedureResponse;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return {
|
|
103
|
+
status: "ERR",
|
|
104
|
+
error: {
|
|
105
|
+
code: 400,
|
|
106
|
+
message: `Unknown error fetching from the server: ${e}`,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ServerToSidekickConnection } from "@covenant-rpc/core/interfaces";
|
|
2
|
+
|
|
3
|
+
export function emptyServerToSidekick(): ServerToSidekickConnection {
|
|
4
|
+
return {
|
|
5
|
+
addConnection: async () => null,
|
|
6
|
+
update: async () => null,
|
|
7
|
+
postMessage: async () => null,
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ServerToSidekickConnection, SidekickToServerConnection } from "@covenant-rpc/core/interfaces";
|
|
2
|
+
import type { ChannelConnectionPayload, ServerMessage } from "@covenant-rpc/core/channel";
|
|
3
|
+
import ION from "@covenant-rpc/ion";
|
|
4
|
+
|
|
5
|
+
export function httpSidekickToServer(baseUrl: string, key: string): SidekickToServerConnection {
|
|
6
|
+
const getUrl = (type: string) => {
|
|
7
|
+
const url = new URL(baseUrl);
|
|
8
|
+
url.searchParams.set("type", type);
|
|
9
|
+
return url.toString();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
async sendMessage(message) {
|
|
14
|
+
const json = ION.stringify(message);
|
|
15
|
+
|
|
16
|
+
const res = await fetch(getUrl("channel"), {
|
|
17
|
+
headers: {
|
|
18
|
+
"Authorization": `Bearer ${key}`,
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
},
|
|
21
|
+
method: "POST",
|
|
22
|
+
body: json,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (res.ok) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
channel: message.channel,
|
|
31
|
+
params: message.params,
|
|
32
|
+
fault: "server",
|
|
33
|
+
message: `Failed to send message to server. Received: ${res.status} - ${res.statusText}`,
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class HttpServerToSidekick implements ServerToSidekickConnection {
|
|
41
|
+
private url: URL;
|
|
42
|
+
private key: string;
|
|
43
|
+
|
|
44
|
+
constructor(url: string, key: string) {
|
|
45
|
+
this.url = new URL(url);
|
|
46
|
+
this.key = key;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async addConnection(payload: ChannelConnectionPayload): Promise<Error | null> {
|
|
50
|
+
const url = new URL(this.url.toString());
|
|
51
|
+
url.pathname = "/connection";
|
|
52
|
+
|
|
53
|
+
const res = await fetch(url.toString(), {
|
|
54
|
+
body: ION.stringify(payload),
|
|
55
|
+
headers: {
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
"Authorization": `Bearer ${this.key}`,
|
|
58
|
+
},
|
|
59
|
+
method: "POST",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
return new Error(`Error posting connection from sidekick: ${res.status} - ${res.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async update(resources: string[]): Promise<Error | null> {
|
|
69
|
+
const url = new URL(this.url.toString());
|
|
70
|
+
url.pathname = "/resources";
|
|
71
|
+
|
|
72
|
+
const res = await fetch(url.toString(), {
|
|
73
|
+
body: ION.stringify({
|
|
74
|
+
resources: resources,
|
|
75
|
+
}),
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
"Authorization": `Bearer ${this.key}`,
|
|
79
|
+
},
|
|
80
|
+
method: "POST",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
return new Error(`Error posting resources from sidekick: ${res.status} - ${res.statusText}`);
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async postMessage(message: ServerMessage): Promise<Error | null> {
|
|
90
|
+
const url = new URL(this.url.toString());
|
|
91
|
+
url.pathname = "/message";
|
|
92
|
+
|
|
93
|
+
const res = await fetch(url.toString(), {
|
|
94
|
+
body: ION.stringify(message),
|
|
95
|
+
headers: {
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
"Authorization": `Bearer ${this.key}`,
|
|
98
|
+
},
|
|
99
|
+
method: "POST",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (!res.ok) {
|
|
103
|
+
return new Error(`Error posting message from sidekick: ${res.status} - ${res.statusText}`);
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function httpServerToSidekick(url: string, key: string): ServerToSidekickConnection {
|
|
110
|
+
return new HttpServerToSidekick(url, key);
|
|
111
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ClientToSidekickConnection, ServerToSidekickConnection } from "@covenant-rpc/core/interfaces";
|
|
2
|
+
import type { Sidekick, SidekickClient } from "../sidekick";
|
|
3
|
+
import type { SidekickIncomingMessage, SidekickOutgoingMessage } from "@covenant-rpc/core/sidekick/protocol";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export function mockServerToSidekick(sidekick: Sidekick): ServerToSidekickConnection {
|
|
7
|
+
return {
|
|
8
|
+
async addConnection(p) {
|
|
9
|
+
sidekick.addConnection(p);
|
|
10
|
+
return null;
|
|
11
|
+
},
|
|
12
|
+
async update(resources: string[]) {
|
|
13
|
+
await sidekick.updateResources(resources);
|
|
14
|
+
return null;
|
|
15
|
+
},
|
|
16
|
+
async postMessage(m) {
|
|
17
|
+
await sidekick.postServerMessage(m);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function mockClientToSidekick(sidekick: Sidekick, client: SidekickClient): ClientToSidekickConnection {
|
|
24
|
+
return {
|
|
25
|
+
sendMessage(message: SidekickIncomingMessage) {
|
|
26
|
+
sidekick.handleClientMessage(client, message);
|
|
27
|
+
},
|
|
28
|
+
onMessage(v) {
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
package/lib/logger.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Logger as ILogger, LoggerLevel, Prefix } from "@covenant-rpc/core/logger";
|
|
2
|
+
|
|
3
|
+
const loggerLevels: Record<LoggerLevel, number> = { "slient": 0, "error": 1, "warn": 2, "info": 3, "debug": 4 };
|
|
4
|
+
|
|
5
|
+
function levelSatisfies(currentLevel: LoggerLevel, maxLevel: LoggerLevel): boolean {
|
|
6
|
+
return loggerLevels[currentLevel] <= loggerLevels[maxLevel];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Logger implements ILogger {
|
|
10
|
+
prefixes: Prefix[];
|
|
11
|
+
level: LoggerLevel
|
|
12
|
+
|
|
13
|
+
constructor(level: LoggerLevel, prefixes: Prefix[] = []) {
|
|
14
|
+
this.prefixes = prefixes;
|
|
15
|
+
this.level = level;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
sublogger(prefix: Prefix): Logger {
|
|
19
|
+
return new Logger(this.level, [...this.prefixes, prefix]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pushPrefix(prefix: Prefix): Logger {
|
|
23
|
+
this.prefixes.push(prefix);
|
|
24
|
+
return this
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
clone(): Logger {
|
|
28
|
+
return new Logger(this.level, [...this.prefixes]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
debug(text: string): void {
|
|
32
|
+
if (!levelSatisfies("debug", this.level)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`${this.getPrefix()}DEBUG: ${text}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
info(text: string): void {
|
|
40
|
+
if (!levelSatisfies("info", this.level)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`${this.getPrefix()}INFO: ${text}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
error(text: string): void {
|
|
48
|
+
if (!levelSatisfies("error", this.level)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(`${this.getPrefix()}ERROR: ${text}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
warn(text: string): void {
|
|
56
|
+
if (!levelSatisfies("error", this.level)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`${this.getPrefix()}WARNING: ${text}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fatal(text: string): never {
|
|
64
|
+
console.log("---------------------------------");
|
|
65
|
+
console.log(`${this.getPrefix()}FATAL: ${text}`);
|
|
66
|
+
console.log("---------------------------------");
|
|
67
|
+
throw new Error(`FATAL: ${text}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private getPrefix(): string {
|
|
71
|
+
if (this.prefixes.length === 0) {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
const strs = this.prefixes.map(p => typeof p === "string" ? p : p());
|
|
75
|
+
return `[${strs.join(" |> ")}] `;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|