This repository was archived by the owner on Sep 15, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGenericClient.ts
121 lines (112 loc) · 4.4 KB
/
GenericClient.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
///<references path="../../node_modules/@types/node/index.d.ts" />
///<references path="../../node_modules/@types/sax/index.d.ts" />
const net = require('net')
import * as events from 'events'
import { SAXParser } from 'sax'
export class GenericClient extends events.EventEmitter {
private clientSocket: any
private name: string
status: ClientStatus.Status = ClientStatus.Status.NOT_CONNECTED
ready: Promise<void>
private allData: string = '' // all data received (for saving to a replay file)
private dataSoFar: string = '' // all data received after the last completed message
private currentData: string // all data received at the current data-event
private firstTagOfMessage: string = undefined // the first opening tag of a message
private firstTagPosition: number
private messageLevel: number = 0 // to keep track of the xml nesting, if this drops to zero, we found a matching closing tag for the first opened tag of the message
private offset: number = 0 // the position of the first character in the dataSoFar buffer as far as the parser is concerned (the parser parsed all messages)
// TODO: this parser is only used to find the start and end of a xml message. The message string is then emitted as message and parsed AGAIN. This should be merged in the future.
private parser = new SAXParser(true, {})
constructor(host: string, port: number, sendProtocol: boolean = true, name?: string) {
super()
this.name = name
this.clientSocket = net.createConnection(port, host, () => {
if (sendProtocol) {
this.writeData('<protocol>', () => {
this.setStatus(ClientStatus.Status.CONNECTED) //NodeJS Sockets don't have flush, so this will have to do
})
} else {
this.setStatus(ClientStatus.Status.CONNECTED)
}
})
this.parser.onopentag = (tag) => {
if (tag.name != 'protocol') {
if (this.firstTagOfMessage == undefined) {
this.firstTagOfMessage = tag.name
this.firstTagPosition = this.parser.startTagPosition - 1 // one character more for the <
this.messageLevel = 0
}
if (tag.name == this.firstTagOfMessage) {
this.messageLevel++
}
}
}
this.parser.onclosetag = (tagName) => {
if (tagName == this.firstTagOfMessage) {
this.messageLevel--
}
if (this.messageComplete()) {
const msg = this.dataSoFar.substring(this.firstTagPosition - this.offset, this.parser.position - this.offset)
//Logger.getLogger().log(this.name ? this.name : "GenericClient", "emitMessage", msg);
this.emit('message', msg)
const nextStart = this.parser.position - this.offset
this.offset = this.parser.position
this.dataSoFar = this.dataSoFar.substring(nextStart)
this.firstTagOfMessage = undefined
}
}
this.clientSocket.on('data', (data: string) => {
const clientName = this.name ? this.name : 'unnamed client'
this.allData += data
this.currentData = data
this.dataSoFar += data
this.parser.write(this.currentData)
//Logger.getLogger().log(this.name ? this.name : "GenericClient", "receiveData", data);
})
this.clientSocket.on('end', () => {
this.setStatus(ClientStatus.Status.DISCONNECTED)
})
this.ready = new Promise<void>((res, rej) => {
this.on('status', s => {
const clientName = this.name ? this.name : 'unnamed client'
if (s == ClientStatus.Status.CONNECTED) {
res()
}
})
})
}
writeData(data: string, callback?: () => void) {
//Logger.getLogger().log(this.name ? this.name : "GenericClient", "writeData", data);
this.clientSocket.write(data, callback)
}
private messageComplete(): boolean {
return (this.firstTagOfMessage != undefined && this.messageLevel == 0)
}
private setStatus(s: ClientStatus.Status) {
this.status = s
this.emit('status', s)
}
public getAllData() {
return this.allData
}
}
export module ClientStatus {
export enum Status {
NOT_CONNECTED,
CONNECTED,
DISCONNECTED,
ERROR
}
export function toString(s: ClientStatus.Status): string {
switch (s) {
case ClientStatus.Status.NOT_CONNECTED:
return 'NOT CONNECTED'
case ClientStatus.Status.CONNECTED:
return 'CONNECTED'
case ClientStatus.Status.ERROR:
return 'ERROR'
case ClientStatus.Status.DISCONNECTED:
return 'DISCONNECTED'
}
}
}