ng-talkback 21.0.16 → 21.0.18
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/browser/package.json +1 -1
- package/browser-prod/package.json +1 -1
- package/lib/build-info._auto-generated_.d.ts +1 -1
- package/lib/build-info._auto-generated_.js +1 -1
- package/lib/package.json +1 -1
- package/lib-prod/{build-info._auto-generated_.ts → build-info._auto-generated_.js} +1 -2
- package/lib-prod/env/{env.angular-node-app.ts → env.angular-node-app.js} +1 -1
- package/lib-prod/env/{env.docs-webapp.ts → env.docs-webapp.js} +1 -1
- package/lib-prod/env/{env.electron-app.ts → env.electron-app.js} +1 -1
- package/lib-prod/env/{env.mobile-app.ts → env.mobile-app.js} +1 -1
- package/lib-prod/env/{env.npm-lib-and-cli-tool.ts → env.npm-lib-and-cli-tool.js} +1 -1
- package/lib-prod/env/{env.vscode-plugin.ts → env.vscode-plugin.js} +1 -1
- package/lib-prod/es6.backend.js +2 -0
- package/lib-prod/features/error-rate.backend.js +21 -0
- package/lib-prod/features/latency.backend.js +28 -0
- package/lib-prod/{index._auto-generated_.ts → index._auto-generated_.js} +1 -1
- package/lib-prod/index.js +19 -0
- package/lib-prod/logger.backend.js +21 -0
- package/lib-prod/migrations/index.js +2 -0
- package/lib-prod/migrations/{migrations_index._auto-generated_.ts → migrations_index._auto-generated_.js} +0 -2
- package/lib-prod/options.backend.js +85 -0
- package/lib-prod/package.json +1 -1
- package/lib-prod/request-handler.backend.js +137 -0
- package/lib-prod/server.backend.js +79 -0
- package/lib-prod/summary.backend.js +19 -0
- package/lib-prod/talkback-factory.backend.js +16 -0
- package/lib-prod/tape-matcher.backend.js +91 -0
- package/lib-prod/tape-renderer.backend.js +94 -0
- package/lib-prod/tape-store.backend.js +92 -0
- package/lib-prod/tape.backend.js +69 -0
- package/lib-prod/types.backend.js +1 -0
- package/lib-prod/utils/content-encoding.backend.js +36 -0
- package/lib-prod/utils/headers.backend.js +14 -0
- package/lib-prod/utils/media-type.backend.js +49 -0
- package/package.json +1 -1
- package/websql/package.json +1 -1
- package/websql-prod/package.json +1 -1
- package/lib-prod/es6.backend.ts +0 -3
- package/lib-prod/features/error-rate.backend.ts +0 -31
- package/lib-prod/features/latency.backend.ts +0 -38
- package/lib-prod/index.ts +0 -27
- package/lib-prod/lib-info.md +0 -8
- package/lib-prod/logger.backend.ts +0 -26
- package/lib-prod/migrations/index.ts +0 -2
- package/lib-prod/migrations/migrations-info.md +0 -6
- package/lib-prod/options.backend.ts +0 -140
- package/lib-prod/request-handler.backend.ts +0 -165
- package/lib-prod/server.backend.ts +0 -100
- package/lib-prod/summary.backend.ts +0 -26
- package/lib-prod/talkback-factory.backend.ts +0 -18
- package/lib-prod/tape-matcher.backend.ts +0 -113
- package/lib-prod/tape-renderer.backend.ts +0 -118
- package/lib-prod/tape-store.backend.ts +0 -111
- package/lib-prod/tape.backend.ts +0 -93
- package/lib-prod/types.backend.ts +0 -55
- package/lib-prod/utils/content-encoding.backend.ts +0 -53
- package/lib-prod/utils/headers.backend.ts +0 -14
- package/lib-prod/utils/media-type.backend.ts +0 -62
- /package/lib-prod/env/{index.ts → index.js} +0 -0
package/browser/package.json
CHANGED
|
@@ -25,6 +25,6 @@ exports.CURRENT_PACKAGE_TAON_VERSION = 'v21';
|
|
|
25
25
|
/**
|
|
26
26
|
* Autogenerated by current cli tool. Use *tnp release* to bump version.
|
|
27
27
|
*/
|
|
28
|
-
exports.CURRENT_PACKAGE_VERSION = '21.0.
|
|
28
|
+
exports.CURRENT_PACKAGE_VERSION = '21.0.18';
|
|
29
29
|
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
30
30
|
//# sourceMappingURL=build-info._auto-generated_.js.map
|
package/lib/package.json
CHANGED
|
@@ -22,6 +22,5 @@ export const CURRENT_PACKAGE_TAON_VERSION = 'v21';
|
|
|
22
22
|
/**
|
|
23
23
|
* Autogenerated by current cli tool. Use *tnp release* to bump version.
|
|
24
24
|
*/
|
|
25
|
-
export const CURRENT_PACKAGE_VERSION = '21.0.
|
|
25
|
+
export const CURRENT_PACKAGE_VERSION = '21.0.18';
|
|
26
26
|
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
27
|
-
|
|
@@ -63,4 +63,4 @@ export const ENV_ANGULAR_NODE_APP_IS_CI_PROCESS = undefined;
|
|
|
63
63
|
export const ENV_ANGULAR_NODE_APP_DOCKER_ADDITIONAL_CONTAINER = undefined;
|
|
64
64
|
export const ENV_ANGULAR_NODE_APP_DOCKER_SKIP_START_IN_ORDER = undefined;
|
|
65
65
|
export const ENV_ANGULAR_NODE_APP_DOCKER_SKIP_USING_MYSQL_DB = undefined;
|
|
66
|
-
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
66
|
+
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
@@ -63,4 +63,4 @@ export const ENV_DOCS_WEBAPP_IS_CI_PROCESS = undefined;
|
|
|
63
63
|
export const ENV_DOCS_WEBAPP_DOCKER_ADDITIONAL_CONTAINER = undefined;
|
|
64
64
|
export const ENV_DOCS_WEBAPP_DOCKER_SKIP_START_IN_ORDER = undefined;
|
|
65
65
|
export const ENV_DOCS_WEBAPP_DOCKER_SKIP_USING_MYSQL_DB = undefined;
|
|
66
|
-
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
66
|
+
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
@@ -63,4 +63,4 @@ export const ENV_ELECTRON_APP_IS_CI_PROCESS = undefined;
|
|
|
63
63
|
export const ENV_ELECTRON_APP_DOCKER_ADDITIONAL_CONTAINER = undefined;
|
|
64
64
|
export const ENV_ELECTRON_APP_DOCKER_SKIP_START_IN_ORDER = undefined;
|
|
65
65
|
export const ENV_ELECTRON_APP_DOCKER_SKIP_USING_MYSQL_DB = undefined;
|
|
66
|
-
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
66
|
+
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
@@ -63,4 +63,4 @@ export const ENV_MOBILE_APP_IS_CI_PROCESS = undefined;
|
|
|
63
63
|
export const ENV_MOBILE_APP_DOCKER_ADDITIONAL_CONTAINER = undefined;
|
|
64
64
|
export const ENV_MOBILE_APP_DOCKER_SKIP_START_IN_ORDER = undefined;
|
|
65
65
|
export const ENV_MOBILE_APP_DOCKER_SKIP_USING_MYSQL_DB = undefined;
|
|
66
|
-
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
66
|
+
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
@@ -63,4 +63,4 @@ export const ENV_NPM_LIB_AND_CLI_TOOL_IS_CI_PROCESS = undefined;
|
|
|
63
63
|
export const ENV_NPM_LIB_AND_CLI_TOOL_DOCKER_ADDITIONAL_CONTAINER = undefined;
|
|
64
64
|
export const ENV_NPM_LIB_AND_CLI_TOOL_DOCKER_SKIP_START_IN_ORDER = undefined;
|
|
65
65
|
export const ENV_NPM_LIB_AND_CLI_TOOL_DOCKER_SKIP_USING_MYSQL_DB = undefined;
|
|
66
|
-
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
66
|
+
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
@@ -63,4 +63,4 @@ export const ENV_VSCODE_PLUGIN_IS_CI_PROCESS = undefined;
|
|
|
63
63
|
export const ENV_VSCODE_PLUGIN_DOCKER_ADDITIONAL_CONTAINER = undefined;
|
|
64
64
|
export const ENV_VSCODE_PLUGIN_DOCKER_SKIP_START_IN_ORDER = undefined;
|
|
65
65
|
export const ENV_VSCODE_PLUGIN_DOCKER_SKIP_USING_MYSQL_DB = undefined;
|
|
66
|
-
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
66
|
+
// THIS FILE IS GENERATED - DO NOT MODIFY
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import OptionsFactory from '../options.backend';
|
|
2
|
+
export default class ErrorRate {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.options = options;
|
|
5
|
+
}
|
|
6
|
+
shouldSimulate(req, tape) {
|
|
7
|
+
const globalErrorRate = typeof (this.options.errorRate) === 'number' ? this.options.errorRate : this.options.errorRate(req);
|
|
8
|
+
const errorRate = tape && tape.meta.errorRate !== undefined ? tape.meta.errorRate : globalErrorRate;
|
|
9
|
+
OptionsFactory.validateErrorRate(errorRate);
|
|
10
|
+
const random = Math.random() * 100;
|
|
11
|
+
return random < errorRate;
|
|
12
|
+
}
|
|
13
|
+
simulate(req) {
|
|
14
|
+
this.options.logger.log(`Simulating error for ${req.url}`);
|
|
15
|
+
return {
|
|
16
|
+
status: 503,
|
|
17
|
+
headers: { 'content-type': ['text/plain'] },
|
|
18
|
+
body: Buffer.from("talkback - failure injection")
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import OptionsFactory from '../options.backend';
|
|
2
|
+
export default class Latency {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.options = options;
|
|
5
|
+
}
|
|
6
|
+
async simulate(req, tape) {
|
|
7
|
+
const resolved = Promise.resolve();
|
|
8
|
+
const latencyGenerator = tape && tape.meta.latency !== undefined ? tape.meta.latency : this.options.latency;
|
|
9
|
+
if (!latencyGenerator) {
|
|
10
|
+
return resolved;
|
|
11
|
+
}
|
|
12
|
+
OptionsFactory.validateLatency(latencyGenerator);
|
|
13
|
+
let latency = 0;
|
|
14
|
+
const type = typeof latencyGenerator;
|
|
15
|
+
if (type === "number") {
|
|
16
|
+
latency = latencyGenerator;
|
|
17
|
+
}
|
|
18
|
+
else if (Array.isArray(latencyGenerator)) {
|
|
19
|
+
const high = latencyGenerator[1];
|
|
20
|
+
const low = latencyGenerator[0];
|
|
21
|
+
latency = Math.random() * (high - low) + low;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
latency = latencyGenerator(req);
|
|
25
|
+
}
|
|
26
|
+
return new Promise(r => setTimeout(r, latency));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
let a = 2;
|
|
2
|
+
a++;
|
|
3
|
+
//#region @backend
|
|
4
|
+
export * from './options.backend';
|
|
5
|
+
export * from './tape.backend';
|
|
6
|
+
import TalkbackFactory from './talkback-factory.backend';
|
|
7
|
+
import { DefaultOptions, FallbackMode, RecordMode } from './options.backend';
|
|
8
|
+
const talkbackFn = (options) => {
|
|
9
|
+
return TalkbackFactory.server(options);
|
|
10
|
+
};
|
|
11
|
+
talkbackFn.Options = {
|
|
12
|
+
Default: DefaultOptions,
|
|
13
|
+
FallbackMode,
|
|
14
|
+
RecordMode
|
|
15
|
+
};
|
|
16
|
+
talkbackFn.requestHandler = (options) => TalkbackFactory.requestHandler(options);
|
|
17
|
+
export const talkback = talkbackFn;
|
|
18
|
+
export default talkbackFn;
|
|
19
|
+
//#endregion
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default class Logger {
|
|
2
|
+
constructor(options) {
|
|
3
|
+
this.options = options;
|
|
4
|
+
if (this.options.debug) {
|
|
5
|
+
console.debug("DEBUG mode active");
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
log(message) {
|
|
9
|
+
if (!this.options.silent || this.options.debug) {
|
|
10
|
+
console.log(message);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
debug(message) {
|
|
14
|
+
if (this.options.debug) {
|
|
15
|
+
console.debug(message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
error(message) {
|
|
19
|
+
console.error(message);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Logger from './logger.backend';
|
|
2
|
+
export const RecordMode = {
|
|
3
|
+
NEW: "NEW", // If no tape matches the request, proxy it and save the response to a tape
|
|
4
|
+
OVERWRITE: "OVERWRITE", // Always proxy the request and save the response to a tape, overwriting any existing one
|
|
5
|
+
DISABLED: "DISABLED", // If a matching tape exists, return it. Otherwise, don't proxy the request and use `fallbackMode` for the response
|
|
6
|
+
ALL: []
|
|
7
|
+
};
|
|
8
|
+
RecordMode.ALL = [RecordMode.NEW, RecordMode.OVERWRITE, RecordMode.DISABLED];
|
|
9
|
+
export const FallbackMode = {
|
|
10
|
+
NOT_FOUND: "NOT_FOUND",
|
|
11
|
+
PROXY: "PROXY",
|
|
12
|
+
ALL: []
|
|
13
|
+
};
|
|
14
|
+
FallbackMode.ALL = [FallbackMode.NOT_FOUND, FallbackMode.PROXY];
|
|
15
|
+
export const DefaultOptions = {
|
|
16
|
+
host: "",
|
|
17
|
+
port: 8080,
|
|
18
|
+
path: "./tapes/",
|
|
19
|
+
record: RecordMode.NEW,
|
|
20
|
+
fallbackMode: FallbackMode.NOT_FOUND,
|
|
21
|
+
name: "unnamed",
|
|
22
|
+
tapeNameGenerator: undefined,
|
|
23
|
+
https: {
|
|
24
|
+
enabled: false,
|
|
25
|
+
keyPath: undefined,
|
|
26
|
+
certPath: undefined
|
|
27
|
+
},
|
|
28
|
+
ignoreHeaders: ["content-length", "host"],
|
|
29
|
+
ignoreQueryParams: [],
|
|
30
|
+
ignoreBody: false,
|
|
31
|
+
bodyMatcher: undefined,
|
|
32
|
+
urlMatcher: undefined,
|
|
33
|
+
requestDecorator: undefined,
|
|
34
|
+
responseDecorator: undefined,
|
|
35
|
+
tapeDecorator: undefined,
|
|
36
|
+
latency: 0,
|
|
37
|
+
errorRate: 0,
|
|
38
|
+
silent: false,
|
|
39
|
+
summary: true,
|
|
40
|
+
debug: false,
|
|
41
|
+
logger: new Logger({})
|
|
42
|
+
};
|
|
43
|
+
export default class OptionsFactory {
|
|
44
|
+
static prepare(usrOpts = {}) {
|
|
45
|
+
const opts = {
|
|
46
|
+
...DefaultOptions,
|
|
47
|
+
name: usrOpts.host,
|
|
48
|
+
...usrOpts,
|
|
49
|
+
ignoreHeaders: [
|
|
50
|
+
...DefaultOptions.ignoreHeaders,
|
|
51
|
+
...(usrOpts.ignoreHeaders || [])
|
|
52
|
+
]
|
|
53
|
+
};
|
|
54
|
+
this.logger = new Logger(opts);
|
|
55
|
+
opts.logger = this.logger;
|
|
56
|
+
this.validateOptions(opts);
|
|
57
|
+
return opts;
|
|
58
|
+
}
|
|
59
|
+
static validateOptions(opts) {
|
|
60
|
+
this.validateRecord(opts.record);
|
|
61
|
+
this.validateFallbackMode(opts.fallbackMode);
|
|
62
|
+
this.validateLatency(opts.latency);
|
|
63
|
+
this.validateErrorRate(opts.errorRate);
|
|
64
|
+
}
|
|
65
|
+
static validateRecord(record) {
|
|
66
|
+
if (typeof (record) === "string" && !RecordMode.ALL.includes(record)) {
|
|
67
|
+
throw `INVALID OPTION: record has an invalid value of '${record}'`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
static validateFallbackMode(fallbackMode) {
|
|
71
|
+
if (typeof (fallbackMode) === "string" && !FallbackMode.ALL.includes(fallbackMode)) {
|
|
72
|
+
throw `INVALID OPTION: fallbackMode has an invalid value of '${fallbackMode}'`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
static validateLatency(latency) {
|
|
76
|
+
if (Array.isArray(latency) && latency.length !== 2) {
|
|
77
|
+
throw `Invalid LATENCY option. If using a range, the array should only have 2 values [min, max]. Current=[${latency}]`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
static validateErrorRate(errorRate) {
|
|
81
|
+
if (typeof (errorRate) !== "function" && (errorRate < 0 || errorRate > 100)) {
|
|
82
|
+
throw `Invalid ERRORRATE option. Value should be between 0 and 100. Current=[${errorRate}]`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
package/lib-prod/package.json
CHANGED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import Tape from './tape.backend';
|
|
4
|
+
import OptionsFactory, { RecordMode, FallbackMode } from './options.backend';
|
|
5
|
+
import ErrorRate from './features/error-rate.backend';
|
|
6
|
+
import Latency from './features/latency.backend';
|
|
7
|
+
export default class RequestHandler {
|
|
8
|
+
constructor(tapeStore, options) {
|
|
9
|
+
this.tapeStore = tapeStore;
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.errorRate = new ErrorRate(this.options);
|
|
12
|
+
this.latency = new Latency(this.options);
|
|
13
|
+
}
|
|
14
|
+
async handle(req) {
|
|
15
|
+
const matchingContext = {
|
|
16
|
+
id: uuidv4()
|
|
17
|
+
};
|
|
18
|
+
const recordMode = typeof (this.options.record) === "string" ? this.options.record : this.options.record(req);
|
|
19
|
+
OptionsFactory.validateRecord(recordMode);
|
|
20
|
+
if (this.options.requestDecorator) {
|
|
21
|
+
req = this.options.requestDecorator(req, matchingContext);
|
|
22
|
+
if (!req) {
|
|
23
|
+
throw new Error("requestDecorator didn't return a req object");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
let newTape = new Tape(req, this.options);
|
|
27
|
+
let matchingTape = this.tapeStore.find(newTape);
|
|
28
|
+
let resObj, responseTape;
|
|
29
|
+
if (recordMode !== RecordMode.OVERWRITE && matchingTape) {
|
|
30
|
+
responseTape = matchingTape;
|
|
31
|
+
if (this.errorRate.shouldSimulate(req, matchingTape)) {
|
|
32
|
+
return this.errorRate.simulate(req);
|
|
33
|
+
}
|
|
34
|
+
await this.latency.simulate(req, matchingTape);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
if (matchingTape) {
|
|
38
|
+
responseTape = matchingTape;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
responseTape = newTape;
|
|
42
|
+
}
|
|
43
|
+
if (recordMode === RecordMode.NEW || recordMode === RecordMode.OVERWRITE) {
|
|
44
|
+
resObj = await this.makeRealRequest(req);
|
|
45
|
+
responseTape.res = { ...resObj };
|
|
46
|
+
if (this.options.tapeDecorator) {
|
|
47
|
+
responseTape = this.options.tapeDecorator(responseTape, matchingContext);
|
|
48
|
+
if (!responseTape) {
|
|
49
|
+
throw new Error("tapeDecorator didn't return a tape object");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await this.tapeStore.save(responseTape);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
resObj = await this.onNoRecord(req);
|
|
56
|
+
responseTape.res = { ...resObj };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
resObj = responseTape.res;
|
|
60
|
+
if (this.options.responseDecorator) {
|
|
61
|
+
const clonedTape = await responseTape.clone();
|
|
62
|
+
const resTape = this.options.responseDecorator(clonedTape, req, matchingContext);
|
|
63
|
+
if (!resTape) {
|
|
64
|
+
throw new Error("responseDecorator didn't return a tape object");
|
|
65
|
+
}
|
|
66
|
+
if (resTape.res.headers["content-length"]) {
|
|
67
|
+
resTape.res.headers["content-length"] = resTape.res.body.length;
|
|
68
|
+
}
|
|
69
|
+
resObj = resTape.res;
|
|
70
|
+
}
|
|
71
|
+
return resObj;
|
|
72
|
+
}
|
|
73
|
+
async onNoRecord(req) {
|
|
74
|
+
const fallbackMode = typeof (this.options.fallbackMode) === "string" ? this.options.fallbackMode : this.options.fallbackMode(req);
|
|
75
|
+
OptionsFactory.validateFallbackMode(fallbackMode);
|
|
76
|
+
this.options.logger.log(`Tape for ${req.url} not found and recording is disabled (fallbackMode: ${fallbackMode})`);
|
|
77
|
+
this.options.logger.log({
|
|
78
|
+
url: req.url,
|
|
79
|
+
headers: req.headers
|
|
80
|
+
});
|
|
81
|
+
if (fallbackMode === FallbackMode.PROXY) {
|
|
82
|
+
if (this.errorRate.shouldSimulate(req, undefined)) {
|
|
83
|
+
return this.errorRate.simulate(req);
|
|
84
|
+
}
|
|
85
|
+
await this.latency.simulate(req, undefined);
|
|
86
|
+
return await this.makeRealRequest(req);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
status: 404,
|
|
90
|
+
headers: { "content-type": ["text/plain"] },
|
|
91
|
+
body: Buffer.from("talkback - tape not found")
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async makeRealRequest(req) {
|
|
95
|
+
// let fetchBody: Buffer | null
|
|
96
|
+
let { method, url, body } = req;
|
|
97
|
+
// fetchBody = body
|
|
98
|
+
const headers = { ...req.headers };
|
|
99
|
+
delete headers.host;
|
|
100
|
+
const host = this.options.host;
|
|
101
|
+
// this.options.logger.log(`Making real request to ${host}${url}`)
|
|
102
|
+
// if (method === "GET" || method === "HEAD") {
|
|
103
|
+
// fetchBody = null
|
|
104
|
+
// }
|
|
105
|
+
let urlToGetData = `${host}${url}`;
|
|
106
|
+
// console.log(`host: "${urlToGetData}"` )
|
|
107
|
+
// console.log(`url: "${url}"` )
|
|
108
|
+
var fRes = {
|
|
109
|
+
status: 400,
|
|
110
|
+
headers: {},
|
|
111
|
+
body: new Buffer('')
|
|
112
|
+
};
|
|
113
|
+
try {
|
|
114
|
+
// console.log(body.toString())
|
|
115
|
+
const r = await axios({
|
|
116
|
+
url: urlToGetData,
|
|
117
|
+
method,
|
|
118
|
+
headers,
|
|
119
|
+
data: body,
|
|
120
|
+
responseType: 'arraybuffer',
|
|
121
|
+
});
|
|
122
|
+
fRes = {
|
|
123
|
+
status: r.status,
|
|
124
|
+
headers: r.headers,
|
|
125
|
+
body: r.data
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
fRes = {
|
|
130
|
+
status: err?.response?.status,
|
|
131
|
+
headers: err?.response?.headers,
|
|
132
|
+
body: err?.response?.data
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return fRes;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import RequestHandler from './request-handler.backend';
|
|
2
|
+
import Summary from './summary.backend';
|
|
3
|
+
import TapeStore from './tape-store.backend';
|
|
4
|
+
import * as http from 'http';
|
|
5
|
+
import { https, fse } from 'tnp-core/lib-prod';
|
|
6
|
+
export default class TalkbackServer {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.closed = false;
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.tapeStore = new TapeStore(this.options);
|
|
11
|
+
this.requestHandler = new RequestHandler(this.tapeStore, this.options);
|
|
12
|
+
this.closeSignalHandler = this.close.bind(this);
|
|
13
|
+
}
|
|
14
|
+
handleRequest(rawReq, res) {
|
|
15
|
+
// console.log(`rawReq: ${rawReq.url}`)
|
|
16
|
+
let reqBody = [];
|
|
17
|
+
rawReq.on("data", (chunk) => {
|
|
18
|
+
reqBody.push(chunk);
|
|
19
|
+
}).on("end", async () => {
|
|
20
|
+
try {
|
|
21
|
+
const req = {
|
|
22
|
+
headers: rawReq.headers,
|
|
23
|
+
url: rawReq.url,
|
|
24
|
+
method: rawReq.method,
|
|
25
|
+
body: Buffer.concat(reqBody)
|
|
26
|
+
};
|
|
27
|
+
const fRes = await this.requestHandler.handle(req);
|
|
28
|
+
res.writeHead(fRes.status, fRes.headers);
|
|
29
|
+
res.end(fRes.body);
|
|
30
|
+
}
|
|
31
|
+
catch (ex) {
|
|
32
|
+
console.error("Error handling request", ex);
|
|
33
|
+
res.statusCode = 500;
|
|
34
|
+
res.end();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async start(callback) {
|
|
39
|
+
await this.tapeStore.load();
|
|
40
|
+
const handleRequest = this.handleRequest.bind(this);
|
|
41
|
+
const serverFactory = this.options.https.enabled ? () => {
|
|
42
|
+
const httpsOpts = {
|
|
43
|
+
key: fse.readFileSync(this.options.https.keyPath),
|
|
44
|
+
cert: fse.readFileSync(this.options.https.certPath)
|
|
45
|
+
};
|
|
46
|
+
return https.createServer(httpsOpts, handleRequest);
|
|
47
|
+
} : () => http.createServer(handleRequest);
|
|
48
|
+
this.server = serverFactory();
|
|
49
|
+
console.log(`Starting talkback on ${this.options.port}`);
|
|
50
|
+
this.server.listen(this.options.port, callback);
|
|
51
|
+
process.on("exit", this.closeSignalHandler);
|
|
52
|
+
process.on("SIGINT", this.closeSignalHandler);
|
|
53
|
+
process.on("SIGTERM", this.closeSignalHandler);
|
|
54
|
+
return this.server;
|
|
55
|
+
}
|
|
56
|
+
hasTapeBeenUsed(tapeName) {
|
|
57
|
+
return this.tapeStore.hasTapeBeenUsed(tapeName);
|
|
58
|
+
}
|
|
59
|
+
resetTapeUsage() {
|
|
60
|
+
this.tapeStore.resetTapeUsage();
|
|
61
|
+
}
|
|
62
|
+
close(callback) {
|
|
63
|
+
if (this.closed) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.closed = true;
|
|
67
|
+
this.server.close(callback);
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
process.removeListener("exit", this.closeSignalHandler);
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
process.removeListener("SIGINT", this.closeSignalHandler);
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
process.removeListener("SIGTERM", this.closeSignalHandler);
|
|
74
|
+
if (this.options.summary) {
|
|
75
|
+
const summary = new Summary(this.tapeStore.tapes, this.options);
|
|
76
|
+
summary.print();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default class Summary {
|
|
2
|
+
constructor(tapes, opts) {
|
|
3
|
+
this.tapes = tapes;
|
|
4
|
+
this.opts = opts;
|
|
5
|
+
}
|
|
6
|
+
print() {
|
|
7
|
+
console.log(`===== SUMMARY (${this.opts.name}) =====`);
|
|
8
|
+
const newTapes = this.tapes.filter(t => t.new);
|
|
9
|
+
if (newTapes.length > 0) {
|
|
10
|
+
console.log("New tapes:");
|
|
11
|
+
newTapes.forEach(t => console.log(`- ${t.path}`));
|
|
12
|
+
}
|
|
13
|
+
const unusedTapes = this.tapes.filter(t => !t.used);
|
|
14
|
+
if (unusedTapes.length > 0) {
|
|
15
|
+
console.log("Unused tapes:");
|
|
16
|
+
unusedTapes.forEach(t => console.log(`- ${t.path}`));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Options from './options.backend';
|
|
2
|
+
import TapeStore from './tape-store.backend';
|
|
3
|
+
import TalkbackServer from './server.backend';
|
|
4
|
+
import RequestHandler from './request-handler.backend';
|
|
5
|
+
export default class TalkbackFactory {
|
|
6
|
+
static server(options) {
|
|
7
|
+
const fullOptions = Options.prepare(options);
|
|
8
|
+
return new TalkbackServer(fullOptions);
|
|
9
|
+
}
|
|
10
|
+
static async requestHandler(options) {
|
|
11
|
+
const fullOptions = Options.prepare(options);
|
|
12
|
+
const tapeStore = new TapeStore(fullOptions);
|
|
13
|
+
await tapeStore.load();
|
|
14
|
+
return new RequestHandler(tapeStore, fullOptions);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import ContentEncoding from './utils/content-encoding.backend';
|
|
2
|
+
import MediaType from './utils/media-type.backend';
|
|
3
|
+
import { ___NS__isEqual } from 'tnp-core/lib-prod';
|
|
4
|
+
export default class TapeMatcher {
|
|
5
|
+
constructor(tape, options) {
|
|
6
|
+
this.tape = tape;
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
sameAs(otherTape) {
|
|
10
|
+
const otherReq = otherTape.req;
|
|
11
|
+
const req = this.tape.req;
|
|
12
|
+
if (!this.isSameUrl(req, otherReq)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (!this.isSameMethod(req, otherReq)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (!this.isSameHeaders(req, otherReq)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return this.options.ignoreBody || this.isSameBody(req, otherReq);
|
|
22
|
+
}
|
|
23
|
+
isSameBody(req, otherReq) {
|
|
24
|
+
const mediaType = new MediaType(req);
|
|
25
|
+
const contentEncoding = new ContentEncoding(req);
|
|
26
|
+
let sameBody;
|
|
27
|
+
if (contentEncoding.isUncompressed() && mediaType.isJSON() && req.body.length > 0 && otherReq.body.length > 0) {
|
|
28
|
+
const parsedReqBody = JSON.parse(req.body.toString());
|
|
29
|
+
const parsedOtherReqBody = JSON.parse(otherReq.body.toString());
|
|
30
|
+
sameBody = ___NS__isEqual(parsedReqBody, parsedOtherReqBody);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
sameBody = req.body.equals(otherReq.body);
|
|
34
|
+
}
|
|
35
|
+
if (!sameBody) {
|
|
36
|
+
if (!this.options.bodyMatcher) {
|
|
37
|
+
this.options.logger.debug(`Not same BODY ${req.body} vs ${otherReq.body}`);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const bodyMatches = this.options.bodyMatcher(this.tape, otherReq);
|
|
41
|
+
if (!bodyMatches) {
|
|
42
|
+
this.options.logger.debug(`Not same bodyMatcher ${req.body} vs ${otherReq.body}`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
isSameHeaders(req, otherReq) {
|
|
49
|
+
const currentHeadersLength = Object.keys(req.headers).length;
|
|
50
|
+
const otherHeadersLength = Object.keys(otherReq.headers).length;
|
|
51
|
+
const sameNumberOfHeaders = currentHeadersLength === otherHeadersLength;
|
|
52
|
+
if (!sameNumberOfHeaders) {
|
|
53
|
+
this.options.logger.debug(`Not same #HEADERS ${JSON.stringify(req.headers)} vs ${JSON.stringify(otherReq.headers)}`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
let headersSame = true;
|
|
57
|
+
Object.keys(req.headers).forEach(k => {
|
|
58
|
+
const entryHeader = req.headers[k];
|
|
59
|
+
const header = otherReq.headers[k];
|
|
60
|
+
headersSame = headersSame && entryHeader === header;
|
|
61
|
+
});
|
|
62
|
+
if (!headersSame) {
|
|
63
|
+
this.options.logger.debug(`Not same HEADERS values ${JSON.stringify(req.headers)} vs ${JSON.stringify(otherReq.headers)}`);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
isSameMethod(req, otherReq) {
|
|
69
|
+
const sameMethod = req.method === otherReq.method;
|
|
70
|
+
if (!sameMethod) {
|
|
71
|
+
this.options.logger.debug(`Not same METHOD ${req.method} vs ${otherReq.method}`);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
isSameUrl(req, otherReq) {
|
|
77
|
+
const sameURL = req.url === otherReq.url;
|
|
78
|
+
if (!sameURL) {
|
|
79
|
+
if (!this.options.urlMatcher) {
|
|
80
|
+
this.options.logger.debug(`Not same URL ${req.url} vs ${otherReq.url}`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const urlMatches = this.options.urlMatcher(this.tape, otherReq);
|
|
84
|
+
if (!urlMatches) {
|
|
85
|
+
this.options.logger.debug(`Not same urlMatcher ${req.url} vs ${otherReq.url}`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|