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.
Files changed (59) hide show
  1. package/browser/package.json +1 -1
  2. package/browser-prod/package.json +1 -1
  3. package/lib/build-info._auto-generated_.d.ts +1 -1
  4. package/lib/build-info._auto-generated_.js +1 -1
  5. package/lib/package.json +1 -1
  6. package/lib-prod/{build-info._auto-generated_.ts → build-info._auto-generated_.js} +1 -2
  7. package/lib-prod/env/{env.angular-node-app.ts → env.angular-node-app.js} +1 -1
  8. package/lib-prod/env/{env.docs-webapp.ts → env.docs-webapp.js} +1 -1
  9. package/lib-prod/env/{env.electron-app.ts → env.electron-app.js} +1 -1
  10. package/lib-prod/env/{env.mobile-app.ts → env.mobile-app.js} +1 -1
  11. package/lib-prod/env/{env.npm-lib-and-cli-tool.ts → env.npm-lib-and-cli-tool.js} +1 -1
  12. package/lib-prod/env/{env.vscode-plugin.ts → env.vscode-plugin.js} +1 -1
  13. package/lib-prod/es6.backend.js +2 -0
  14. package/lib-prod/features/error-rate.backend.js +21 -0
  15. package/lib-prod/features/latency.backend.js +28 -0
  16. package/lib-prod/{index._auto-generated_.ts → index._auto-generated_.js} +1 -1
  17. package/lib-prod/index.js +19 -0
  18. package/lib-prod/logger.backend.js +21 -0
  19. package/lib-prod/migrations/index.js +2 -0
  20. package/lib-prod/migrations/{migrations_index._auto-generated_.ts → migrations_index._auto-generated_.js} +0 -2
  21. package/lib-prod/options.backend.js +85 -0
  22. package/lib-prod/package.json +1 -1
  23. package/lib-prod/request-handler.backend.js +137 -0
  24. package/lib-prod/server.backend.js +79 -0
  25. package/lib-prod/summary.backend.js +19 -0
  26. package/lib-prod/talkback-factory.backend.js +16 -0
  27. package/lib-prod/tape-matcher.backend.js +91 -0
  28. package/lib-prod/tape-renderer.backend.js +94 -0
  29. package/lib-prod/tape-store.backend.js +92 -0
  30. package/lib-prod/tape.backend.js +69 -0
  31. package/lib-prod/types.backend.js +1 -0
  32. package/lib-prod/utils/content-encoding.backend.js +36 -0
  33. package/lib-prod/utils/headers.backend.js +14 -0
  34. package/lib-prod/utils/media-type.backend.js +49 -0
  35. package/package.json +1 -1
  36. package/websql/package.json +1 -1
  37. package/websql-prod/package.json +1 -1
  38. package/lib-prod/es6.backend.ts +0 -3
  39. package/lib-prod/features/error-rate.backend.ts +0 -31
  40. package/lib-prod/features/latency.backend.ts +0 -38
  41. package/lib-prod/index.ts +0 -27
  42. package/lib-prod/lib-info.md +0 -8
  43. package/lib-prod/logger.backend.ts +0 -26
  44. package/lib-prod/migrations/index.ts +0 -2
  45. package/lib-prod/migrations/migrations-info.md +0 -6
  46. package/lib-prod/options.backend.ts +0 -140
  47. package/lib-prod/request-handler.backend.ts +0 -165
  48. package/lib-prod/server.backend.ts +0 -100
  49. package/lib-prod/summary.backend.ts +0 -26
  50. package/lib-prod/talkback-factory.backend.ts +0 -18
  51. package/lib-prod/tape-matcher.backend.ts +0 -113
  52. package/lib-prod/tape-renderer.backend.ts +0 -118
  53. package/lib-prod/tape-store.backend.ts +0 -111
  54. package/lib-prod/tape.backend.ts +0 -93
  55. package/lib-prod/types.backend.ts +0 -55
  56. package/lib-prod/utils/content-encoding.backend.ts +0 -53
  57. package/lib-prod/utils/headers.backend.ts +0 -14
  58. package/lib-prod/utils/media-type.backend.ts +0 -62
  59. /package/lib-prod/env/{index.ts → index.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-talkback/browser",
3
- "version": "21.0.16",
3
+ "version": "21.0.18",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^21.0.0",
6
6
  "@angular/core": "^21.0.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-talkback/browser-prod",
3
- "version": "21.0.16",
3
+ "version": "21.0.18",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^21.0.0",
6
6
  "@angular/core": "^21.0.0"
@@ -21,4 +21,4 @@ export declare const CURRENT_PACKAGE_TAON_VERSION = "v21";
21
21
  /**
22
22
  * Autogenerated by current cli tool. Use *tnp release* to bump version.
23
23
  */
24
- export declare const CURRENT_PACKAGE_VERSION = "21.0.16";
24
+ export declare const CURRENT_PACKAGE_VERSION = "21.0.18";
@@ -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.16';
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
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "ng-talkback/lib",
3
- "version": "21.0.16"
3
+ "version": "21.0.18"
4
4
  }
@@ -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.16';
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,2 @@
1
+ import talkbackFn from './index';
2
+ export default talkbackFn;
@@ -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
+ }
@@ -2,4 +2,4 @@
2
2
  // This file is auto-generated during init process. Do not modify.
3
3
  // This is only placeholder.
4
4
  // Use property "shouldGenerateAutogenIndexFile: true"
5
- // in taon.jsonc to enable ts exports auto generation.
5
+ // in taon.jsonc to enable ts exports auto generation.
@@ -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,2 @@
1
+ //@ts-nocheck
2
+ export * from './migrations_index._auto-generated_';
@@ -1,5 +1,3 @@
1
1
  // THIS FILE IS GENERATED - DO NOT MODIFY
2
-
3
2
  // THIS FILE IS GENERATED - DO NOT MODIFY
4
-
5
3
  // THIS FILE IS GENERATED - DO NOT MODIFY
@@ -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
+ }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "ng-talkback/lib-prod",
3
- "version": "21.0.16"
3
+ "version": "21.0.18"
4
4
  }
@@ -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
+ }