@kaonis/protocol 1.0.0
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/README.md +29 -0
- package/index.d.ts +82 -0
- package/index.js +151 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @kaonis/protocol
|
|
2
|
+
|
|
3
|
+
Shared protocol types and runtime schemas for communication between `woly-backend` (node agent) and `woly-cnc-backend` (C&C).
|
|
4
|
+
|
|
5
|
+
## Exports
|
|
6
|
+
|
|
7
|
+
- `PROTOCOL_VERSION`
|
|
8
|
+
- `SUPPORTED_PROTOCOL_VERSIONS`
|
|
9
|
+
- `outboundNodeMessageSchema`
|
|
10
|
+
- `inboundCncCommandSchema`
|
|
11
|
+
- Type exports from `index.d.ts` (`NodeMessage`, `CncCommand`, `NodeRegistration`, etc.)
|
|
12
|
+
|
|
13
|
+
## Publish
|
|
14
|
+
|
|
15
|
+
From the `woly-backend` repo root:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm run protocol:pack
|
|
19
|
+
npm run protocol:publish
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or use the GitHub workflow: `Publish Protocol Package`.
|
|
23
|
+
|
|
24
|
+
## Versioning
|
|
25
|
+
|
|
26
|
+
1. Bump `packages/protocol/package.json` version.
|
|
27
|
+
2. Publish package.
|
|
28
|
+
3. Update both repos to consume the new semver version.
|
|
29
|
+
4. Run protocol contract tests in both repos before merge.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const PROTOCOL_VERSION: '1.0.0';
|
|
4
|
+
export const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
|
|
5
|
+
|
|
6
|
+
export type HostStatus = 'awake' | 'asleep';
|
|
7
|
+
|
|
8
|
+
export interface HostPayload {
|
|
9
|
+
name: string;
|
|
10
|
+
mac: string;
|
|
11
|
+
ip: string;
|
|
12
|
+
status: HostStatus;
|
|
13
|
+
lastSeen: string | null;
|
|
14
|
+
discovered: number;
|
|
15
|
+
pingResponsive?: number | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NodeMetadata {
|
|
19
|
+
version: string;
|
|
20
|
+
platform: string;
|
|
21
|
+
protocolVersion: string;
|
|
22
|
+
networkInfo: {
|
|
23
|
+
subnet: string;
|
|
24
|
+
gateway: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface NodeRegistration {
|
|
29
|
+
nodeId: string;
|
|
30
|
+
name: string;
|
|
31
|
+
location: string;
|
|
32
|
+
authToken: string;
|
|
33
|
+
publicUrl?: string;
|
|
34
|
+
metadata: NodeMetadata;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type NodeMessage =
|
|
38
|
+
| { type: 'register'; data: NodeRegistration }
|
|
39
|
+
| { type: 'heartbeat'; data: { nodeId: string; timestamp: Date } }
|
|
40
|
+
| { type: 'host-discovered'; data: { nodeId: string } & HostPayload }
|
|
41
|
+
| { type: 'host-updated'; data: { nodeId: string } & HostPayload }
|
|
42
|
+
| { type: 'host-removed'; data: { nodeId: string; name: string } }
|
|
43
|
+
| { type: 'scan-complete'; data: { nodeId: string; hostCount: number } }
|
|
44
|
+
| {
|
|
45
|
+
type: 'command-result';
|
|
46
|
+
data: {
|
|
47
|
+
nodeId: string;
|
|
48
|
+
commandId: string;
|
|
49
|
+
success: boolean;
|
|
50
|
+
message?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
timestamp: Date;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export interface RegisteredCommandData {
|
|
57
|
+
nodeId: string;
|
|
58
|
+
heartbeatInterval: number;
|
|
59
|
+
protocolVersion?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type CncCommand =
|
|
63
|
+
| { type: 'registered'; data: RegisteredCommandData }
|
|
64
|
+
| { type: 'wake'; commandId: string; data: { hostName: string; mac: string } }
|
|
65
|
+
| { type: 'scan'; commandId: string; data: { immediate: boolean } }
|
|
66
|
+
| {
|
|
67
|
+
type: 'update-host';
|
|
68
|
+
commandId: string;
|
|
69
|
+
data: {
|
|
70
|
+
currentName?: string;
|
|
71
|
+
name: string;
|
|
72
|
+
mac?: string;
|
|
73
|
+
ip?: string;
|
|
74
|
+
status?: HostStatus;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
| { type: 'delete-host'; commandId: string; data: { name: string } }
|
|
78
|
+
| { type: 'ping'; data: { timestamp: Date } }
|
|
79
|
+
| { type: 'error'; message: string };
|
|
80
|
+
|
|
81
|
+
export const inboundCncCommandSchema: z.ZodType<CncCommand>;
|
|
82
|
+
export const outboundNodeMessageSchema: z.ZodType<NodeMessage>;
|
package/index.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const PROTOCOL_VERSION = '1.0.0';
|
|
4
|
+
const SUPPORTED_PROTOCOL_VERSIONS = [PROTOCOL_VERSION];
|
|
5
|
+
|
|
6
|
+
const hostStatusSchema = z.enum(['awake', 'asleep']);
|
|
7
|
+
|
|
8
|
+
const hostPayloadSchema = z.object({
|
|
9
|
+
name: z.string().min(1),
|
|
10
|
+
mac: z.string().min(1),
|
|
11
|
+
ip: z.string().min(1),
|
|
12
|
+
status: hostStatusSchema,
|
|
13
|
+
lastSeen: z.string().nullable(),
|
|
14
|
+
discovered: z.number().int(),
|
|
15
|
+
pingResponsive: z.number().int().nullable().optional(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const nodeMetadataSchema = z.object({
|
|
19
|
+
version: z.string().min(1),
|
|
20
|
+
platform: z.string().min(1),
|
|
21
|
+
protocolVersion: z.string().min(1),
|
|
22
|
+
networkInfo: z.object({
|
|
23
|
+
subnet: z.string().min(1),
|
|
24
|
+
gateway: z.string().min(1),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const commandResultPayloadSchema = z.object({
|
|
29
|
+
nodeId: z.string().min(1),
|
|
30
|
+
commandId: z.string().min(1),
|
|
31
|
+
success: z.boolean(),
|
|
32
|
+
message: z.string().optional(),
|
|
33
|
+
error: z.string().optional(),
|
|
34
|
+
timestamp: z.coerce.date(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const outboundNodeMessageSchema = z.discriminatedUnion('type', [
|
|
38
|
+
z.object({
|
|
39
|
+
type: z.literal('register'),
|
|
40
|
+
data: z.object({
|
|
41
|
+
nodeId: z.string().min(1),
|
|
42
|
+
name: z.string().min(1),
|
|
43
|
+
location: z.string().min(1),
|
|
44
|
+
authToken: z.string().min(1),
|
|
45
|
+
publicUrl: z.string().optional(),
|
|
46
|
+
metadata: nodeMetadataSchema,
|
|
47
|
+
}),
|
|
48
|
+
}),
|
|
49
|
+
z.object({
|
|
50
|
+
type: z.literal('heartbeat'),
|
|
51
|
+
data: z.object({
|
|
52
|
+
nodeId: z.string().min(1),
|
|
53
|
+
timestamp: z.coerce.date(),
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
z.object({
|
|
57
|
+
type: z.literal('host-discovered'),
|
|
58
|
+
data: z
|
|
59
|
+
.object({
|
|
60
|
+
nodeId: z.string().min(1),
|
|
61
|
+
})
|
|
62
|
+
.merge(hostPayloadSchema),
|
|
63
|
+
}),
|
|
64
|
+
z.object({
|
|
65
|
+
type: z.literal('host-updated'),
|
|
66
|
+
data: z
|
|
67
|
+
.object({
|
|
68
|
+
nodeId: z.string().min(1),
|
|
69
|
+
})
|
|
70
|
+
.merge(hostPayloadSchema),
|
|
71
|
+
}),
|
|
72
|
+
z.object({
|
|
73
|
+
type: z.literal('host-removed'),
|
|
74
|
+
data: z.object({
|
|
75
|
+
nodeId: z.string().min(1),
|
|
76
|
+
name: z.string().min(1),
|
|
77
|
+
}),
|
|
78
|
+
}),
|
|
79
|
+
z.object({
|
|
80
|
+
type: z.literal('scan-complete'),
|
|
81
|
+
data: z.object({
|
|
82
|
+
nodeId: z.string().min(1),
|
|
83
|
+
hostCount: z.number().int().nonnegative(),
|
|
84
|
+
}),
|
|
85
|
+
}),
|
|
86
|
+
z.object({
|
|
87
|
+
type: z.literal('command-result'),
|
|
88
|
+
data: commandResultPayloadSchema,
|
|
89
|
+
}),
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const inboundCncCommandSchema = z.discriminatedUnion('type', [
|
|
93
|
+
z.object({
|
|
94
|
+
type: z.literal('registered'),
|
|
95
|
+
data: z.object({
|
|
96
|
+
nodeId: z.string().min(1),
|
|
97
|
+
heartbeatInterval: z.number().int().positive(),
|
|
98
|
+
protocolVersion: z.string().min(1).optional(),
|
|
99
|
+
}),
|
|
100
|
+
}),
|
|
101
|
+
z.object({
|
|
102
|
+
type: z.literal('wake'),
|
|
103
|
+
commandId: z.string().min(1),
|
|
104
|
+
data: z.object({
|
|
105
|
+
hostName: z.string().min(1),
|
|
106
|
+
mac: z.string().min(1),
|
|
107
|
+
}),
|
|
108
|
+
}),
|
|
109
|
+
z.object({
|
|
110
|
+
type: z.literal('scan'),
|
|
111
|
+
commandId: z.string().min(1),
|
|
112
|
+
data: z.object({
|
|
113
|
+
immediate: z.boolean(),
|
|
114
|
+
}),
|
|
115
|
+
}),
|
|
116
|
+
z.object({
|
|
117
|
+
type: z.literal('update-host'),
|
|
118
|
+
commandId: z.string().min(1),
|
|
119
|
+
data: z.object({
|
|
120
|
+
currentName: z.string().min(1).optional(),
|
|
121
|
+
name: z.string().min(1),
|
|
122
|
+
mac: z.string().min(1).optional(),
|
|
123
|
+
ip: z.string().min(1).optional(),
|
|
124
|
+
status: hostStatusSchema.optional(),
|
|
125
|
+
}),
|
|
126
|
+
}),
|
|
127
|
+
z.object({
|
|
128
|
+
type: z.literal('delete-host'),
|
|
129
|
+
commandId: z.string().min(1),
|
|
130
|
+
data: z.object({
|
|
131
|
+
name: z.string().min(1),
|
|
132
|
+
}),
|
|
133
|
+
}),
|
|
134
|
+
z.object({
|
|
135
|
+
type: z.literal('ping'),
|
|
136
|
+
data: z.object({
|
|
137
|
+
timestamp: z.coerce.date(),
|
|
138
|
+
}),
|
|
139
|
+
}),
|
|
140
|
+
z.object({
|
|
141
|
+
type: z.literal('error'),
|
|
142
|
+
message: z.string().min(1),
|
|
143
|
+
}),
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
PROTOCOL_VERSION,
|
|
148
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
149
|
+
inboundCncCommandSchema,
|
|
150
|
+
outboundNodeMessageSchema,
|
|
151
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kaonis/protocol",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared WoLy node <-> C&C protocol types and runtime schemas",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"index.js",
|
|
10
|
+
"index.d.ts",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./index.d.ts",
|
|
16
|
+
"default": "./index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/kaonis/woly-backend.git",
|
|
25
|
+
"directory": "packages/protocol"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/kaonis/woly-backend/tree/master/packages/protocol",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/kaonis/woly-backend/issues"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"woly",
|
|
33
|
+
"protocol",
|
|
34
|
+
"zod",
|
|
35
|
+
"websocket"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"zod": "^3.24.1"
|
|
39
|
+
}
|
|
40
|
+
}
|