ng-talkback 21.0.14 → 21.0.16

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 (127) hide show
  1. package/browser/package.json +1 -1
  2. package/browser-prod/package.json +1 -1
  3. package/browser-prod.re-export.json +1 -0
  4. package/lib/build-info._auto-generated_.d.ts +1 -1
  5. package/lib/build-info._auto-generated_.js +1 -1
  6. package/lib/package.json +1 -1
  7. package/lib-prod/build-info._auto-generated_.ts +27 -0
  8. package/lib-prod/env/env.angular-node-app.ts +66 -0
  9. package/lib-prod/env/env.docs-webapp.ts +66 -0
  10. package/lib-prod/env/env.electron-app.ts +66 -0
  11. package/lib-prod/env/env.mobile-app.ts +66 -0
  12. package/lib-prod/env/env.npm-lib-and-cli-tool.ts +66 -0
  13. package/lib-prod/env/env.vscode-plugin.ts +66 -0
  14. package/lib-prod/es6.backend.ts +3 -0
  15. package/lib-prod/features/error-rate.backend.ts +31 -0
  16. package/lib-prod/features/latency.backend.ts +38 -0
  17. package/lib-prod/{index._auto-generated_.js → index._auto-generated_.ts} +0 -1
  18. package/lib-prod/index.ts +27 -0
  19. package/lib-prod/lib-info.md +8 -0
  20. package/lib-prod/logger.backend.ts +26 -0
  21. package/lib-prod/migrations/index.ts +2 -0
  22. package/lib-prod/migrations/migrations-info.md +6 -0
  23. package/lib-prod/migrations/{migrations_index._auto-generated_.js → migrations_index._auto-generated_.ts} +2 -1
  24. package/lib-prod/options.backend.ts +140 -0
  25. package/lib-prod/package.json +1 -1
  26. package/lib-prod/request-handler.backend.ts +165 -0
  27. package/lib-prod/server.backend.ts +100 -0
  28. package/lib-prod/summary.backend.ts +26 -0
  29. package/lib-prod/talkback-factory.backend.ts +18 -0
  30. package/lib-prod/tape-matcher.backend.ts +113 -0
  31. package/lib-prod/tape-renderer.backend.ts +118 -0
  32. package/lib-prod/tape-store.backend.ts +111 -0
  33. package/lib-prod/tape.backend.ts +93 -0
  34. package/lib-prod/types.backend.ts +55 -0
  35. package/lib-prod/utils/content-encoding.backend.ts +53 -0
  36. package/lib-prod/utils/headers.backend.ts +14 -0
  37. package/lib-prod/utils/media-type.backend.ts +62 -0
  38. package/lib-prod.re-export.json +1 -0
  39. package/package.json +3 -2
  40. package/websql/package.json +1 -1
  41. package/websql-prod/package.json +1 -1
  42. package/websql-prod.re-export.json +1 -0
  43. package/lib-prod/build-info._auto-generated_.d.ts +0 -24
  44. package/lib-prod/build-info._auto-generated_.js +0 -30
  45. package/lib-prod/build-info._auto-generated_.js.map +0 -1
  46. package/lib-prod/env/env.angular-node-app.d.ts +0 -64
  47. package/lib-prod/env/env.angular-node-app.js +0 -71
  48. package/lib-prod/env/env.angular-node-app.js.map +0 -1
  49. package/lib-prod/env/env.docs-webapp.d.ts +0 -64
  50. package/lib-prod/env/env.docs-webapp.js +0 -71
  51. package/lib-prod/env/env.docs-webapp.js.map +0 -1
  52. package/lib-prod/env/env.electron-app.d.ts +0 -64
  53. package/lib-prod/env/env.electron-app.js +0 -71
  54. package/lib-prod/env/env.electron-app.js.map +0 -1
  55. package/lib-prod/env/env.mobile-app.d.ts +0 -64
  56. package/lib-prod/env/env.mobile-app.js +0 -71
  57. package/lib-prod/env/env.mobile-app.js.map +0 -1
  58. package/lib-prod/env/env.npm-lib-and-cli-tool.d.ts +0 -64
  59. package/lib-prod/env/env.npm-lib-and-cli-tool.js +0 -71
  60. package/lib-prod/env/env.npm-lib-and-cli-tool.js.map +0 -1
  61. package/lib-prod/env/env.vscode-plugin.d.ts +0 -64
  62. package/lib-prod/env/env.vscode-plugin.js +0 -71
  63. package/lib-prod/env/env.vscode-plugin.js.map +0 -1
  64. package/lib-prod/env/index.js +0 -23
  65. package/lib-prod/env/index.js.map +0 -1
  66. package/lib-prod/es6.backend.d.ts +0 -2
  67. package/lib-prod/es6.backend.js +0 -5
  68. package/lib-prod/es6.backend.js.map +0 -1
  69. package/lib-prod/features/error-rate.backend.d.ts +0 -9
  70. package/lib-prod/features/error-rate.backend.js +0 -26
  71. package/lib-prod/features/error-rate.backend.js.map +0 -1
  72. package/lib-prod/features/latency.backend.d.ts +0 -8
  73. package/lib-prod/features/latency.backend.js +0 -33
  74. package/lib-prod/features/latency.backend.js.map +0 -1
  75. package/lib-prod/index._auto-generated_.d.ts +0 -0
  76. package/lib-prod/index._auto-generated_.js.map +0 -1
  77. package/lib-prod/index.d.ts +0 -41
  78. package/lib-prod/index.js +0 -37
  79. package/lib-prod/index.js.map +0 -1
  80. package/lib-prod/logger.backend.d.ts +0 -7
  81. package/lib-prod/logger.backend.js +0 -26
  82. package/lib-prod/logger.backend.js.map +0 -1
  83. package/lib-prod/migrations/index.d.ts +0 -1
  84. package/lib-prod/migrations/index.js +0 -19
  85. package/lib-prod/migrations/index.js.map +0 -1
  86. package/lib-prod/migrations/migrations_index._auto-generated_.d.ts +0 -0
  87. package/lib-prod/migrations/migrations_index._auto-generated_.js.map +0 -1
  88. package/lib-prod/options.backend.d.ts +0 -52
  89. package/lib-prod/options.backend.js +0 -91
  90. package/lib-prod/options.backend.js.map +0 -1
  91. package/lib-prod/request-handler.backend.d.ts +0 -13
  92. package/lib-prod/request-handler.backend.js +0 -145
  93. package/lib-prod/request-handler.backend.js.map +0 -1
  94. package/lib-prod/server.backend.d.ts +0 -17
  95. package/lib-prod/server.backend.js +0 -88
  96. package/lib-prod/server.backend.js.map +0 -1
  97. package/lib-prod/summary.backend.d.ts +0 -8
  98. package/lib-prod/summary.backend.js +0 -25
  99. package/lib-prod/summary.backend.js.map +0 -1
  100. package/lib-prod/talkback-factory.backend.d.ts +0 -7
  101. package/lib-prod/talkback-factory.backend.js +0 -20
  102. package/lib-prod/talkback-factory.backend.js.map +0 -1
  103. package/lib-prod/tape-matcher.backend.d.ts +0 -12
  104. package/lib-prod/tape-matcher.backend.js +0 -97
  105. package/lib-prod/tape-matcher.backend.js.map +0 -1
  106. package/lib-prod/tape-renderer.backend.d.ts +0 -24
  107. package/lib-prod/tape-renderer.backend.js +0 -99
  108. package/lib-prod/tape-renderer.backend.js.map +0 -1
  109. package/lib-prod/tape-store.backend.d.ts +0 -16
  110. package/lib-prod/tape-store.backend.js +0 -99
  111. package/lib-prod/tape-store.backend.js.map +0 -1
  112. package/lib-prod/tape.backend.d.ts +0 -19
  113. package/lib-prod/tape.backend.js +0 -80
  114. package/lib-prod/tape.backend.js.map +0 -1
  115. package/lib-prod/types.backend.d.ts +0 -43
  116. package/lib-prod/types.backend.js +0 -3
  117. package/lib-prod/types.backend.js.map +0 -1
  118. package/lib-prod/utils/content-encoding.backend.d.ts +0 -10
  119. package/lib-prod/utils/content-encoding.backend.js +0 -41
  120. package/lib-prod/utils/content-encoding.backend.js.map +0 -1
  121. package/lib-prod/utils/headers.backend.d.ts +0 -4
  122. package/lib-prod/utils/headers.backend.js +0 -18
  123. package/lib-prod/utils/headers.backend.js.map +0 -1
  124. package/lib-prod/utils/media-type.backend.d.ts +0 -10
  125. package/lib-prod/utils/media-type.backend.js +0 -55
  126. package/lib-prod/utils/media-type.backend.js.map +0 -1
  127. /package/lib-prod/env/{index.d.ts → index.ts} +0 -0
