monocart-reporter 2.9.6 → 2.9.8
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/LICENSE +21 -21
- package/README.md +1180 -1180
- package/lib/cli.js +372 -372
- package/lib/common.js +244 -244
- package/lib/default/columns.js +79 -79
- package/lib/default/options.js +95 -95
- package/lib/default/summary.js +80 -80
- package/lib/default/template.html +47 -47
- package/lib/generate-data.js +174 -174
- package/lib/generate-report.js +360 -360
- package/lib/index.d.ts +268 -268
- package/lib/index.js +253 -253
- package/lib/index.mjs +19 -19
- package/lib/merge-data.js +405 -405
- package/lib/packages/monocart-reporter-assets.js +3 -3
- package/lib/packages/monocart-reporter-vendor.js +22 -23
- package/lib/platform/concurrency.js +74 -74
- package/lib/platform/share.js +369 -369
- package/lib/plugins/audit/audit.js +119 -119
- package/lib/plugins/comments.js +124 -124
- package/lib/plugins/coverage/coverage.js +169 -169
- package/lib/plugins/email.js +76 -76
- package/lib/plugins/metadata/metadata.js +25 -25
- package/lib/plugins/network/network.js +186 -186
- package/lib/plugins/state/client.js +152 -152
- package/lib/plugins/state/state.js +194 -194
- package/lib/utils/pie.js +148 -148
- package/lib/utils/system.js +145 -145
- package/lib/utils/util.js +512 -511
- package/lib/visitor.js +915 -915
- package/package.json +10 -10
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const Util = require('../../utils/util.js');
|
|
4
|
-
const Assets = require('../../assets.js');
|
|
5
|
-
|
|
6
|
-
const getHarData = (har) => {
|
|
7
|
-
if (typeof har === 'string') {
|
|
8
|
-
if (!fs.existsSync(har)) {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
return Util.readJSONSync(har);
|
|
12
|
-
}
|
|
13
|
-
if (Buffer.isBuffer(har)) {
|
|
14
|
-
return JSON.parse(har.toString('utf-8'));
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// http://www.softwareishard.com/blog/har-12-spec/
|
|
19
|
-
const getNetworkSummary = (log) => {
|
|
20
|
-
|
|
21
|
-
const { entries, pages } = log;
|
|
22
|
-
|
|
23
|
-
const waterfalls = {};
|
|
24
|
-
pages.forEach((page) => {
|
|
25
|
-
const pageTimings = page.pageTimings;
|
|
26
|
-
const timestampStart = new Date(page.startedDateTime).getTime();
|
|
27
|
-
const time = Math.max(pageTimings.onContentLoad, pageTimings.onLoad) || 0;
|
|
28
|
-
waterfalls[page.id] = {
|
|
29
|
-
timestampStart,
|
|
30
|
-
start: 0,
|
|
31
|
-
time,
|
|
32
|
-
... pageTimings,
|
|
33
|
-
entries: []
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const summary = {
|
|
38
|
-
requests: entries.length,
|
|
39
|
-
size: 0,
|
|
40
|
-
status: {},
|
|
41
|
-
methods: {},
|
|
42
|
-
waterfalls
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const countField = (k, v) => {
|
|
46
|
-
if (summary[k][v]) {
|
|
47
|
-
summary[k][v] += 1;
|
|
48
|
-
} else {
|
|
49
|
-
summary[k][v] = 1;
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const countRequest = (req) => {
|
|
54
|
-
const {
|
|
55
|
-
bodySize, headersSize, method
|
|
56
|
-
} = req;
|
|
57
|
-
countField('methods', method);
|
|
58
|
-
|
|
59
|
-
summary.size += Math.max(bodySize, 0) + Math.max(headersSize, 0);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const countResponse = (res) => {
|
|
63
|
-
const {
|
|
64
|
-
bodySize, headersSize, status
|
|
65
|
-
} = res;
|
|
66
|
-
countField('status', status);
|
|
67
|
-
|
|
68
|
-
summary.size += (Math.max(headersSize, 0) + Math.max(bodySize, 0));
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const countWaterfall = (entry) => {
|
|
72
|
-
|
|
73
|
-
const pageWaterfall = waterfalls[entry.pageref];
|
|
74
|
-
|
|
75
|
-
const timestampStart = new Date(entry.startedDateTime).getTime();
|
|
76
|
-
const entryWaterfall = {
|
|
77
|
-
start: timestampStart - pageWaterfall.timestampStart,
|
|
78
|
-
time: Math.max(entry.time, 0),
|
|
79
|
-
... entry.timings
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
pageWaterfall.time = Math.max(pageWaterfall.time, entryWaterfall.start + entryWaterfall.time);
|
|
83
|
-
pageWaterfall.entries.push(entryWaterfall);
|
|
84
|
-
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
entries.forEach((entry) => {
|
|
88
|
-
|
|
89
|
-
countRequest(entry.request);
|
|
90
|
-
countResponse(entry.response);
|
|
91
|
-
countWaterfall(entry);
|
|
92
|
-
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
return summary;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const saveNetworkHtmlReport = async (reportData, _options) => {
|
|
99
|
-
|
|
100
|
-
const { htmlDir, inline } = _options;
|
|
101
|
-
|
|
102
|
-
// deps
|
|
103
|
-
const jsFiles = ['monocart-reporter-network'];
|
|
104
|
-
|
|
105
|
-
const options = {
|
|
106
|
-
inline,
|
|
107
|
-
reportData,
|
|
108
|
-
jsFiles,
|
|
109
|
-
assetsPath: '../assets',
|
|
110
|
-
outputDir: htmlDir,
|
|
111
|
-
htmlFile: 'index.html',
|
|
112
|
-
|
|
113
|
-
reportDataFile: 'network-data.js'
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const htmlPath = await Assets.saveHtmlReport(options);
|
|
117
|
-
|
|
118
|
-
return htmlPath;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const attachNetworkReport = async (har, testInfo, options = {}) => {
|
|
122
|
-
|
|
123
|
-
const logging = Util.resolveLogging(testInfo, options);
|
|
124
|
-
Util.initLoggingLevel(logging, 'network');
|
|
125
|
-
|
|
126
|
-
const harData = getHarData(har);
|
|
127
|
-
if (!harData || !harData.log) {
|
|
128
|
-
Util.logError(`failed to load HAR: ${har}`);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const outputDir = Util.resolveOutputDir(testInfo);
|
|
133
|
-
|
|
134
|
-
options = {
|
|
135
|
-
// default title
|
|
136
|
-
name: `Network Report - ${testInfo.title}`,
|
|
137
|
-
outputDir,
|
|
138
|
-
outputName: `network-${Util.resolveTestIdWithRetry(testInfo)}`,
|
|
139
|
-
inline: false,
|
|
140
|
-
... options
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const htmlDir = path.resolve(options.outputDir, options.outputName);
|
|
144
|
-
if (!fs.existsSync(htmlDir)) {
|
|
145
|
-
fs.mkdirSync(htmlDir, {
|
|
146
|
-
recursive: true
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
options.htmlDir = htmlDir;
|
|
150
|
-
|
|
151
|
-
const summary = getNetworkSummary(harData.log);
|
|
152
|
-
|
|
153
|
-
// save har
|
|
154
|
-
const reportData = {
|
|
155
|
-
name: options.name,
|
|
156
|
-
summary,
|
|
157
|
-
... harData
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const htmlPath = await saveNetworkHtmlReport(reportData, options);
|
|
161
|
-
|
|
162
|
-
const report = {
|
|
163
|
-
name: options.name,
|
|
164
|
-
... harData.log,
|
|
165
|
-
htmlPath,
|
|
166
|
-
summary
|
|
167
|
-
};
|
|
168
|
-
delete report.entries;
|
|
169
|
-
|
|
170
|
-
// save report json
|
|
171
|
-
const definition = Util.attachments.network;
|
|
172
|
-
const reportPath = path.resolve(htmlDir, definition.reportFile);
|
|
173
|
-
Util.writeJSONSync(reportPath, report);
|
|
174
|
-
|
|
175
|
-
testInfo.attachments.push({
|
|
176
|
-
name: definition.name,
|
|
177
|
-
contentType: definition.contentType,
|
|
178
|
-
path: path.resolve(htmlDir, 'index.html')
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return report;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
module.exports = {
|
|
185
|
-
attachNetworkReport
|
|
186
|
-
};
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Util = require('../../utils/util.js');
|
|
4
|
+
const Assets = require('../../assets.js');
|
|
5
|
+
|
|
6
|
+
const getHarData = (har) => {
|
|
7
|
+
if (typeof har === 'string') {
|
|
8
|
+
if (!fs.existsSync(har)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
return Util.readJSONSync(har);
|
|
12
|
+
}
|
|
13
|
+
if (Buffer.isBuffer(har)) {
|
|
14
|
+
return JSON.parse(har.toString('utf-8'));
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// http://www.softwareishard.com/blog/har-12-spec/
|
|
19
|
+
const getNetworkSummary = (log) => {
|
|
20
|
+
|
|
21
|
+
const { entries, pages } = log;
|
|
22
|
+
|
|
23
|
+
const waterfalls = {};
|
|
24
|
+
pages.forEach((page) => {
|
|
25
|
+
const pageTimings = page.pageTimings;
|
|
26
|
+
const timestampStart = new Date(page.startedDateTime).getTime();
|
|
27
|
+
const time = Math.max(pageTimings.onContentLoad, pageTimings.onLoad) || 0;
|
|
28
|
+
waterfalls[page.id] = {
|
|
29
|
+
timestampStart,
|
|
30
|
+
start: 0,
|
|
31
|
+
time,
|
|
32
|
+
... pageTimings,
|
|
33
|
+
entries: []
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const summary = {
|
|
38
|
+
requests: entries.length,
|
|
39
|
+
size: 0,
|
|
40
|
+
status: {},
|
|
41
|
+
methods: {},
|
|
42
|
+
waterfalls
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const countField = (k, v) => {
|
|
46
|
+
if (summary[k][v]) {
|
|
47
|
+
summary[k][v] += 1;
|
|
48
|
+
} else {
|
|
49
|
+
summary[k][v] = 1;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const countRequest = (req) => {
|
|
54
|
+
const {
|
|
55
|
+
bodySize, headersSize, method
|
|
56
|
+
} = req;
|
|
57
|
+
countField('methods', method);
|
|
58
|
+
|
|
59
|
+
summary.size += Math.max(bodySize, 0) + Math.max(headersSize, 0);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const countResponse = (res) => {
|
|
63
|
+
const {
|
|
64
|
+
bodySize, headersSize, status
|
|
65
|
+
} = res;
|
|
66
|
+
countField('status', status);
|
|
67
|
+
|
|
68
|
+
summary.size += (Math.max(headersSize, 0) + Math.max(bodySize, 0));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const countWaterfall = (entry) => {
|
|
72
|
+
|
|
73
|
+
const pageWaterfall = waterfalls[entry.pageref];
|
|
74
|
+
|
|
75
|
+
const timestampStart = new Date(entry.startedDateTime).getTime();
|
|
76
|
+
const entryWaterfall = {
|
|
77
|
+
start: timestampStart - pageWaterfall.timestampStart,
|
|
78
|
+
time: Math.max(entry.time, 0),
|
|
79
|
+
... entry.timings
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
pageWaterfall.time = Math.max(pageWaterfall.time, entryWaterfall.start + entryWaterfall.time);
|
|
83
|
+
pageWaterfall.entries.push(entryWaterfall);
|
|
84
|
+
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
entries.forEach((entry) => {
|
|
88
|
+
|
|
89
|
+
countRequest(entry.request);
|
|
90
|
+
countResponse(entry.response);
|
|
91
|
+
countWaterfall(entry);
|
|
92
|
+
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return summary;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const saveNetworkHtmlReport = async (reportData, _options) => {
|
|
99
|
+
|
|
100
|
+
const { htmlDir, inline } = _options;
|
|
101
|
+
|
|
102
|
+
// deps
|
|
103
|
+
const jsFiles = ['monocart-reporter-network'];
|
|
104
|
+
|
|
105
|
+
const options = {
|
|
106
|
+
inline,
|
|
107
|
+
reportData,
|
|
108
|
+
jsFiles,
|
|
109
|
+
assetsPath: '../assets',
|
|
110
|
+
outputDir: htmlDir,
|
|
111
|
+
htmlFile: 'index.html',
|
|
112
|
+
|
|
113
|
+
reportDataFile: 'network-data.js'
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const htmlPath = await Assets.saveHtmlReport(options);
|
|
117
|
+
|
|
118
|
+
return htmlPath;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const attachNetworkReport = async (har, testInfo, options = {}) => {
|
|
122
|
+
|
|
123
|
+
const logging = Util.resolveLogging(testInfo, options);
|
|
124
|
+
Util.initLoggingLevel(logging, 'network');
|
|
125
|
+
|
|
126
|
+
const harData = getHarData(har);
|
|
127
|
+
if (!harData || !harData.log) {
|
|
128
|
+
Util.logError(`failed to load HAR: ${har}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const outputDir = Util.resolveOutputDir(testInfo);
|
|
133
|
+
|
|
134
|
+
options = {
|
|
135
|
+
// default title
|
|
136
|
+
name: `Network Report - ${testInfo.title}`,
|
|
137
|
+
outputDir,
|
|
138
|
+
outputName: `network-${Util.resolveTestIdWithRetry(testInfo)}`,
|
|
139
|
+
inline: false,
|
|
140
|
+
... options
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const htmlDir = path.resolve(options.outputDir, options.outputName);
|
|
144
|
+
if (!fs.existsSync(htmlDir)) {
|
|
145
|
+
fs.mkdirSync(htmlDir, {
|
|
146
|
+
recursive: true
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
options.htmlDir = htmlDir;
|
|
150
|
+
|
|
151
|
+
const summary = getNetworkSummary(harData.log);
|
|
152
|
+
|
|
153
|
+
// save har
|
|
154
|
+
const reportData = {
|
|
155
|
+
name: options.name,
|
|
156
|
+
summary,
|
|
157
|
+
... harData
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const htmlPath = await saveNetworkHtmlReport(reportData, options);
|
|
161
|
+
|
|
162
|
+
const report = {
|
|
163
|
+
name: options.name,
|
|
164
|
+
... harData.log,
|
|
165
|
+
htmlPath,
|
|
166
|
+
summary
|
|
167
|
+
};
|
|
168
|
+
delete report.entries;
|
|
169
|
+
|
|
170
|
+
// save report json
|
|
171
|
+
const definition = Util.attachments.network;
|
|
172
|
+
const reportPath = path.resolve(htmlDir, definition.reportFile);
|
|
173
|
+
Util.writeJSONSync(reportPath, report);
|
|
174
|
+
|
|
175
|
+
testInfo.attachments.push({
|
|
176
|
+
name: definition.name,
|
|
177
|
+
contentType: definition.contentType,
|
|
178
|
+
path: path.resolve(htmlDir, 'index.html')
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return report;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
attachNetworkReport
|
|
186
|
+
};
|
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
const { WebSocket } = require('../../packages/monocart-reporter-vendor.js');
|
|
2
|
-
const Util = require('../../utils/util.js');
|
|
3
|
-
|
|
4
|
-
const getServerUrl = (options = {}) => {
|
|
5
|
-
return `ws://${options.host}:${options.port}`;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
class Client {
|
|
9
|
-
|
|
10
|
-
constructor(clientOptions) {
|
|
11
|
-
this.options = clientOptions;
|
|
12
|
-
this.requests = new Map();
|
|
13
|
-
this.resolves = [];
|
|
14
|
-
|
|
15
|
-
const serverUrl = getServerUrl(clientOptions);
|
|
16
|
-
|
|
17
|
-
// https://github.com/websockets/ws/blob/master/doc/ws.md
|
|
18
|
-
const ws = new WebSocket(serverUrl);
|
|
19
|
-
|
|
20
|
-
ws.on('error', (err) => {
|
|
21
|
-
|
|
22
|
-
// socket hang up: the port is exists but not websocket server
|
|
23
|
-
// connect ECONNREFUSED: websocket server unavailable
|
|
24
|
-
|
|
25
|
-
if (Util.isList(this.resolves)) {
|
|
26
|
-
this.resolves.forEach((item) => {
|
|
27
|
-
item.reject(err);
|
|
28
|
-
});
|
|
29
|
-
this.resolves = null;
|
|
30
|
-
} else {
|
|
31
|
-
Util.logError(`websocket error: ${err.message}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
ws.on('message', (data) => {
|
|
37
|
-
this.onMessage(data);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
ws.on('open', () => {
|
|
41
|
-
this.ws = ws;
|
|
42
|
-
if (Util.isList(this.resolves)) {
|
|
43
|
-
this.resolves.forEach((item) => {
|
|
44
|
-
item.resolve(ws);
|
|
45
|
-
});
|
|
46
|
-
this.resolves = null;
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
connect() {
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
|
|
54
|
-
if (this.ws) {
|
|
55
|
-
resolve(this.ws);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (this.resolves) {
|
|
60
|
-
this.resolves.push({
|
|
61
|
-
resolve,
|
|
62
|
-
reject
|
|
63
|
-
});
|
|
64
|
-
} else {
|
|
65
|
-
reject(new Error('an error occurs when connecting websocket server'));
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
onMessage(buf) {
|
|
71
|
-
const message = JSON.parse(buf.toString());
|
|
72
|
-
if (!message) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const { id, data } = message;
|
|
76
|
-
if (!id) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const req = this.requests.get(id);
|
|
81
|
-
if (!req) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.requests.delete(id);
|
|
86
|
-
|
|
87
|
-
clearTimeout(req.timeout_id);
|
|
88
|
-
req.resolve(data);
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async execute(data) {
|
|
93
|
-
|
|
94
|
-
let err;
|
|
95
|
-
const ws = await this.connect().catch((e) => {
|
|
96
|
-
err = e;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
return new Promise((resolve, reject) => {
|
|
100
|
-
|
|
101
|
-
if (!ws) {
|
|
102
|
-
reject(err);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const id = Util.uid();
|
|
107
|
-
const timeout = this.options.timeout;
|
|
108
|
-
const timeout_id = setTimeout(() => {
|
|
109
|
-
this.requests.delete(id);
|
|
110
|
-
reject(new Error(`timed out receiving message from websocket server: ${timeout}ms`));
|
|
111
|
-
}, timeout);
|
|
112
|
-
|
|
113
|
-
this.requests.set(id, {
|
|
114
|
-
resolve,
|
|
115
|
-
timeout_id
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const message = JSON.stringify({
|
|
119
|
-
id,
|
|
120
|
-
data
|
|
121
|
-
});
|
|
122
|
-
ws.send(message);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
state() {
|
|
127
|
-
return {
|
|
128
|
-
get: (... args) => {
|
|
129
|
-
return this.execute(['get', ... args]);
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
set: (... args) => {
|
|
133
|
-
return this.execute(['set', ... args]);
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
remove: (... args) => {
|
|
137
|
-
return this.execute(['remove', ... args]);
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
send: (... args) => {
|
|
141
|
-
return this.execute(['send', ... args]);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
module.exports = {
|
|
150
|
-
getServerUrl,
|
|
151
|
-
Client
|
|
152
|
-
};
|
|
1
|
+
const { WebSocket } = require('../../packages/monocart-reporter-vendor.js');
|
|
2
|
+
const Util = require('../../utils/util.js');
|
|
3
|
+
|
|
4
|
+
const getServerUrl = (options = {}) => {
|
|
5
|
+
return `ws://${options.host}:${options.port}`;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
class Client {
|
|
9
|
+
|
|
10
|
+
constructor(clientOptions) {
|
|
11
|
+
this.options = clientOptions;
|
|
12
|
+
this.requests = new Map();
|
|
13
|
+
this.resolves = [];
|
|
14
|
+
|
|
15
|
+
const serverUrl = getServerUrl(clientOptions);
|
|
16
|
+
|
|
17
|
+
// https://github.com/websockets/ws/blob/master/doc/ws.md
|
|
18
|
+
const ws = new WebSocket(serverUrl);
|
|
19
|
+
|
|
20
|
+
ws.on('error', (err) => {
|
|
21
|
+
|
|
22
|
+
// socket hang up: the port is exists but not websocket server
|
|
23
|
+
// connect ECONNREFUSED: websocket server unavailable
|
|
24
|
+
|
|
25
|
+
if (Util.isList(this.resolves)) {
|
|
26
|
+
this.resolves.forEach((item) => {
|
|
27
|
+
item.reject(err);
|
|
28
|
+
});
|
|
29
|
+
this.resolves = null;
|
|
30
|
+
} else {
|
|
31
|
+
Util.logError(`websocket error: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
ws.on('message', (data) => {
|
|
37
|
+
this.onMessage(data);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
ws.on('open', () => {
|
|
41
|
+
this.ws = ws;
|
|
42
|
+
if (Util.isList(this.resolves)) {
|
|
43
|
+
this.resolves.forEach((item) => {
|
|
44
|
+
item.resolve(ws);
|
|
45
|
+
});
|
|
46
|
+
this.resolves = null;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
connect() {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
|
|
54
|
+
if (this.ws) {
|
|
55
|
+
resolve(this.ws);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (this.resolves) {
|
|
60
|
+
this.resolves.push({
|
|
61
|
+
resolve,
|
|
62
|
+
reject
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
reject(new Error('an error occurs when connecting websocket server'));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onMessage(buf) {
|
|
71
|
+
const message = JSON.parse(buf.toString());
|
|
72
|
+
if (!message) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const { id, data } = message;
|
|
76
|
+
if (!id) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const req = this.requests.get(id);
|
|
81
|
+
if (!req) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.requests.delete(id);
|
|
86
|
+
|
|
87
|
+
clearTimeout(req.timeout_id);
|
|
88
|
+
req.resolve(data);
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async execute(data) {
|
|
93
|
+
|
|
94
|
+
let err;
|
|
95
|
+
const ws = await this.connect().catch((e) => {
|
|
96
|
+
err = e;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
|
|
101
|
+
if (!ws) {
|
|
102
|
+
reject(err);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const id = Util.uid();
|
|
107
|
+
const timeout = this.options.timeout;
|
|
108
|
+
const timeout_id = setTimeout(() => {
|
|
109
|
+
this.requests.delete(id);
|
|
110
|
+
reject(new Error(`timed out receiving message from websocket server: ${timeout}ms`));
|
|
111
|
+
}, timeout);
|
|
112
|
+
|
|
113
|
+
this.requests.set(id, {
|
|
114
|
+
resolve,
|
|
115
|
+
timeout_id
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const message = JSON.stringify({
|
|
119
|
+
id,
|
|
120
|
+
data
|
|
121
|
+
});
|
|
122
|
+
ws.send(message);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
state() {
|
|
127
|
+
return {
|
|
128
|
+
get: (... args) => {
|
|
129
|
+
return this.execute(['get', ... args]);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
set: (... args) => {
|
|
133
|
+
return this.execute(['set', ... args]);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
remove: (... args) => {
|
|
137
|
+
return this.execute(['remove', ... args]);
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
send: (... args) => {
|
|
141
|
+
return this.execute(['send', ... args]);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
getServerUrl,
|
|
151
|
+
Client
|
|
152
|
+
};
|