Skip to content

Commit 53210c3

Browse files
committed
greatly improve schema and type checking
1 parent 89d81f7 commit 53210c3

7 files changed

Lines changed: 218 additions & 94 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nothing-special/kaiware-lib",
3-
"version": "0.4.0",
3+
"version": "0.5.0",
44
"type": "module",
55
"author": {
66
"name": "Garrett Downs",

src/enums/MessageType.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,27 @@ export enum MessageType {
77

88
GetElementStyles = 'get-element-styles',
99
GetElementStylesRes = 'get-element-styles-res',
10+
1011
SetElementStyles = 'set-element-styles',
1112
SetElementStylesRes = 'set-element-styles-res',
1213

1314
GetElementData = 'get-element-data',
1415
GetElementDataRes = 'get-element-data-res',
16+
1517
SetElementData = 'set-element-data',
1618
SetElementDataRes = 'set-element-data-res',
1719

1820
GetStorage = 'get-storage',
1921
GetStorageRes = 'get-storage-res',
22+
2023
SetStorage = 'set-storage',
2124
SetStorageRes = 'set-storage-res',
2225

26+
ClearLogs = 'clear-logs',
27+
ClearLogsRes = 'clear-logs-res',
28+
2329
// Misc
2430

2531
NewLog = 'new-log',
26-
ClearLogs = 'clear-logs',
2732
Error = 'error'
2833
}

src/lib/connection.ts

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
import { z } from 'zod';
22
import { MessageType } from '../enums';
3-
import {
4-
Config,
5-
GetDeviceInfoPayload,
6-
GetDeviceInfoResPayload,
7-
GetElementDataPayload,
8-
GetElementDataResPayload,
9-
GetElementStylesPayload,
10-
GetElementStylesResPayload,
11-
GetElementsPayload,
12-
GetElementsResPayload,
13-
GetStoragePayload,
14-
GetStorageResPayload,
15-
Message
16-
} from '../types';
17-
import { isJson } from '../utils';
3+
import { Config, MessageWithId, rawMessageSchema } from '../types';
184

195
export class Connection {
206
private socket: WebSocket | null = null;
@@ -41,18 +27,7 @@ export class Connection {
4127
this.socket.onmessage = (event) => {
4228
console.log('Message received: ', JSON.parse(event.data));
4329

44-
const messageSchema = z
45-
.string()
46-
.refine((val) => isJson(val), 'Must be a valid JSON string')
47-
.transform((val) => JSON.parse(val))
48-
.pipe(
49-
z.object({
50-
type: z.nativeEnum(MessageType),
51-
data: z.any().optional()
52-
})
53-
);
54-
55-
const validateMessage = messageSchema.safeParse(event.data);
30+
const validateMessage = rawMessageSchema.safeParse(event.data);
5631
if (!validateMessage.success) {
5732
console.log(
5833
JSON.stringify(formatValidationError(validateMessage.error.issues))
@@ -61,7 +36,7 @@ export class Connection {
6136
}
6237

6338
// eslint-disable-next-line @typescript-eslint/no-explicit-any
64-
const message = JSON.parse(event.data) as Message<any>;
39+
const message = JSON.parse(event.data) as MessageWithId;
6540

6641
switch (message.type) {
6742
case MessageType.GetDeviceInfo:
@@ -105,7 +80,7 @@ export class Connection {
10580
this.socket.close();
10681
}
10782

108-
sendMessage<TData = undefined>(message: Message<TData>) {
83+
sendMessage(message: MessageWithId) {
10984
console.log('Sending message: ', message);
11085

11186
if (!this.socket) {
@@ -116,8 +91,8 @@ export class Connection {
11691
this.socket.send(JSON.stringify(message));
11792
}
11893

119-
private handleGetDeviceInfo(message: Message<GetDeviceInfoPayload>) {
120-
this.sendMessage<GetDeviceInfoResPayload>({
94+
private handleGetDeviceInfo(message: MessageWithId & { type: MessageType.GetDeviceInfo }) {
95+
this.sendMessage({
12196
requestId: message.requestId,
12297
type: MessageType.GetDeviceInfoRes,
12398
data: {
@@ -128,17 +103,17 @@ export class Connection {
128103
});
129104
}
130105

131-
private handleGetElements(message: Message<GetElementsPayload>) {
132-
this.sendMessage<GetElementsResPayload>({
106+
private handleGetElements(message: MessageWithId & { type: MessageType.GetElements }) {
107+
this.sendMessage({
133108
requestId: message.requestId,
134109
type: MessageType.GetElementsRes,
135110
data: document.querySelector('html')?.outerHTML ?? ''
136111
});
137112
}
138113

139-
private handleGetStorage(message: Message<GetStoragePayload>) {
114+
private handleGetStorage(message: MessageWithId & { type: MessageType.GetStorage }) {
140115
if (message.data.storageType === 'local') {
141-
this.sendMessage<GetStorageResPayload>({
116+
this.sendMessage({
142117
requestId: message.requestId,
143118
type: MessageType.GetStorageRes,
144119
data: {
@@ -147,7 +122,7 @@ export class Connection {
147122
}
148123
});
149124
} else if (message.data.storageType === 'session') {
150-
this.sendMessage<GetStorageResPayload>({
125+
this.sendMessage({
151126
requestId: message.requestId,
152127
type: MessageType.GetStorageRes,
153128
data: {
@@ -158,7 +133,9 @@ export class Connection {
158133
}
159134
}
160135

161-
private handleGetElementStyles(message: Message<GetElementStylesPayload>) {
136+
private handleGetElementStyles(
137+
message: MessageWithId & { type: MessageType.GetElementStyles }
138+
) {
162139
console.log('Getting styles for element at index: ', message.data.index);
163140

164141
const element = document.querySelectorAll('*')[message.data.index];
@@ -172,7 +149,7 @@ export class Connection {
172149
},
173150
{} as Record<string, string>
174151
);
175-
this.sendMessage<GetElementStylesResPayload>({
152+
this.sendMessage({
176153
requestId: message.requestId,
177154
type: MessageType.GetElementStylesRes,
178155
data: {
@@ -182,18 +159,18 @@ export class Connection {
182159
});
183160
}
184161

185-
private handleGetElementData(message: Message<GetElementDataPayload>) {
162+
private handleGetElementData(message: MessageWithId & { type: MessageType.GetElementData }) {
186163
console.log('Getting data for element at index: ', message.data.index);
187164

188165
const element = document.querySelectorAll('*')[message.data.index];
189166
console.log('Element: ', element);
190167

191168
// TODO: Get data for element
192169

193-
this.sendMessage<GetElementDataResPayload>({
170+
this.sendMessage({
194171
requestId: message.requestId,
195172
type: MessageType.GetElementDataRes,
196-
data: {}
173+
data: { index: message.data.index, data: {} }
197174
});
198175
}
199176
}

src/lib/kaiware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { LogLevel, MessageType } from '../enums';
2-
import { Config, GetLogResPayload } from '../types';
2+
import { Config } from '../types';
33
import { parseError } from '../utils';
44
import { Connection } from './connection';
55

@@ -52,7 +52,7 @@ export class Kaiware {
5252
}
5353
});
5454

55-
this.connection.sendMessage<GetLogResPayload>({
55+
this.connection.sendMessage({
5656
requestId: '',
5757
type: MessageType.NewLog,
5858
data: {

src/types/schemas.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { describe, expect, it } from 'vitest';
3+
import { LogLevel, MessageType } from '../enums';
4+
import { getDeviceInfoResPayloadSchema, messageSchema, rawMessageSchema } from './schemas';
5+
6+
describe('schemas', () => {
7+
describe('messages', () => {
8+
const testCases: any[] = [
9+
[{requestId:'req',type:MessageType.GetDeviceInfo, data: null}, true, ''],
10+
[{requestId:'req',type:MessageType.GetDeviceInfoRes, data: { id: 'id', name: 'name', connectionType: 'wifi' }}, true, ''],
11+
[{requestId:'req',type:MessageType.GetElements, data: null}, true, ''],
12+
[{requestId:'req',type:MessageType.GetElementsRes, data: 'html'}, true, ''],
13+
[{requestId:'req',type:MessageType.GetElementStyles, data:{ index: 1 }}, true, ''],
14+
[{requestId:'req',type:MessageType.GetElementStylesRes, data:{ index: 1, styles: {color:'red'} }}, true, ''],
15+
[{requestId:'req',type:MessageType.GetElementData, data:{ index: 1 }}, true, ''],
16+
[{requestId:'req',type:MessageType.GetElementDataRes, data:{ index: 1, data: {height:'100px'} }}, true, ''],
17+
[{requestId:'req',type:MessageType.GetStorage, data: {storageType: 'local'}}, true, ''],
18+
[{requestId:'req',type:MessageType.GetStorageRes, data: {storageType: 'local', data: {}}}, true, ''],
19+
[{requestId:'req',type:MessageType.SetStorage, data: {storageType: 'local', data: {}}}, true, ''],
20+
[{requestId:'req',type:MessageType.SetStorageRes, data: null}, true, ''],
21+
[{requestId:'req',type:MessageType.NewLog, data: {source: 'src', level: LogLevel.Info, data: ['1','2'], timestamp: '2024'}}, true, ''],
22+
[{requestId:'req',type:MessageType.ClearLogs, data: null}, true, ''],
23+
[{requestId:'req',type:MessageType.ClearLogsRes, data: null}, true, ''],
24+
[{requestId: null, type:MessageType.GetDeviceInfo, data: null}, false, 'requestId'],
25+
[{requestId:'req', type: null, data: null}, false, 'type'],
26+
[{requestId:'req', type:MessageType.GetDeviceInfoRes, data: { id: 'id', name: null, connectionType: 'wifi' }}, false, 'data'],
27+
[{requestId:'req', type:MessageType.GetElements, data: 123}, false, 'data'],
28+
[{requestId:'req', type:MessageType.GetElementsRes, data: null}, false, 'data'],
29+
[{requestId:'req', type:MessageType.GetElementStyles, data:{ index: [] }}, false, 'data'],
30+
[{requestId:'req', type:MessageType.GetElementStylesRes, data:{ index: 1, styles: null }}, false, 'data'],
31+
[{requestId:'req', type:MessageType.GetElementData, data: null}, false, 'data'],
32+
[{requestId:'req', type:MessageType.GetElementDataRes, data:{ index: 'abc', data: {height:'100px'} }}, false, 'data'],
33+
[{requestId:'req', type:MessageType.GetStorage, data: {storageType: 'other'}}, false, 'data'],
34+
[{requestId:'req', type:MessageType.GetStorageRes, data: {storageType: 'local', data: null}}, false, 'data'],
35+
[{requestId:'req', type:MessageType.SetStorage, data: {storageType: null, data: {}}}, false, 'data'],
36+
[{requestId:'req', type:MessageType.SetStorageRes, data: {prop:1}}, false, 'data'],
37+
[{requestId:'req', type:MessageType.NewLog, data: {source: 'src', level: 'other', data: ['1','2'], timestamp: '2024'}}, false, 'data'],
38+
[{requestId:'req', type:MessageType.ClearLogs, data: 123}, false, 'data'],
39+
[{requestId:'req', type:MessageType.ClearLogsRes, data: 'abc'}, false, 'data']
40+
];
41+
42+
it.each(testCases)(`should parse %s and return %s`, (data, success, errMsg) => {
43+
const result = messageSchema.safeParse(data);
44+
45+
expect(result.success).toBe(success);
46+
if(success) {
47+
expect((result as any).data).toEqual(data);
48+
} else {
49+
expect((result as any).error.issues[0].path[0]).toEqual(errMsg);
50+
}
51+
});
52+
53+
it.each(testCases.map(a => ([
54+
JSON.stringify(a[0]),
55+
a[1],
56+
a[2]
57+
])))(`should parse %s and return %s`, (data, success, errMsg) => {
58+
const result = rawMessageSchema.safeParse(data);
59+
60+
expect(result.success).toBe(success);
61+
if(success) {
62+
expect(JSON.stringify((result as any).data)).toEqual(data);
63+
} else {
64+
expect((result as any).error.issues[0].path[0]).toEqual(errMsg);
65+
}
66+
});
67+
});
68+
69+
describe('getDeviceInfoResPayloadSchema', () => {
70+
it.each([
71+
[{ id: 'id', name: 'name', connectionType: 'wifi' }, true],
72+
[{ id: 'id', name: 'name', connectionType: 'usb' }, true]
73+
])(`should successfully parse %s`, (data, success) => {
74+
const result = getDeviceInfoResPayloadSchema.safeParse(data);
75+
76+
expect(result.success).toBe(success);
77+
expect((result as any).data).toEqual(data);
78+
});
79+
80+
it.each([
81+
[{ id: 'id', name: 'name', connectionType: 'other' }, false, 'connectionType'],
82+
[{ id: undefined, name: 'name', connectionType: 'usb' }, false, 'id']
83+
])(`should fail to parse %s`, (data, success, errMsg) => {
84+
const result = getDeviceInfoResPayloadSchema.safeParse(data);
85+
86+
expect(result.success).toBe(success);
87+
expect((result as any).error.issues[0].path[0]).toEqual(errMsg);
88+
});
89+
});
90+
});

0 commit comments

Comments
 (0)