@@ -0,0 +1,140 @@
1
+ import Logger from './logger.backend';
2
+ import Tape from './tape.backend';
3
+ import {Req, MatchingContext} from './types.backend';
4
+
5
+ export const RecordMode = {
6
+ NEW: "NEW", // If no tape matches the request, proxy it and save the response to a tape
7
+ OVERWRITE: "OVERWRITE", // Always proxy the request and save the response to a tape, overwriting any existing one
8
+ DISABLED: "DISABLED", // If a matching tape exists, return it. Otherwise, don't proxy the request and use `fallbackMode` for the response
9
+ ALL: [] as string[]
10
+ }
11
+ RecordMode.ALL = [RecordMode.NEW, RecordMode.OVERWRITE, RecordMode.DISABLED]
12
+
13
+ export const FallbackMode = {
14
+ NOT_FOUND: "NOT_FOUND",
15
+ PROXY: "PROXY",
16
+ ALL: [] as string[]
17
+ }
18
+ FallbackMode.ALL = [FallbackMode.NOT_FOUND, FallbackMode.PROXY]
19
+
20
+ export interface Options {
21
+ host: string,
22
+ port: number,
23
+ path: string,
24
+ record: string | ((req: Req) => string),
25
+ fallbackMode: string | ((req: Req) => string),
26
+ name: string,
27
+ tapeNameGenerator?: (tapeNumber: number, tape: Tape) => string,
28
+ https: {
29
+ enabled: boolean,
30
+ keyPath?: string,
31
+ certPath?: string
32
+ },
33
+ ignoreHeaders: string[],
34
+ ignoreQueryParams: string[],
35
+ ignoreBody: boolean,
36
+
37
+ bodyMatcher?: (tape: Tape, req: Req) => boolean,
38
+ urlMatcher?: (tape: Tape, req: Req) => boolean,
39
+
40
+ requestDecorator?: (req: Req, context: MatchingContext) => Req,
41
+ responseDecorator?: (tape: Tape, req: Req, context: MatchingContext) => Tape,
42
+ tapeDecorator?: (tape: Tape, context: MatchingContext) => Tape,
43
+
44
+ latency: number | number[] | ((req: Req) => number),
45
+ errorRate: number | ((req: Req) => number),
46
+
47
+ silent: boolean,
48
+ summary: boolean,
49
+ debug: boolean,
50
+ logger: Logger
51
+ }
52
+
53
+ export const DefaultOptions: Options = {
54
+ host: "",
55
+ port: 8080,
56
+ path: "./tapes/",
57
+ record: RecordMode.NEW,
58
+ fallbackMode: FallbackMode.NOT_FOUND,
59
+ name: "unnamed",
60
+ tapeNameGenerator: undefined,
61
+
62
+ https: {
63
+ enabled: false,
64
+ keyPath: undefined,
65
+ certPath: undefined
66
+ },
67
+
68
+ ignoreHeaders: ["content-length", "host"],
69
+ ignoreQueryParams: [],
70
+ ignoreBody: false,
71
+
72
+ bodyMatcher: undefined,
73
+ urlMatcher: undefined,
74
+
75
+ requestDecorator: undefined,
76
+ responseDecorator: undefined,
77
+ tapeDecorator: undefined,
78
+
79
+ latency: 0,
80
+ errorRate: 0,
81
+
82
+ silent: false,
83
+ summary: true,
84
+ debug: false,
85
+ logger: new Logger({})
86
+ }
87
+
88
+ export default class OptionsFactory {
89
+ private static logger: Logger
90
+
91
+ static prepare(usrOpts: Partial<Options> = {}) {
92
+ const opts: typeof DefaultOptions = {
93
+ ...DefaultOptions,
94
+ name: usrOpts.host!,
95
+ ...usrOpts,
96
+ ignoreHeaders: [
97
+ ...DefaultOptions.ignoreHeaders,
98
+ ...(usrOpts.ignoreHeaders || [])
99
+ ]
100
+ }
101
+
102
+ this.logger = new Logger(opts)
103
+ opts.logger = this.logger
104
+
105
+ this.validateOptions(opts)
106
+
107
+ return opts
108
+ }
109
+
110
+ static validateOptions(opts: Options) {
111
+ this.validateRecord(opts.record)
112
+ this.validateFallbackMode(opts.fallbackMode)
113
+ this.validateLatency(opts.latency)
114
+ this.validateErrorRate(opts.errorRate)
115
+ }
116
+
117
+ static validateRecord(record: any) {
118
+ if (typeof (record) === "string" && !RecordMode.ALL.includes(record)) {
119
+ throw `INVALID OPTION: record has an invalid value of '${record}'`
120
+ }
121
+ }
122
+
123
+ static validateFallbackMode(fallbackMode: any) {
124
+ if (typeof (fallbackMode) === "string" && !FallbackMode.ALL.includes(fallbackMode)) {
125
+ throw `INVALID OPTION: fallbackMode has an invalid value of '${fallbackMode}'`
126
+ }
127
+ }
128
+
129
+ static validateLatency(latency: any) {
130
+ if (Array.isArray(latency) && latency.length !== 2) {
131
+ throw `Invalid LATENCY option. If using a range, the array should only have 2 values [min, max]. Current=[${latency}]`
132
+ }
133
+ }
134
+
135
+ static validateErrorRate(errorRate: any) {
136
+ if (typeof (errorRate) !== "function" && (errorRate < 0 || errorRate > 100)) {
137
+ throw `Invalid ERRORRATE option. Value should be between 0 and 100. Current=[${errorRate}]`
138
+ }
139
+ }
140
+ }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "ng-talkback/lib-prod",
3
- "version": "21.0.14"
3
+ "version": "21.0.16"
4
4
  }
