busroot-sdk 0.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/eslint.config.mjs +8 -0
- package/jest.config.ts +10 -0
- package/package.json +32 -0
- package/src/client.test.ts +5 -0
- package/src/client.ts +226 -0
- package/src/example.ts +43 -0
- package/src/hooks.ts +43 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
package/jest.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "busroot-sdk",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"main": "./build/index.js",
|
|
5
|
+
"types": "./build/index.d.ts",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"example": "ts-node -r dotenv-expand/config ./src/example.ts",
|
|
9
|
+
"dev": "pnpm run lint && tsc -b --watch",
|
|
10
|
+
"build": "pnpm run lint && tsc -b --verbose",
|
|
11
|
+
"lint": "eslint 'src/**/*.ts'",
|
|
12
|
+
"test": "jest"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@jest/globals": "^29.7.0",
|
|
16
|
+
"@types/react": "^18.0.26",
|
|
17
|
+
"eslint-config-custom": "workspace:*",
|
|
18
|
+
"ts-jest": "^29.4.0",
|
|
19
|
+
"tsconfig": "workspace:*",
|
|
20
|
+
"typescript-eslint": "^8.38.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"busroot-common": "workspace:*",
|
|
24
|
+
"jest": "^30.0.4",
|
|
25
|
+
"moment": "^2.30.1",
|
|
26
|
+
"mqtt": "^5.14.0",
|
|
27
|
+
"react": "^18.3.1",
|
|
28
|
+
"ts-node": "^10.9.1",
|
|
29
|
+
"typescript": "^5.8.3",
|
|
30
|
+
"zod": "^3.25.1"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PlantSchema,
|
|
3
|
+
PlantViewModel,
|
|
4
|
+
ScheduleViewModel,
|
|
5
|
+
BusrootSignals,
|
|
6
|
+
SkuViewModel,
|
|
7
|
+
StationSchema,
|
|
8
|
+
StationViewModel,
|
|
9
|
+
AccountViewModel,
|
|
10
|
+
ApiKeyViewModel,
|
|
11
|
+
MqttEventType,
|
|
12
|
+
} from "busroot-common";
|
|
13
|
+
import mqtt from "mqtt/dist/mqtt.esm";
|
|
14
|
+
|
|
15
|
+
export type Listener = (msg: any) => void;
|
|
16
|
+
|
|
17
|
+
interface BusrootResponse {
|
|
18
|
+
isSuccess: boolean;
|
|
19
|
+
errorCode?: string;
|
|
20
|
+
result: any;
|
|
21
|
+
results: any[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class Client {
|
|
25
|
+
mqttClient: mqtt.MqttClient;
|
|
26
|
+
listenersByEventType = new Map<keyof typeof MqttEventType, Set<Listener>>();
|
|
27
|
+
apiUrl: string;
|
|
28
|
+
|
|
29
|
+
constructor(public config: { accountId: string; apiKey: string; host: string }) {
|
|
30
|
+
this.apiUrl = "http://" + config.host + ":3000/api";
|
|
31
|
+
|
|
32
|
+
this.mqttClient = mqtt.connect({
|
|
33
|
+
host: config.host,
|
|
34
|
+
port: 8883,
|
|
35
|
+
username: "api-key",
|
|
36
|
+
password: this.config.apiKey,
|
|
37
|
+
clientId: this.config.accountId + "/sdk",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.mqttClient.on("message", (topic, payload) => {
|
|
41
|
+
const eventType = topic.toUpperCase().split("/").at(-1);
|
|
42
|
+
|
|
43
|
+
for (const [listenerEventType, listeners] of this.listenersByEventType) {
|
|
44
|
+
if (MqttEventType[listenerEventType] !== eventType) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const listener of listeners) {
|
|
49
|
+
try {
|
|
50
|
+
listener(JSON.parse(payload.toString()));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
//
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.mqttClient.on("error", (error) => {
|
|
59
|
+
console.error(error);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.mqttClient.on("connect", () => {
|
|
63
|
+
console.log("Connected to Busroot MQTT...");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
this.mqttClient.on("reconnect", () => {
|
|
67
|
+
console.log("Disconnected from Busroot MQTT...");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.mqttClient.subscribe(`busroot/v2/${this.config.accountId}/events/#`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
destroy() {
|
|
74
|
+
this.mqttClient.end();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async get(path: string, query?: { [query: string]: string }) {
|
|
78
|
+
let url = this.apiUrl + path;
|
|
79
|
+
|
|
80
|
+
if (query != null) {
|
|
81
|
+
url +=
|
|
82
|
+
"?" +
|
|
83
|
+
Object.entries(query)
|
|
84
|
+
.map((value) => value[0] + "=" + value[1])
|
|
85
|
+
.join("&");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const res = await fetch(url, {
|
|
89
|
+
headers: { "x-api-key": this.config.apiKey },
|
|
90
|
+
method: "GET",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (res.status !== 200) {
|
|
94
|
+
throw new Error(`${res.status}: ${res.statusText}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const resJson: BusrootResponse = await res.json();
|
|
98
|
+
|
|
99
|
+
return resJson;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async post(path: string, body: object) {
|
|
103
|
+
const res = await fetch(this.apiUrl + path, {
|
|
104
|
+
headers: { "x-api-key": this.config.apiKey, "Content-Type": "application/json" },
|
|
105
|
+
method: "POST",
|
|
106
|
+
body: JSON.stringify(body),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const resJson: BusrootResponse = await res.json();
|
|
110
|
+
|
|
111
|
+
return resJson;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get profile() {
|
|
115
|
+
return {
|
|
116
|
+
get: async () => {
|
|
117
|
+
const res = await this.get("/auth/profile");
|
|
118
|
+
|
|
119
|
+
return res.result;
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get account() {
|
|
125
|
+
return {
|
|
126
|
+
create: async (domain: string, plantName: string): Promise<{ account: AccountViewModel; apiKey: string }> => {
|
|
127
|
+
const res = await this.post("/account/new", { domain, plantName });
|
|
128
|
+
|
|
129
|
+
return res.result;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
get apiKey() {
|
|
135
|
+
return {
|
|
136
|
+
create: async (): Promise<ApiKeyViewModel> => {
|
|
137
|
+
const res = await this.post("/api-key", {});
|
|
138
|
+
|
|
139
|
+
return res.result;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get plant() {
|
|
145
|
+
return {
|
|
146
|
+
get: async (plantCode: string): Promise<PlantViewModel> => {
|
|
147
|
+
const res = await this.get("/plant", { plantCode });
|
|
148
|
+
|
|
149
|
+
return res.result;
|
|
150
|
+
},
|
|
151
|
+
createUpdate: async (station: Partial<PlantSchema>): Promise<PlantViewModel> => {
|
|
152
|
+
const res = await this.post("/station", station);
|
|
153
|
+
|
|
154
|
+
return res.result;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get station() {
|
|
160
|
+
return {
|
|
161
|
+
get: async (stationCode: string): Promise<StationViewModel[]> => {
|
|
162
|
+
const res = await this.get("/station", { stationcodes: stationCode });
|
|
163
|
+
|
|
164
|
+
return res.results;
|
|
165
|
+
},
|
|
166
|
+
createUpdate: async (station: Partial<StationSchema>): Promise<StationViewModel> => {
|
|
167
|
+
const res = await this.post("/station", station);
|
|
168
|
+
|
|
169
|
+
return res.result;
|
|
170
|
+
},
|
|
171
|
+
signal: (stationCode: string, payload: BusrootSignals): void => {
|
|
172
|
+
throw new Error("noop");
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
sku = {
|
|
178
|
+
get: async (skuCode: string): Promise<SkuViewModel> => {
|
|
179
|
+
const res = await this.get("/sku", { skucodes: skuCode });
|
|
180
|
+
|
|
181
|
+
return res.result;
|
|
182
|
+
},
|
|
183
|
+
createUpdate: async (sku: Partial<SkuViewModel>): Promise<SkuViewModel> => {
|
|
184
|
+
const res = await this.post("/sku", sku);
|
|
185
|
+
|
|
186
|
+
return res.result;
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
schedule = {
|
|
191
|
+
get: async (scheduleId: number): Promise<ScheduleViewModel[]> => {
|
|
192
|
+
const res = await this.get("/schedule", { ids: String(scheduleId) });
|
|
193
|
+
|
|
194
|
+
return res.results;
|
|
195
|
+
},
|
|
196
|
+
createUpdate: async (schedule: Partial<ScheduleViewModel>): Promise<ScheduleViewModel> => {
|
|
197
|
+
const res = await this.post("/schedule", schedule);
|
|
198
|
+
|
|
199
|
+
return res.result;
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
on(event: MqttEventType, listener: Listener) {
|
|
204
|
+
let set = this.listenersByEventType.get(event);
|
|
205
|
+
|
|
206
|
+
if (!set) {
|
|
207
|
+
set = new Set<Listener>();
|
|
208
|
+
this.listenersByEventType.set(event, set);
|
|
209
|
+
}
|
|
210
|
+
set.add(listener);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
off(event: MqttEventType, listener: Listener) {
|
|
214
|
+
const set = this.listenersByEventType.get(event);
|
|
215
|
+
if (!set) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
set.delete(listener);
|
|
220
|
+
if (set.size === 0) {
|
|
221
|
+
this.listenersByEventType.delete(event);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { Client };
|
package/src/example.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import moment from "moment";
|
|
2
|
+
import { Client } from "./client";
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const busrootOiClient = new Client({ accountId: "account_000", apiKey: "admin_api_key", host: "localhost" });
|
|
6
|
+
|
|
7
|
+
// const profile = await busroot.profile.get();
|
|
8
|
+
|
|
9
|
+
// console.log(profile);
|
|
10
|
+
|
|
11
|
+
const account = await busrootOiClient.account.create("opind.co", "London");
|
|
12
|
+
|
|
13
|
+
// console.log(account);
|
|
14
|
+
|
|
15
|
+
const busrootClient = new Client({ accountId: account.account.id, apiKey: account.apiKey, host: "localhost" });
|
|
16
|
+
|
|
17
|
+
// const profile = await busrootClient.profile.get();
|
|
18
|
+
|
|
19
|
+
// console.log(profile);
|
|
20
|
+
|
|
21
|
+
const station1 = await busrootClient.station.createUpdate({ code: "machine1", name: "Machine 1", groupCode: "unknown" });
|
|
22
|
+
|
|
23
|
+
const sku1 = await busrootClient.sku.createUpdate({ code: "PRODUCT1", name: "Product 1" });
|
|
24
|
+
|
|
25
|
+
const { id: scheduleId } = await busrootClient.schedule.createUpdate({
|
|
26
|
+
stationCode: station1.code,
|
|
27
|
+
skuCode: sku1.code,
|
|
28
|
+
plannedStartAt: moment().toISOString(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log(scheduleId);
|
|
32
|
+
|
|
33
|
+
// busrootClient.station.signal(station1.code, { production_complete: 1 });
|
|
34
|
+
|
|
35
|
+
const schedule = await busrootClient.schedule.get(scheduleId);
|
|
36
|
+
|
|
37
|
+
console.log(schedule[0]);
|
|
38
|
+
|
|
39
|
+
busrootOiClient.destroy();
|
|
40
|
+
busrootClient.destroy();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
main();
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Client } from "./client";
|
|
3
|
+
import z from "zod";
|
|
4
|
+
|
|
5
|
+
// TODO: Account ID and API key should be saved to a local cookie and removed from the query param.
|
|
6
|
+
// TODO: Add logout functionality, that clears the cookies.
|
|
7
|
+
|
|
8
|
+
export const ConfigPayloadSchema = z.object({
|
|
9
|
+
accountId: z.string().max(255),
|
|
10
|
+
apiKey: z.string().max(255),
|
|
11
|
+
host: z.string().max(255),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export function useBusroot() {
|
|
15
|
+
const [client, setClient] = useState<Client>();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (typeof window === undefined) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
23
|
+
const tokenParam = searchParams.get("busroot_token");
|
|
24
|
+
|
|
25
|
+
if (tokenParam != null && client == null) {
|
|
26
|
+
try {
|
|
27
|
+
const config = ConfigPayloadSchema.parse(JSON.parse(atob(tokenParam)));
|
|
28
|
+
|
|
29
|
+
setClient(new Client({ accountId: config.accountId, apiKey: config.apiKey, host: config.host }));
|
|
30
|
+
} catch (e: any) {
|
|
31
|
+
console.log(e.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return;
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const logout = () => {
|
|
39
|
+
console.log("not implemented");
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return { client, logout };
|
|
43
|
+
}
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/client.test.ts","./src/client.ts","./src/example.ts","./src/hooks.ts","./src/index.ts"],"version":"5.8.3"}
|