Skip to content

Commit 001fc31

Browse files
committed
push before destroy droplet.
1 parent 0306f14 commit 001fc31

File tree

8 files changed

+225
-10
lines changed

8 files changed

+225
-10
lines changed

src/database/CreateTables.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,16 @@ export function CreateTables(dbConnection: mysql.Pool) {
102102
FOREIGN KEY (\`sender\`) REFERENCES Accounts(\`AccountID\`),
103103
FOREIGN KEY (\`DeviceHWID\`) REFERENCES Devices(\`DeviceHWID\`)
104104
)`;
105-
105+
const PTAuditLog = `CREATE TABLE IF NOT EXISTS \`PT_Audit_Log\` (
106+
\`id\` BIGINT NOT NULL AUTO_INCREMENT,
107+
\`timestamp\` BIGINT NOT NULL,
108+
\`DeviceHWID\` VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
109+
\`action\` VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
110+
\`actionData\` VARCHAR(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
111+
\`actorUsername\` VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
112+
PRIMARY KEY (\`id\`),
113+
FOREIGN KEY (\`DeviceHWID\`) REFERENCES Devices(\`DeviceHWID\`)
114+
)`;
106115
const before = performance.now();
107116
console.log("[Debug] Creating tables if doesn't exist...");
108117
dbConnection.query(AccountsTable);
@@ -116,5 +125,6 @@ export function CreateTables(dbConnection: mysql.Pool) {
116125
dbConnection.query(DeviceSubAccessInvitesTable);
117126
dbConnection.query(PTImageSnapshots);
118127
dbConnection.query(PTMessaging);
128+
dbConnection.query(PTAuditLog);
119129
console.log("[Debug] Tables created, %sms", (performance.now() - before).toFixed(2));
120130
}

src/dbops/GetAudits.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { getConnection } from "../database/Connection";
2+
import { AuditEntry } from "../types/AuditEntry";
3+
const connection = getConnection(); // get mysql2 connection from pool
4+
5+
export function GetAudits(DeviceHWID: string): Promise<AuditEntry[]>{
6+
return new Promise((resolve, reject) => {
7+
connection
8+
.promise()
9+
.execute("SELECT * FROM PT_Audit_Log WHERE DeviceHWID = ? ORDER BY timestamp DESC", [DeviceHWID])
10+
.then(([rows, fields]) => {
11+
if (Array.isArray(rows)) {
12+
resolve(rows as AuditEntry[]);
13+
} else {
14+
reject("Invalid response from database.");
15+
console.error("Invalid response from database.")
16+
}
17+
});
18+
});
19+
}

src/dbops/UpdateDevice.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { getConnection } from "../database/Connection";
2+
const dbConnection = getConnection();
3+
4+
export function UpdateDevice(DeviceHWID: string, DeviceName: string, DeviceDescription: string) {
5+
const sqlStatement = `UPDATE Devices SET DeviceName = ?, DeviceDescription = ? WHERE DeviceHWID = ?`;
6+
return new Promise((resolve, reject) => {
7+
dbConnection
8+
.promise()
9+
.execute(sqlStatement, [DeviceName, DeviceDescription, DeviceHWID])
10+
.then(([rows, fields]) => {
11+
resolve(rows);
12+
});
13+
});
14+
}

src/dbops/WriteAuditEntry.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { getConnection } from "../database/Connection";
2+
const connection = getConnection(); // get mysql2 connection from pool
3+
4+
export function writeAuditEntry(DeviceHWID: string, ActorUsername: string, Action: string, ActionData: string) {
5+
return new Promise((resolve, reject) => {
6+
console.log("Auditor: audit entry", DeviceHWID, ActorUsername, Action, ActionData)
7+
connection.query(
8+
`INSERT INTO \`PT_Audit_Log\` (\`DeviceHWID\`, \`actorUsername\`, \`action\`, \`actionData\`, \`timestamp\`) VALUES (?, ?, ?, ?, ?)`,
9+
[DeviceHWID, ActorUsername, Action, ActionData, Date.now()],
10+
(err, results) => {
11+
if (err) {
12+
console.error("[Error] Failed to write audit entry to database.");
13+
console.error(err);
14+
15+
reject(err);
16+
} else {
17+
resolve(results);
18+
}
19+
}
20+
);
21+
});
22+
}

src/types/AuditEntry.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* FROM DATABASE:
3+
* id: number;
4+
* timestamp: big int;
5+
* DeviceHWID: VARCHAR
6+
* action: VARCHAR
7+
* actionData: VARCHAR
8+
* actorUsername: VARCHAR
9+
* */
10+
11+
export type AuditEntry = {
12+
id: number;
13+
timestamp: number;
14+
DeviceHWID: string;
15+
action: string;
16+
actionData: string;
17+
actorUsername: string;
18+
};

src/types/GetOrCreateInviteHash.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,29 @@ export function GetOrCreateInviteHash(DeviceHWID: string, Username: string): Pro
4949
}
5050

5151

52-
52+
53+
export function InvalidateHash(DeviceHWID: string, Username: string) {
54+
return new Promise((resolve, reject) => {
55+
dbConnection
56+
.promise()
57+
.execute("SELECT * FROM Devices WHERE AccountOwnerID = (SELECT AccountID FROM Accounts WHERE Username = ?) AND DeviceHWID = ? ", [
58+
Username,
59+
DeviceHWID,
60+
])
61+
.then(([rows, fields]) => {
62+
if (Array.isArray(rows)) {
63+
if (rows.length === 0) {
64+
// It is not the owner of the device, reject the request.
65+
reject("Not the owner of the device.");
66+
} else {
67+
dbConnection
68+
.promise()
69+
.execute("DELETE FROM DevicesSubAccessInvites WHERE DeviceHWID = ?", [DeviceHWID])
70+
.then(([rows, fields]) => {
71+
resolve(true)
72+
});
73+
}
74+
}
75+
});
76+
});
77+
}

src/ws/ClientEvents.ts

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ import { RemoveSession } from "../dbops/RemoveSession";
2929
import { GetSessions } from "../dbops/GetSessions";
3030
import { GetImagery } from "../dbops/GetImagery";
3131
import { ClearNotifications } from "../dbops/ClearNotifications";
32-
import { GetOrCreateInviteHash } from "../types/GetOrCreateInviteHash";
32+
import { GetOrCreateInviteHash, InvalidateHash } from "../types/GetOrCreateInviteHash";
3333
import { GetInviteDetails } from "../dbops/GetInviteDetails";
3434
import { AcceptInvite } from "../dbops/AcceptInvite";
3535
import { GetDeviceMessages } from "../dbops/GetDeviceMessages";
3636
import { SendMessageToHWID } from "../dbops/SendMessageToHWID";
3737
import { GetAccountDetails, GetAccountDetailsUsername } from "../dbops/GetAccountDetails";
38+
import { UpdateDevice } from "../dbops/UpdateDevice";
39+
import { writeAuditEntry } from "../dbops/WriteAuditEntry";
40+
import { GetAudits } from "../dbops/GetAudits";
3841
export function ClientEvents(socket: Socket): void {
3942
function authDisconnect() {
4043
console.log("[Debug] Client socket authentication timed-out.");
@@ -208,7 +211,6 @@ export function ClientEvents(socket: Socket): void {
208211
success: false,
209212
message: "Device is offline.",
210213
},
211-
212214
};
213215
callback(failState);
214216
return;
@@ -223,11 +225,22 @@ export function ClientEvents(socket: Socket): void {
223225
};
224226
// A better idea might be introducing a new TypeScript type for updates
225227
// which don't require all DeviceBaseToggle fields to be present.
228+
226229
sendToggleStateToClientExceptSelf(socket, socket.data.subscribedDeviceHwid, local); // We do not need to wait for other clients to ack.
227230
// Uncomment this for debugging... (Sends the toggle state to the client that sent the toggle state)
228231
//sendToggleStateToClient(socket.data.subscribedDeviceHwid, local)
232+
229233
sendToggleToDevice(socket.data.subscribedDeviceHwid, local).then((res: ToggleResult) => {
230234
if (res.success) {
235+
// write success audit log entry
236+
writeAuditEntry(
237+
socket.data.subscribedDeviceHwid,
238+
socket.data.username,
239+
socket.data.username + " selected " + data.toggleName,
240+
socket.data.username + " selected " + data.toggleName + " successfully and it has finished"
241+
);
242+
243+
// mutate state
231244
mutateState(socket.data.subscribedDeviceHwid, (deviceState) => {
232245
console.log("Mutator", deviceState);
233246
for (let i = 0; i < deviceState.deviceToggles.length; i++) {
@@ -238,6 +251,13 @@ export function ClientEvents(socket: Socket): void {
238251
}
239252
});
240253
} else {
254+
// write failed audit log entry
255+
writeAuditEntry(
256+
socket.data.subscribedDeviceHwid,
257+
socket.data.username,
258+
socket.data.username + " selected " + data.toggleName,
259+
socket.data.username + " selected " + data.toggleName + ", but it has failed with error: " + res.message??"Unknown error"
260+
);
241261
const failed: ToggleWithStatus = {
242262
toggleName: data.toggleName,
243263
toggleValue: !data.toggleValue, // Invert since failed.
@@ -275,7 +295,7 @@ export function ClientEvents(socket: Socket): void {
275295
socket.on("GetParticularSchedule", (data: { scheduleId: string }, callback: any) => {
276296
if (!socket.data.authenticated) return;
277297
});
278-
298+
279299
socket.on(
280300
"GetStats",
281301
(
@@ -415,6 +435,12 @@ export function ClientEvents(socket: Socket): void {
415435
device.socket.volatile.emit("ManualPicture");
416436
});
417437

438+
socket.on("GetAuditLog", (data: never, callback) => {
439+
if (!socket.data.authenticated) return;
440+
GetAudits(socket.data.subscribedDeviceHwid).then((res) => {
441+
callback(res);
442+
});
443+
})
418444
socket.on("InviteHashes", (data: never, callback) => {
419445
if (!socket.data.authenticated) return;
420446
GetOrCreateInviteHash(socket.data.subscribedDeviceHwid, socket.data.username)
@@ -510,7 +536,7 @@ export function ClientEvents(socket: Socket): void {
510536
sender: rez.AccountID,
511537
DeviceHWID: socket.data.subscribedDeviceHwid,
512538
};
513-
cs.socket.emit("MessagingReceive",msgLocal);
539+
cs.socket.emit("MessagingReceive", msgLocal);
514540
}
515541
});
516542
});
@@ -528,4 +554,62 @@ export function ClientEvents(socket: Socket): void {
528554
});
529555
});
530556
});
557+
558+
socket.on("RebootSubscribedDevice", (data: { hard?: boolean }, callback) => {
559+
if (!socket.data.authenticated) return;
560+
console.log("Got reboot request", data);
561+
const device = findDeviceSocket(socket.data.subscribedDeviceHwid);
562+
if (!device) {
563+
return;
564+
}
565+
device.socket.volatile.emit("Reboot", data, () => {
566+
if (callback) callback();
567+
});
568+
});
569+
570+
socket.on("RemoveTrigger", (data: ScheduledTask, callback) => {
571+
if (!socket.data.authenticated) return;
572+
console.log("Got remove trigger request", data);
573+
const device = findDeviceSocket(socket.data.subscribedDeviceHwid);
574+
if (!device) {
575+
return;
576+
}
577+
device.socket.volatile.emit("RemoveTrigger", data, () => {
578+
if (callback) callback();
579+
});
580+
});
581+
socket.on("DismissNotification", (data: { id: number }, callback) => {
582+
if (!socket.data.authenticated) return;
583+
ClearNotifications(socket.data.subscribedDeviceHwid, data.id).then((res) => {
584+
if (callback) callback();
585+
});
586+
});
587+
588+
socket.on("RevokeInviteHash", (data: never, callback) => {
589+
if (!socket.data.authenticated) return;
590+
InvalidateHash(socket.data.subscribedDeviceHwid, socket.data.username)
591+
.then((res) => {
592+
// return new hash
593+
GetOrCreateInviteHash(socket.data.subscribedDeviceHwid, socket.data.username).then((res) => {
594+
if (callback)
595+
callback({
596+
success: true,
597+
data: res,
598+
});
599+
});
600+
})
601+
.catch((e) => {
602+
console.log(e);
603+
if (callback)
604+
callback({
605+
success: false,
606+
message: e,
607+
});
608+
});
609+
});
610+
socket.on("UpdateSubscribedDevice", (data: { DeviceName: string; DeviceDescription: string }, callback) => {
611+
UpdateDevice(socket.data.subscribedDeviceHwid, data.DeviceName, data.DeviceDescription).then((res) => {
612+
if (callback) callback();
613+
});
614+
});
531615
}

