@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.
Files changed (4) hide show
  1. package/README.md +29 -0
  2. package/index.d.ts +82 -0
  3. package/index.js +151 -0
  4. 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
+ }