@@ -0,0 +1,165 @@
1
+ import TapeStore from './tape-store.backend';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+
4
+ import axios, { AxiosResponse } from 'axios';
5
+
6
+ import Tape from './tape.backend';
7
+ import OptionsFactory, { RecordMode, FallbackMode, Options } from './options.backend';
8
+ import ErrorRate from './features/error-rate.backend';
9
+ import Latency from './features/latency.backend';
10
+ import { HttpRequest, HttpResponse, MatchingContext } from './types.backend';
11
+
12
+ export default class RequestHandler {
13
+ private readonly tapeStore: TapeStore
14
+ private readonly options: Options
15
+ private readonly errorRate: ErrorRate
16
+ private readonly latency: Latency
17
+
18
+ constructor(tapeStore: TapeStore, options: Options) {
19
+ this.tapeStore = tapeStore
20
+ this.options = options
21
+ this.errorRate = new ErrorRate(this.options)
22
+ this.latency = new Latency(this.options)
23
+ }
24
+
25
+ async handle(req: HttpRequest): Promise<HttpResponse> {
26
+ const matchingContext: MatchingContext = {
27
+ id: uuidv4()
28
+ }
29
+
30
+ const recordMode = typeof (this.options.record) === "string" ? this.options.record : this.options.record(req)
31
+
32
+ OptionsFactory.validateRecord(recordMode)
33
+
34
+ if (this.options.requestDecorator) {
35
+ req = this.options.requestDecorator(req, matchingContext)
36
+ if (!req) {
37
+ throw new Error("requestDecorator didn't return a req object")
38
+ }
39
+ }
40
+
41
+ let newTape = new Tape(req, this.options)
42
+ let matchingTape = this.tapeStore.find(newTape)
43
+ let resObj, responseTape
44
+
45
+ if (recordMode !== RecordMode.OVERWRITE && matchingTape) {
46
+ responseTape = matchingTape
47
+
48
+ if (this.errorRate.shouldSimulate(req, matchingTape)) {
49
+ return this.errorRate.simulate(req)
50
+ }
51
+
52
+ await this.latency.simulate(req, matchingTape)
53
+ } else {
54
+ if (matchingTape) {
55
+ responseTape = matchingTape
56
+ } else {
57
+ responseTape = newTape
58
+ }
59
+
60
+ if (recordMode === RecordMode.NEW || recordMode === RecordMode.OVERWRITE) {
61
+ resObj = await this.makeRealRequest(req)
62
+ responseTape.res = { ...resObj }
63
+ if (this.options.tapeDecorator) {
64
+ responseTape = this.options.tapeDecorator(responseTape, matchingContext)
65
+ if (!responseTape) {
66
+ throw new Error("tapeDecorator didn't return a tape object")
67
+ }
68
+ }
69
+ await this.tapeStore.save(responseTape)
70
+ } else {
71
+ resObj = await this.onNoRecord(req)
72
+ responseTape.res = { ...resObj }
73
+ }
74
+ }
75
+
76
+ resObj = responseTape.res;
77
+
78
+ if (this.options.responseDecorator) {
79
+ const clonedTape = await responseTape.clone()
80
+ const resTape = this.options.responseDecorator(clonedTape, req, matchingContext)
81
+ if (!resTape) {
82
+ throw new Error("responseDecorator didn't return a tape object")
83
+ }
84
+
85
+ if (resTape.res.headers["content-length"]) {
86
+ resTape.res.headers["content-length"] = resTape.res.body.length
87
+ }
88
+ resObj = resTape.res
89
+ }
90
+
91
+ return resObj
92
+ }
93
+
94
+ private async onNoRecord(req: HttpRequest) {
95
+ const fallbackMode = typeof (this.options.fallbackMode) === "string" ? this.options.fallbackMode : this.options.fallbackMode(req)
96
+
97
+ OptionsFactory.validateFallbackMode(fallbackMode)
98
+
99
+ this.options.logger.log(`Tape for ${req.url} not found and recording is disabled (fallbackMode: ${fallbackMode})`)
100
+ this.options.logger.log({
101
+ url: req.url,
102
+ headers: req.headers
103
+ })
104
+
105
+ if (fallbackMode === FallbackMode.PROXY) {
106
+ if (this.errorRate.shouldSimulate(req, undefined)) {
107
+ return this.errorRate.simulate(req)
108
+ }
109
+
110
+ await this.latency.simulate(req, undefined)
111
+ return await this.makeRealRequest(req)
112
+ }
113
+
114
+ return {
115
+ status: 404,
116
+ headers: { "content-type": ["text/plain"] },
117
+ body: Buffer.from("talkback - tape not found")
118
+ } as HttpResponse
119
+ }
120
+
121
+ private async makeRealRequest(req: HttpRequest) {
122
+ // let fetchBody: Buffer | null
123
+ let { method, url, body } = req
124
+ // fetchBody = body
125
+ const headers = { ...req.headers }
126
+ delete headers.host
127
+
128
+ const host = this.options.host
129
+ // this.options.logger.log(`Making real request to ${host}${url}`)
130
+
131
+ // if (method === "GET" || method === "HEAD") {
132
+ // fetchBody = null
133
+ // }
134
+ let urlToGetData = `${host}${url}`;
135
+ // console.log(`host: "${urlToGetData}"` )
136
+ // console.log(`url: "${url}"` )
137
+ var fRes = {
138
+ status: 400,
139
+ headers: {},
140
+ body: new Buffer('')
141
+ } as any;
142
+ try {
143
+ // console.log(body.toString())
144
+ const r = await axios({
145
+ url: urlToGetData,
146
+ method,
147
+ headers,
148
+ data: body,
149
+ responseType: 'arraybuffer',
150
+ }) as any;
151
+ fRes = {
152
+ status: r.status,
153
+ headers: r.headers,
154
+ body: r.data
155
+ } as HttpResponse;
156
+ } catch (err) {
157
+ fRes = {
158
+ status: err?.response?.status,
159
+ headers: err?.response?.headers,
160
+ body: err?.response?.data
161
+ } as any;
162
+ }
163
+ return fRes;
164
+ }
165
+ }
@@ -0,0 +1,100 @@
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
+ import { Options } from './options.backend';
7
+ import { Req } from './types.backend';
8
+
9
+ export default class TalkbackServer {
10
+ private readonly options: Options
11
+ readonly tapeStore: TapeStore
12
+ private requestHandler: RequestHandler
13
+ private readonly closeSignalHandler?: (...args: any[]) => void
14
+ private server?: http.Server
15
+ private closed: boolean = false
16
+
17
+ constructor(options: Options) {
18
+ this.options = options
19
+ this.tapeStore = new TapeStore(this.options)
20
+ this.requestHandler = new RequestHandler(this.tapeStore, this.options)
21
+
22
+ this.closeSignalHandler = this.close.bind(this)
23
+ }
24
+
25
+ handleRequest(rawReq: http.IncomingMessage, res: http.ServerResponse) {
26
+ // console.log(`rawReq: ${rawReq.url}`)
27
+ let reqBody = [] as Uint8Array[]
28
+ rawReq.on("data", (chunk) => {
29
+ reqBody.push(chunk)
30
+ }).on("end", async () => {
31
+ try {
32
+ const req: Req = {
33
+ headers: rawReq.headers,
34
+ url: rawReq.url,
35
+ method: rawReq.method,
36
+ body: Buffer.concat(reqBody)
37
+ }
38
+ const fRes = await this.requestHandler.handle(req)
39
+
40
+ res.writeHead(fRes.status, fRes.headers)
41
+ res.end(fRes.body)
42
+ } catch (ex) {
43
+ console.error("Error handling request", ex)
44
+ res.statusCode = 500
45
+ res.end()
46
+ }
47
+ })
48
+ }
49
+
50
+ async start(callback?: () => void) {
51
+ await this.tapeStore.load()
52
+ const handleRequest = this.handleRequest.bind(this)
53
+
54
+ const serverFactory = this.options.https.enabled ? () => {
55
+ const httpsOpts = {
56
+ key: fse.readFileSync(this.options.https.keyPath!),
57
+ cert: fse.readFileSync(this.options.https.certPath!)
58
+ }
59
+ return https.createServer(httpsOpts, handleRequest)
60
+ } : () => http.createServer(handleRequest)
61
+
62
+ this.server = serverFactory()
63
+ console.log(`Starting talkback on ${this.options.port}`)
64
+ this.server.listen(this.options.port, callback)
65
+
66
+ process.on("exit", this.closeSignalHandler as any)
67
+ process.on("SIGINT", this.closeSignalHandler as any)
68
+ process.on("SIGTERM", this.closeSignalHandler as any)
69
+
70
+ return this.server
71
+ }
72
+
73
+ hasTapeBeenUsed(tapeName: string) {
74
+ return this.tapeStore.hasTapeBeenUsed(tapeName)
75
+ }
76
+
77
+ resetTapeUsage() {
78
+ this.tapeStore.resetTapeUsage()
79
+ }
80
+
81
+ close(callback?: () => void) {
82
+ if (this.closed) {
83
+ return
84
+ }
85
+ this.closed = true
86
+ this.server!.close(callback)
87
+
88
+ // @ts-ignore
89
+ process.removeListener("exit", this.closeSignalHandler as any)
90
+ // @ts-ignore
91
+ process.removeListener("SIGINT", this.closeSignalHandler as any)
92
+ // @ts-ignore
93
+ process.removeListener("SIGTERM", this.closeSignalHandler as any)
94
+
95
+ if (this.options.summary) {
96
+ const summary = new Summary(this.tapeStore.tapes, this.options)
97
+ summary.print()
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,26 @@
1
+ import Tape from './tape.backend';
2
+ import {Options} from './options.backend';
3
+
4
+ export default class Summary {
5
+ private tapes: Tape[]
6
+ private opts: Options
7
+
8
+ constructor(tapes: Tape[], opts: Options) {
9
+ this.tapes = tapes
10
+ this.opts = opts
11
+ }
12
+
13
+ print() {
14
+ console.log(`===== SUMMARY (${this.opts.name}) =====`)
15
+ const newTapes = this.tapes.filter(t => t.new)
16
+ if (newTapes.length > 0) {
17
+ console.log("New tapes:")
18
+ newTapes.forEach(t => console.log(`- ${t.path}`))
19
+ }
20
+ const unusedTapes = this.tapes.filter(t => !t.used)
21
+ if (unusedTapes.length > 0) {
22
+ console.log("Unused tapes:")
23
+ unusedTapes.forEach(t => console.log(`- ${t.path}`))
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,18 @@
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
+
6
+ export default class TalkbackFactory {
7
+ static server(options: Partial<Options>) {
8
+ const fullOptions = Options.prepare(options)
9
+ return new TalkbackServer(fullOptions)
10
+ }
11
+
12
+ static async requestHandler(options: Partial<Options>) {
13
+ const fullOptions = Options.prepare(options)
14
+ const tapeStore = new TapeStore(fullOptions)
15
+ await tapeStore.load()
16
+ return new RequestHandler(tapeStore, fullOptions)
17
+ }
18
+ }
@@ -0,0 +1,113 @@
1
+ import ContentEncoding from './utils/content-encoding.backend';
2
+ import MediaType from './utils/media-type.backend';
3
+ import { Options } from './options.backend';
4
+ import Tape from './tape.backend';
5
+ import { Req } from './types.backend';
6
+ import { ___NS__add, ___NS__after, ___NS__ary, ___NS__assign, ___NS__assignIn, ___NS__assignInWith, ___NS__assignWith, ___NS__at, ___NS__attempt, ___NS__before, ___NS__bind, ___NS__bindAll, ___NS__bindKey, ___NS__camelCase, ___NS__capitalize, ___NS__castArray, ___NS__ceil, ___NS__chain, ___NS__chunk, ___NS__clamp, ___NS__clone, ___NS__cloneDeep, ___NS__cloneDeepWith, ___NS__cloneWith, ___NS__compact, ___NS__concat, ___NS__cond, ___NS__conforms, ___NS__conformsTo, ___NS__constant, ___NS__countBy, ___NS__create, ___NS__curry, ___NS__curryRight, ___NS__debounce, ___NS__deburr, ___NS__defaults, ___NS__defaultsDeep, ___NS__defaultTo, ___NS__defer, ___NS__delay, ___NS__difference, ___NS__differenceBy, ___NS__differenceWith, ___NS__divide, ___NS__drop, ___NS__dropRight, ___NS__dropRightWhile, ___NS__dropWhile, ___NS__each, ___NS__eachRight, ___NS__endsWith, ___NS__entries, ___NS__entriesIn, ___NS__eq, ___NS__escape, ___NS__escapeRegExp, ___NS__every, ___NS__extend, ___NS__extendWith, ___NS__fill, ___NS__filter, ___NS__find, ___NS__findIndex, ___NS__findKey, ___NS__findLast, ___NS__findLastIndex, ___NS__findLastKey, ___NS__first, ___NS__flatMap, ___NS__flatMapDeep, ___NS__flatMapDepth, ___NS__flatten, ___NS__flattenDeep, ___NS__flattenDepth, ___NS__flip, ___NS__floor, ___NS__flow, ___NS__flowRight, ___NS__forEach, ___NS__forEachRight, ___NS__forIn, ___NS__forInRight, ___NS__forOwn, ___NS__forOwnRight, ___NS__fromPairs, ___NS__functions, ___NS__functionsIn, ___NS__get, ___NS__groupBy, ___NS__gt, ___NS__gte, ___NS__has, ___NS__hasIn, ___NS__head, ___NS__identity, ___NS__includes, ___NS__indexOf, ___NS__initial, ___NS__inRange, ___NS__intersection, ___NS__intersectionBy, ___NS__intersectionWith, ___NS__invert, ___NS__invertBy, ___NS__invoke, ___NS__invokeMap, ___NS__isArguments, ___NS__isArray, ___NS__isArrayBuffer, ___NS__isArrayLike, ___NS__isArrayLikeObject, ___NS__isBoolean, ___NS__isBuffer, ___NS__isDate, ___NS__isElement, ___NS__isEmpty, ___NS__isEqual, ___NS__isEqualWith, ___NS__isError, ___NS__isFinite, ___NS__isFunction, ___NS__isInteger, ___NS__isLength, ___NS__isMap, ___NS__isMatch, ___NS__isMatchWith, ___NS__isNaN, ___NS__isNative, ___NS__isNil, ___NS__isNull, ___NS__isNumber, ___NS__isObject, ___NS__isObjectLike, ___NS__isPlainObject, ___NS__isRegExp, ___NS__isSafeInteger, ___NS__isSet, ___NS__isString, ___NS__isSymbol, ___NS__isTypedArray, ___NS__isUndefined, ___NS__isWeakMap, ___NS__isWeakSet, ___NS__iteratee, ___NS__join, ___NS__kebabCase, ___NS__keyBy, ___NS__keys, ___NS__keysIn, ___NS__last, ___NS__lastIndexOf, ___NS__lowerCase, ___NS__lowerFirst, ___NS__lt, ___NS__lte, ___NS__map, ___NS__mapKeys, ___NS__mapValues, ___NS__matches, ___NS__matchesProperty, ___NS__max, ___NS__maxBy, ___NS__mean, ___NS__meanBy, ___NS__memoize, ___NS__merge, ___NS__mergeWith, ___NS__method, ___NS__methodOf, ___NS__min, ___NS__minBy, ___NS__mixin, ___NS__multiply, ___NS__negate, ___NS__noop, ___NS__now, ___NS__nth, ___NS__nthArg, ___NS__omit, ___NS__omitBy, ___NS__once, ___NS__orderBy, ___NS__over, ___NS__overArgs, ___NS__overEvery, ___NS__overSome, ___NS__pad, ___NS__padEnd, ___NS__padStart, ___NS__parseInt, ___NS__partial, ___NS__partialRight, ___NS__partition, ___NS__pick, ___NS__pickBy, ___NS__property, ___NS__propertyOf, ___NS__pull, ___NS__pullAll, ___NS__pullAllBy, ___NS__pullAllWith, ___NS__pullAt, ___NS__random, ___NS__range, ___NS__rangeRight, ___NS__rearg, ___NS__reduce, ___NS__reduceRight, ___NS__reject, ___NS__remove, ___NS__repeat, ___NS__replace, ___NS__rest, ___NS__result, ___NS__reverse, ___NS__round, ___NS__sample, ___NS__sampleSize, ___NS__set, ___NS__setWith, ___NS__shuffle, ___NS__size, ___NS__slice, ___NS__snakeCase, ___NS__some, ___NS__sortBy, ___NS__sortedIndex, ___NS__sortedIndexBy, ___NS__sortedIndexOf, ___NS__sortedLastIndex, ___NS__sortedLastIndexBy, ___NS__sortedLastIndexOf, ___NS__sortedUniq, ___NS__sortedUniqBy, ___NS__split, ___NS__spread, ___NS__startCase, ___NS__startsWith, ___NS__stubArray, ___NS__stubFalse, ___NS__stubObject, ___NS__stubString, ___NS__stubTrue, ___NS__subtract, ___NS__sum, ___NS__sumBy, ___NS__tail, ___NS__take, ___NS__takeRight, ___NS__takeRightWhile, ___NS__takeWhile, ___NS__tap, ___NS__template, ___NS__templateSettings, ___NS__throttle, ___NS__thru, ___NS__times, ___NS__toArray, ___NS__toFinite, ___NS__toInteger, ___NS__toLength, ___NS__toLower, ___NS__toNumber, ___NS__toPairs, ___NS__toPairsIn, ___NS__toPath, ___NS__toPlainObject, ___NS__toSafeInteger, ___NS__toString, ___NS__toUpper, ___NS__transform, ___NS__trim, ___NS__trimEnd, ___NS__trimStart, ___NS__truncate, ___NS__unary, ___NS__unescape, ___NS__union, ___NS__unionBy, ___NS__unionWith, ___NS__uniq, ___NS__uniqBy, ___NS__uniqueId, ___NS__uniqWith, ___NS__unset, ___NS__unzip, ___NS__unzipWith, ___NS__update, ___NS__updateWith, ___NS__upperCase, ___NS__upperFirst, ___NS__values, ___NS__valuesIn, ___NS__without, ___NS__words, ___NS__wrap, ___NS__xor, ___NS__xorBy, ___NS__xorWith, ___NS__zip, ___NS__zipObject, ___NS__zipObjectDeep, ___NS__zipWith } from 'tnp-core/lib-prod';
7
+
8
+ export default class TapeMatcher {
9
+ private readonly tape: Tape
10
+ private readonly options: Options
11
+
12
+ constructor(tape: Tape, options: Options) {
13
+ this.tape = tape
14
+ this.options = options
15
+ }
16
+
17
+ sameAs(otherTape: Tape) {
18
+ const otherReq = otherTape.req
19
+ const req = this.tape.req
20
+ if (!this.isSameUrl(req, otherReq)) {
21
+ return false
22
+ }
23
+
24
+ if (!this.isSameMethod(req, otherReq)) {
25
+ return false
26
+ }
27
+
28
+ if (!this.isSameHeaders(req, otherReq)) {
29
+ return false
30
+ }
31
+
32
+ return this.options.ignoreBody || this.isSameBody(req, otherReq);
33
+
34
+ }
35
+
36
+ private isSameBody(req: Req, otherReq: Req) {
37
+ const mediaType = new MediaType(req)
38
+ const contentEncoding = new ContentEncoding(req)
39
+
40
+
41
+ let sameBody: boolean
42
+ if (contentEncoding.isUncompressed() && mediaType.isJSON() && req.body.length > 0 && otherReq.body.length > 0) {
43
+ const parsedReqBody = JSON.parse(req.body.toString())
44
+ const parsedOtherReqBody = JSON.parse(otherReq.body.toString())
45
+ sameBody = ___NS__isEqual(parsedReqBody, parsedOtherReqBody)
46
+ } else {
47
+ sameBody = req.body.equals(otherReq.body)
48
+ }
49
+
50
+ if (!sameBody) {
51
+ if (!this.options.bodyMatcher) {
52
+ this.options.logger.debug(`Not same BODY ${req.body} vs ${otherReq.body}`)
53
+ return false
54
+ }
55
+
56
+ const bodyMatches = this.options.bodyMatcher(this.tape, otherReq)
57
+ if (!bodyMatches) {
58
+ this.options.logger.debug(`Not same bodyMatcher ${req.body} vs ${otherReq.body}`)
59
+ return false
60
+ }
61
+ }
62
+ return true
63
+ }
64
+
65
+ private isSameHeaders(req: Req, otherReq: Req) {
66
+ const currentHeadersLength = Object.keys(req.headers).length
67
+ const otherHeadersLength = Object.keys(otherReq.headers).length
68
+ const sameNumberOfHeaders = currentHeadersLength === otherHeadersLength
69
+ if (!sameNumberOfHeaders) {
70
+ this.options.logger.debug(`Not same #HEADERS ${JSON.stringify(req.headers)} vs ${JSON.stringify(otherReq.headers)}`)
71
+ return false
72
+ }
73
+
74
+ let headersSame = true
75
+ Object.keys(req.headers).forEach(k => {
76
+ const entryHeader = req.headers[k]
77
+ const header = otherReq.headers[k]
78
+
79
+ headersSame = headersSame && entryHeader === header
80
+ })
81
+ if (!headersSame) {
82
+ this.options.logger.debug(`Not same HEADERS values ${JSON.stringify(req.headers)} vs ${JSON.stringify(otherReq.headers)}`)
83
+ return false
84
+ }
85
+ return true
86
+ }
87
+
88
+ private isSameMethod(req: Req, otherReq: Req) {
89
+ const sameMethod = req.method === otherReq.method
90
+ if (!sameMethod) {
91
+ this.options.logger.debug(`Not same METHOD ${req.method} vs ${otherReq.method}`)
92
+ return false
93
+ }
94
+ return true
95
+ }
96
+
97
+ private isSameUrl(req: Req, otherReq: Req) {
98
+ const sameURL = req.url === otherReq.url
99
+ if (!sameURL) {
100
+ if (!this.options.urlMatcher) {
101
+ this.options.logger.debug(`Not same URL ${req.url} vs ${otherReq.url}`)
102
+ return false
103
+ }
104
+
105
+ const urlMatches = this.options.urlMatcher(this.tape, otherReq)
106
+ if (!urlMatches) {
107
+ this.options.logger.debug(`Not same urlMatcher ${req.url} vs ${otherReq.url}`)
108
+ return false
109
+ }
110
+ }
111
+ return true
112
+ }
113
+ }
@@ -0,0 +1,118 @@
1
+ import Headers from './utils/headers.backend';
2
+ import MediaType from './utils/media-type.backend';
3
+ import Tape from './tape.backend';
4
+ import ContentEncoding from './utils/content-encoding.backend';
5
+ import { Options } from './options.backend';
6
+ import { ReqRes } from './types.backend';
7
+ import * as _ from 'lodash';
8
+ import * as bufferShim from 'buffer-shims';
9
+ // const bufferShim = require('buffer-shims')/
10
+
11
+ export default class TapeRenderer {
12
+ private tape: Tape
13
+
14
+ constructor(tape: Tape) {
15
+ this.tape = tape
16
+ }
17
+
18
+ static async fromStore(raw: any, options: Options) {
19
+
20
+ const req = { ...raw.req }
21
+
22
+ req.body = await this.prepareBody(raw, req, req.body, "req")
23
+
24
+ const tape = new Tape(req, options)
25
+ tape.meta = { ...raw.meta }
26
+ const baseRes = { ...raw.res }
27
+ const resBody = await this.prepareBody(tape, baseRes, baseRes.body, "res")
28
+
29
+ tape.res = {
30
+ ...baseRes,
31
+ body: resBody
32
+ }
33
+
34
+ return tape
35
+ }
36
+
37
+ static async prepareBody(tape: Tape, reqResObj: ReqRes, rawBody: string, metaPrefix: "res" | "req") {
38
+ const contentEncoding = new ContentEncoding(reqResObj)
39
+ const isTapeUncompressed = (tape.meta as any)[metaPrefix + "Uncompressed"]
40
+ const isTapeHumanReadable = (tape.meta as any)[metaPrefix + "HumanReadable"]
41
+ const isTapeInPlainText = isTapeUncompressed || contentEncoding.isUncompressed()
42
+
43
+ if (isTapeHumanReadable && isTapeInPlainText) {
44
+ const mediaType = new MediaType(reqResObj)
45
+ let bufferContent = rawBody
46
+ const isResAnObject = typeof (bufferContent) === "object"
47
+
48
+ if (isResAnObject && mediaType.isJSON()) {
49
+ bufferContent = JSON.stringify(bufferContent, null, 2)
50
+ }
51
+
52
+ if (Headers.read(reqResObj.headers, "content-length")) {
53
+ Headers.write(reqResObj.headers, "content-length", Buffer.byteLength(bufferContent).toString(), metaPrefix)
54
+ }
55
+
56
+ if (isTapeUncompressed) {
57
+ return await contentEncoding.compressedBody(bufferContent)
58
+ }
59
+
60
+ return bufferShim.from(bufferContent)
61
+ } else {
62
+ return bufferShim.from(rawBody, "base64")
63
+ }
64
+ }
65
+
66
+ async render() {
67
+ const reqBody = await this.bodyFor(this.tape.req, "req")
68
+ const resBody = await this.bodyFor(this.tape.res!, "res")
69
+ return {
70
+ meta: this.tape.meta,
71
+ req: {
72
+ ...this.tape.req,
73
+ body: reqBody
74
+ },
75
+ res: {
76
+ ...this.tape.res,
77
+ body: resBody
78
+ }
79
+ }
80
+ }
81
+
82
+ async bodyFor(reqResObj: ReqRes, metaPrefix: "req" | "res") {
83
+ const mediaType = new MediaType(reqResObj)
84
+ const contentEncoding = new ContentEncoding(reqResObj)
85
+ const bodyLength = reqResObj.body.length
86
+
87
+
88
+ const isUncompressed = contentEncoding.isUncompressed()
89
+ const contentEncodingSupported = isUncompressed || contentEncoding.supportedAlgorithm()
90
+
91
+ if (mediaType.isHumanReadable() && contentEncodingSupported && bodyLength > 0) {
92
+ (this.tape.meta as any)[metaPrefix + "HumanReadable"] = true
93
+
94
+ let body = reqResObj.body
95
+
96
+ if (!isUncompressed) {
97
+ (this.tape.meta as any)[metaPrefix + "Uncompressed"] = true
98
+ body = await contentEncoding.uncompressedBody(body)
99
+ }
100
+
101
+ const rawBody = body.toString("utf8")
102
+
103
+ if (mediaType.isJSON()) {
104
+ try { // TODO handle this better in future not based on mediaType.isJSON
105
+ const parsed = JSON.parse(rawBody);
106
+ return parsed;
107
+ } catch (error) {
108
+ return rawBody;
109
+ }
110
+ } else {
111
+ return rawBody;
112
+ }
113
+ } else {
114
+ return reqResObj.body.toString("base64")
115
+ }
116
+ }
117
+
118
+ }