src/ws/DeviceEvents.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CheckDeviceSessionValidity } from "../dbops/CheckDeviceSessionValidity"
33
import { addToDeviceSocketList, findDeviceSocket } from "./DeviceSocketList";
44
import { removeFromDeviceSocketList } from "./DeviceSocketList";
55
import { DeviceState } from "../types/DeviceState";
6-
import { sendStateToSubscribedClient } from "./ClientSocketList";
6+
import { getSocketsList, sendStateToSubscribedClient } from "./ClientSocketList";
77
import {
88
deviceConnected,
99
getDeviceState,
@@ -36,6 +36,12 @@ export function DeviceEvents(socket: Socket): void {
3636
*/
3737
socket.data.metricsThrottle = 0;
3838
console.log("[Debug] Device socket authenticated successfully.");
39+
const cs = getSocketsList();
40+
cs.forEach((c) => {
41+
if (c.currentSubscribedDeviceHwid.includes(socket.handshake.query.deviceHwid.toString())) {
42+
c.socket.volatile.emit("DeviceOn"); // Simple notification to the client that the device is offline.
43+
}
44+
});
3945
markDeviceAsOnline(socket.handshake.query.deviceHwid.toString());
4046
socket.data.deviceNumber = res.deviceId;
4147
deviceConnected(socket.handshake.query.deviceHwid.toString()).then((res) => {
@@ -55,7 +61,7 @@ export function DeviceEvents(socket: Socket): void {
5561
// This is a hacky way of writing the metric to the database.
5662
// Check if our last metric write was more than 5 minutes ago.
5763
if (Date.now() - socket.data.metricsThrottle > 1000 * 60 * 5) {
58-
WriteMetric(
64+
WriteMetric(
5965
Date.now(),
6066
"TEMP_COMBINED",
6167
JSON.stringify({
@@ -79,9 +85,9 @@ export function DeviceEvents(socket: Socket): void {
7985
} else {
8086
console.log("Metric write throttled.", Date.now() - socket.data.metricsThrottle);
8187
}
82-
8388
});
8489
markDeviceAsOnline(socket.handshake.query.deviceHwid.toString());
90+
8591
sendStateToSubscribedClient(
8692
socket.handshake.query.deviceHwid.toString(),
8793
getDeviceState(socket.handshake.query.deviceHwid.toString())
@@ -118,6 +124,15 @@ export function DeviceEvents(socket: Socket): void {
118124
// Push it to Firebase using our FirebaseNotificationHook component.
119125
// which is still TODO. But it won't error out.
120126
FirebaseNotificationHook(data);
127+
// Find clients subscribed to this device.
128+
const cs = getSocketsList();
129+
cs.forEach((c) => {
130+
if (c.currentSubscribedDeviceHwid.includes(socket.handshake.query.deviceHwid.toString())) {
131+
console.log("Push notification: Replicating to client:", c.socket.id, "device:", socket.handshake.query.deviceHwid.toString(),data);
132+
c.socket.emit("Notification", data); // Simple notification to the client that the device is offline.
133+
}
134+
});
135+
// Save it to the database.
121136
InsertNotification(data, socket.handshake.query.deviceHwid.toString()).then((state) => {
122137
console.log("Notification saved to database state", state);
123138

@@ -133,11 +148,19 @@ export function DeviceEvents(socket: Socket): void {
133148
});
134149
socket.on("disconnect", () => {
135150
console.log(`Device disconnected.`);
136-
console.log(socket?.handshake?.query?.deviceHwid);
151+
const weHaveHwid = socket?.handshake?.query?.deviceHwid;
152+
console.log(weHaveHwid);
137153
removeFromDeviceStreamList(socket.handshake.query.deviceHwid.toString());
138154
removeFromDeviceSocketList(socket);
139155

140156
markDeviceAsOffline(socket.handshake.query.deviceHwid.toString());
157+
// find any clients that are subscribed to this device and notify them.
158+
const cs = getSocketsList();
159+
cs.forEach((c) => {
160+
if (c.currentSubscribedDeviceHwid.includes(socket.handshake.query.deviceHwid.toString())) {
161+
c.socket.volatile.emit("DeviceOff"); // Simple notification to the client that the device is offline.
162+
}
163+
});
141164

142165
socket.disconnect();
143166
});

0 commit comments

Comments
 (0)