1 | ;
|
2 | const isObject = (x) => x && (typeof x === 'object' || typeof x === 'function');
|
3 | const http = require('http');
|
4 |
|
5 |
|
6 | /*******************************************************************************\
|
7 | ** **
|
8 | ** **
|
9 | ** ================================================== **
|
10 | ** ======== npm package: good-request ======== **
|
11 | ** ================================================== ** **
|
12 | ** **
|
13 | ** New 7/31 version, first published version. **
|
14 | ** "He was ten feet short; he RAN down the ramp. He RAAAN!" **
|
15 | ** **
|
16 | ** nice-request, neat-request, and my-request are already taken. **
|
17 | ** better-request better back down. **
|
18 | ** **
|
19 | ** Based on the 2020-07-31 example documented at: **
|
20 | ** https://nodejs.org/api/http.html#http_http_request_options_callback **
|
21 | ** **
|
22 | ** By wv-coder. **
|
23 | ** **
|
24 | ** **
|
25 | ** ==== API ==== **
|
26 | ** **
|
27 | ** Functions provided: **
|
28 | ** - basicPost (urlString, ct, reqText, callback) **
|
29 | ** - retryingPost (urlString, ct, reqText, nRetries, callback) **
|
30 | ** - retryingJsonPost (urlString, reqObject, nRetries, callback) **
|
31 | ** **
|
32 | ** Callback provides an object with some of the following properties: **
|
33 | ** - received: true | undefined **
|
34 | ** - statusCode: response status integer | undefined **
|
35 | ** - text: response body string | undefined **
|
36 | ** - error: error object | undefined **
|
37 | ** - errorsRetried: array of error objects | undefined **
|
38 | ** - object: response body jso | undefined **
|
39 | ** **
|
40 | ** **
|
41 | \*******************************************************************************/
|
42 |
|
43 |
|
44 | const basicPost =
|
45 | exports. basicPost =
|
46 | function basicPost (urlString, ct, reqText, callback) {
|
47 | const url = new URL(urlString);
|
48 | if (typeof reqText !== 'string') reqText = `${reqText}`;
|
49 | const options = {
|
50 | hostname: url.hostname,
|
51 | port: parseInt(url.port),
|
52 | path: url.pathname,
|
53 | method: 'POST',
|
54 | headers: {
|
55 | 'Content-Type': ct || 'text/plain',
|
56 | 'Content-Length': Buffer.byteLength(reqText),
|
57 | },
|
58 | };
|
59 | const req = http.request(options, (res) => {
|
60 | const resultTextChunks = [];
|
61 | res.setEncoding('utf8');
|
62 | res.on('data', (chunk) => {
|
63 | resultTextChunks.push(chunk);
|
64 | });
|
65 | res.on('end', () => {
|
66 | callback({
|
67 | received: true,
|
68 | statusCode: res.statusCode,
|
69 | text: resultTextChunks.join(''),
|
70 | });
|
71 | });
|
72 | });
|
73 | req.on('error', (e) => {
|
74 | callback({ error: e });
|
75 | });
|
76 | req.write(reqText);
|
77 | req.end();
|
78 | };
|
79 |
|
80 |
|
81 | const retryingPost =
|
82 | exports. retryingPost =
|
83 | function retryingPost (urlString, ct, reqText, nRetries, callback) {
|
84 | const errorsRetried = [];
|
85 | const innerCallback = (result) => {
|
86 | nRetries--;
|
87 | if (result.error && nRetries > 0) {
|
88 | errorsRetried.push(result.error);
|
89 | basicPost(urlString, ct, reqText, innerCallback);
|
90 | } else {
|
91 | result.errorsRetried = errorsRetried;
|
92 | callback(result);
|
93 | }
|
94 | };
|
95 | return basicPost(urlString, ct, reqText, innerCallback);
|
96 | };
|
97 |
|
98 |
|
99 | const retryingJsonPost =
|
100 | exports. retryingJsonPost =
|
101 | function retryingJsonPost (urlString, reqObject, nRetries, callback) {
|
102 | const reqText = `${JSON.stringify(reqObject, undefined, 2)}\n`;
|
103 | const innerCallback = (result) => {
|
104 | try {
|
105 | result.object = JSON.parse(result.text);
|
106 | } catch (jsonParseEx) {
|
107 | if (!result.error) result.error = jsonParseEx;
|
108 | }
|
109 | callback(result);
|
110 | };
|
111 | return retryingPost(urlString, 'application/json', reqText, nRetries, innerCallback);
|
112 | };
|
113 |
|
114 |
|
115 | /*************************************************************\
|
116 | ** **
|
117 | ** ==== HTML Generator ==== **
|
118 | ** 9/14/2020 AV **
|
119 | ** **
|
120 | ** Many times in the past I tried to make this module. **
|
121 | ** I think I understand well enough to do so now. **
|
122 | ** **
|
123 | ** Google.com will be the default model, **
|
124 | ** to default to power while avoiding questions. **
|
125 | ** **
|
126 | ** It's not perfect, nor do I intend it to be yet. **
|
127 | ** **
|
128 | \*************************************************************/
|
129 |
|
130 |
|
131 | const collapseStr =
|
132 | exports. collapseStr =
|
133 | function collapseStr (x) {
|
134 | return x ? (Array.isArray(x) ? x.map(collapseStr).join('') : '' + x) : '';
|
135 | };
|
136 |
|
137 |
|
138 | const makeHtml =
|
139 | exports. makeHtml =
|
140 | function makeHtml ({ lang, fixed, title, style, head, body, mainDiv, script }) {
|
141 |
|
142 | const bits = [];
|
143 |
|
144 | // Just as Google does, I use a lowercase DTD, because in theory it might compress better.
|
145 | bits.push('<!doctype html>');
|
146 |
|
147 | // Due to unfortunate browser implementation, it turns out lang="en" is the only neutral option,
|
148 | // because omitting lang="en" has come to mean "use machine learning to guess a random odd language and apply translation",
|
149 | // and the only way to avoid that annoyance is to say lang="en" regardless.
|
150 | bits.push(`<html lang="${lang || 'en'}">`);
|
151 |
|
152 | // Once again, it might compress better as lowercase.
|
153 | bits.push('<meta charset="utf-8">');
|
154 |
|
155 | bits.push(`<meta name="viewport" content="width=device-width, initial-scale=1${
|
156 | fixed ? ', minimum-scale=1, maximum-scale=1, user-scalable=no' : ''
|
157 | }">`);
|
158 |
|
159 | if (title) bits.push(`<title>${title}</title>`);
|
160 |
|
161 | if (style) {
|
162 | if (isObject(style) && style.src) {
|
163 | const each = Array.isArray(style.src) ? style.src : [style.src];
|
164 | for (let s of each) bits.push(`<link href="${s}" rel="stylesheet">`);
|
165 | } else bits.push(`<style>${collapseStr(style)}</style>`);
|
166 | }
|
167 |
|
168 | if (head) bits.push(collapseStr(head));
|
169 |
|
170 | bits.push('</head>');
|
171 |
|
172 | bits.push('<body>');
|
173 |
|
174 | if (body) bits.push(collapseStr(body));
|
175 |
|
176 | if (mainDiv) bits.push(`<div id="main">${collapseStr(mainDiv)}</div>`);
|
177 |
|
178 | if (script) {
|
179 | if (isObject(script) && script.src) {
|
180 | const each = Array.isArray(script.src) ? script.src : [script.src];
|
181 | for (let s of each) bits.push(`<script src="${s}"></script>`);
|
182 | } else bits.push(`<script>${collapseStr(script)}</script>`);
|
183 | }
|
184 |
|
185 | bits.push('</body>');
|
186 |
|
187 | bits.push('</html>');
|
188 |
|
189 | //TODO: Check "viewport". Test every path. Compare with old 'good-request'. Collapse with previous versions of 'makeHtml'.
|
190 |
|
191 | return bits.join('');
|
192 | };
|
193 |
|
194 |
|
195 | /********************************\
|
196 | ** **
|
197 | ** gss | good-string-sort **
|
198 | ** \********************************************\
|
199 | ** **
|
200 | ** This function correctly sorts strings according to Unicode Codepoints. **
|
201 | ** For example: **
|
202 | ** ['some', 'strings', 'in here.'].sort(gss); **
|
203 | ** **
|
204 | \****************************************************************************/
|
205 |
|
206 |
|
207 | const goodStringSort =
|
208 | exports. goodStringSort =
|
209 | function goodStringSort (a, b) {
|
210 | if (!(typeof a === 'string' && typeof b === 'string'))
|
211 | throw new Error('good-string-sort: Expects a pair of string arguments.');
|
212 | const codepointsA = [...a].map((c) => c.codePointAt());
|
213 | const codepointsB = [...b].map((c) => c.codePointAt());
|
214 | // Note that the length of the codepoint-arrays DIFFERS from the length of the raw strings due to SURROGATE PAIRS.
|
215 | const l = Math.max(codepointsA.length, codepointsB.length);
|
216 | for (let i = 0; i < l; i++) {
|
217 | let sortCharA = codepointsA[i]; if (sortCharA === undefined) sortCharA = -1;
|
218 | let sortCharB = codepointsB[i]; if (sortCharB === undefined) sortCharB = -1;
|
219 | if (sortCharA > sortCharB) return 1;
|
220 | else if (sortCharA < sortCharB) return -1;
|
221 | }
|
222 | return 0;
|
223 | };
|