@sasjs/adapter 4.11.1 → 4.11.3
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/SASViyaApiClient.d.ts +6 -0
- package/SASjs.d.ts +1 -1
- package/api/viya/writeStream.d.ts +1 -1
- package/auth/getTokenRequestErrorPrefix.d.ts +2 -2
- package/file/generateFileUploadForm.d.ts +1 -1
- package/file/generateTableUploadForm.d.ts +1 -1
- package/index-dev.js +77056 -3
- package/index.js +77056 -2
- package/job-execution/JobExecutor.d.ts +1 -1
- package/minified_sas9.js +16558 -2
- package/node/SASViyaApiClient.d.ts +6 -0
- package/node/SASjs.d.ts +1 -1
- package/node/api/viya/writeStream.d.ts +1 -1
- package/node/auth/getTokenRequestErrorPrefix.d.ts +2 -2
- package/node/file/generateFileUploadForm.d.ts +1 -1
- package/node/file/generateTableUploadForm.d.ts +1 -1
- package/node/index.js +28150 -2
- package/node/job-execution/JobExecutor.d.ts +1 -1
- package/node/request/RequestClient.d.ts +11 -2
- package/node/types/PollOptions.d.ts +1 -1
- package/node/types/RequestClient.d.ts +1 -1
- package/node/types/WriteStream.d.ts +6 -3
- package/node/utils/getFormData.d.ts +1 -1
- package/package.json +13 -11
- package/request/RequestClient.d.ts +11 -2
- package/spec/SAS9ApiClient.spec.d.ts +1 -0
- package/spec/SASjsApiClient.spec.d.ts +1 -0
- package/src/SAS9ApiClient.ts +1 -1
- package/src/SASViyaApiClient.ts +1 -1
- package/src/SASjs.ts +2 -2
- package/src/SASjsApiClient.ts +1 -1
- package/src/api/viya/writeStream.ts +10 -5
- package/src/auth/refreshTokensForViya.ts +1 -1
- package/src/file/generateFileUploadForm.ts +1 -1
- package/src/file/generateTableUploadForm.ts +1 -1
- package/src/job-execution/Sas9JobExecutor.ts +1 -1
- package/src/job-execution/SasjsJobExecutor.ts +5 -3
- package/src/job-execution/WebJobExecutor.ts +5 -3
- package/src/request/RequestClient.ts +73 -81
- package/src/request/Sas9RequestClient.ts +5 -5
- package/src/test/SAS_server_app.ts +10 -2
- package/src/types/WriteStream.ts +9 -3
- package/src/utils/convertToCsv.ts +5 -1
- package/src/utils/getFormData.ts +2 -2
- package/src/utils/validateInput.ts +16 -13
- package/types/PollOptions.d.ts +1 -1
- package/types/RequestClient.d.ts +1 -1
- package/types/WriteStream.d.ts +6 -3
- package/types/errors/spec/SAS9AuthError.spec.d.ts +1 -0
- package/utils/getFormData.d.ts +1 -1
- package/utils/spec/parseSasViyaLog.spec.d.ts +1 -0
- package/utils/spec/parseViyaDebugResponse.spec.d.ts +1 -0
- package/index-dev.js.LICENSE.txt +0 -110
- package/index.js.LICENSE.txt +0 -110
- package/minified_sas9.js.LICENSE.txt +0 -15
- package/node/index.js.LICENSE.txt +0 -106
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthConfig, ServerType } from '@sasjs/utils/types';
|
|
2
2
|
import { ExtraResponseAttributes } from '@sasjs/utils/types';
|
|
3
|
-
export
|
|
3
|
+
export type ExecuteFunction = () => Promise<any>;
|
|
4
4
|
export interface JobExecutor {
|
|
5
5
|
execute: (sasJob: string, data: any, config: any, loginRequiredCallback?: any, authConfig?: AuthConfig, extraResponseAttributes?: ExtraResponseAttributes[]) => Promise<any>;
|
|
6
6
|
resendWaitingRequests: () => Promise<void>;
|
|
@@ -42,6 +42,13 @@ export declare class RequestClient implements HttpClient {
|
|
|
42
42
|
etag: string;
|
|
43
43
|
status: number;
|
|
44
44
|
}>;
|
|
45
|
+
/**
|
|
46
|
+
* @param contentType Newer version of Axios is more strict so if you don't
|
|
47
|
+
* set the contentType to `form data` while sending a FormData object
|
|
48
|
+
* application/json will be used by default, axios won’t treat it as FormData.
|
|
49
|
+
* Instead, it serializes data as JSON—resulting in a payload like
|
|
50
|
+
* {"sometable":{}} and we lose the multipart/form-data formatting.
|
|
51
|
+
*/
|
|
45
52
|
post<T>(url: string, data: any, accessToken: string | undefined, contentType?: string, overrideHeaders?: {
|
|
46
53
|
[key: string]: string | number;
|
|
47
54
|
}, additionalSettings?: {
|
|
@@ -79,7 +86,9 @@ export declare class RequestClient implements HttpClient {
|
|
|
79
86
|
* @returns - formatted string.
|
|
80
87
|
*/
|
|
81
88
|
private parseInterceptedBody;
|
|
82
|
-
private
|
|
89
|
+
private handleAxiosResponse;
|
|
90
|
+
private handleAxiosError;
|
|
91
|
+
private formatHeaders;
|
|
83
92
|
/**
|
|
84
93
|
* Sets verbose mode.
|
|
85
94
|
* @param verboseMode - value of the verbose mode, can be true, false or bleached(without extra colors).
|
|
@@ -90,7 +99,7 @@ export declare class RequestClient implements HttpClient {
|
|
|
90
99
|
* @param successCallBack - function that should be triggered on every HTTP response with the status 2**.
|
|
91
100
|
* @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**.
|
|
92
101
|
*/
|
|
93
|
-
enableVerboseMode: (successCallBack?: (
|
|
102
|
+
enableVerboseMode: (successCallBack?: (response: AxiosResponse) => AxiosResponse<any, any>, errorCallBack?: (error: AxiosError) => AxiosError<unknown, any>) => void;
|
|
94
103
|
/**
|
|
95
104
|
* Turns off verbose mode to log every HTTP response.
|
|
96
105
|
*/
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { WriteStream as FsWriteStream } from 'fs';
|
|
4
|
+
export interface WriteStream extends FsWriteStream {
|
|
5
|
+
write(chunk: any, encoding?: BufferEncoding | ((error: Error | null | undefined) => void), cb?: (error: Error | null | undefined) => void): boolean;
|
|
6
|
+
path: string | Buffer;
|
|
4
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import NodeFormData from 'form-data';
|
|
2
2
|
export declare const getFormData: () => NodeFormData | FormData;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"nodeVersionMessage": "echo \u001b[33m make sure you are running node lts version \u001b[0m",
|
|
7
7
|
"preinstall": "npm run nodeVersionMessage",
|
|
8
8
|
"prebuild": "npm run nodeVersionMessage",
|
|
9
|
-
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
|
|
9
|
+
"build": "npx rimraf build && npx rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && npx rimraf build/src && npx rimraf node",
|
|
10
10
|
"package:lib": "npm run build && copyfiles ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
|
11
11
|
"publish:lib": "npm run build && cd build && npm publish",
|
|
12
12
|
"lint:fix": "npx prettier --loglevel silent --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"cypress/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
|
@@ -45,44 +45,46 @@
|
|
|
45
45
|
"license": "ISC",
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@cypress/webpack-preprocessor": "5.9.1",
|
|
48
|
+
"@types/cors": "^2.8.17",
|
|
48
49
|
"@types/express": "4.17.13",
|
|
49
|
-
"@types/jest": "
|
|
50
|
+
"@types/jest": "29.5.14",
|
|
50
51
|
"@types/mime": "2.0.3",
|
|
51
52
|
"@types/pem": "1.9.6",
|
|
52
53
|
"@types/tough-cookie": "4.0.2",
|
|
53
54
|
"copyfiles": "2.4.1",
|
|
55
|
+
"cors": "^2.8.5",
|
|
54
56
|
"cp": "0.2.0",
|
|
55
57
|
"cypress": "7.7.0",
|
|
56
58
|
"dotenv": "16.0.0",
|
|
57
59
|
"express": "4.17.3",
|
|
58
|
-
"jest": "
|
|
59
|
-
"jest-
|
|
60
|
+
"jest": "29.7.0",
|
|
61
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
62
|
+
"jest-extended": "4.0.2",
|
|
60
63
|
"node-polyfill-webpack-plugin": "1.1.4",
|
|
61
64
|
"path": "0.12.7",
|
|
62
65
|
"pem": "1.14.5",
|
|
63
66
|
"prettier": "2.8.7",
|
|
64
67
|
"process": "0.11.10",
|
|
65
|
-
"rimraf": "^6.0.1",
|
|
66
68
|
"semantic-release": "19.0.3",
|
|
67
69
|
"terser-webpack-plugin": "5.3.6",
|
|
68
|
-
"ts-jest": "
|
|
70
|
+
"ts-jest": "29.2.6",
|
|
69
71
|
"ts-loader": "9.4.0",
|
|
70
72
|
"tslint": "6.1.3",
|
|
71
73
|
"tslint-config-prettier": "1.18.0",
|
|
72
74
|
"typedoc": "0.23.24",
|
|
73
75
|
"typedoc-plugin-rename-defaults": "0.6.4",
|
|
74
|
-
"typescript": "4.
|
|
76
|
+
"typescript": "4.9.5",
|
|
75
77
|
"webpack": "5.76.2",
|
|
76
78
|
"webpack-cli": "4.9.2"
|
|
77
79
|
},
|
|
78
80
|
"main": "index.js",
|
|
79
81
|
"dependencies": {
|
|
80
|
-
"@sasjs/utils": "
|
|
81
|
-
"axios": "
|
|
82
|
-
"axios-cookiejar-support": "
|
|
82
|
+
"@sasjs/utils": "3.5.2",
|
|
83
|
+
"axios": "1.8.2",
|
|
84
|
+
"axios-cookiejar-support": "5.0.5",
|
|
83
85
|
"form-data": "4.0.0",
|
|
84
86
|
"https": "1.0.0",
|
|
85
87
|
"tough-cookie": "4.1.3"
|
|
86
88
|
},
|
|
87
|
-
"version": "4.11.
|
|
89
|
+
"version": "4.11.3"
|
|
88
90
|
}
|
|
@@ -42,6 +42,13 @@ export declare class RequestClient implements HttpClient {
|
|
|
42
42
|
etag: string;
|
|
43
43
|
status: number;
|
|
44
44
|
}>;
|
|
45
|
+
/**
|
|
46
|
+
* @param contentType Newer version of Axios is more strict so if you don't
|
|
47
|
+
* set the contentType to `form data` while sending a FormData object
|
|
48
|
+
* application/json will be used by default, axios won’t treat it as FormData.
|
|
49
|
+
* Instead, it serializes data as JSON—resulting in a payload like
|
|
50
|
+
* {"sometable":{}} and we lose the multipart/form-data formatting.
|
|
51
|
+
*/
|
|
45
52
|
post<T>(url: string, data: any, accessToken: string | undefined, contentType?: string, overrideHeaders?: {
|
|
46
53
|
[key: string]: string | number;
|
|
47
54
|
}, additionalSettings?: {
|
|
@@ -79,7 +86,9 @@ export declare class RequestClient implements HttpClient {
|
|
|
79
86
|
* @returns - formatted string.
|
|
80
87
|
*/
|
|
81
88
|
private parseInterceptedBody;
|
|
82
|
-
private
|
|
89
|
+
private handleAxiosResponse;
|
|
90
|
+
private handleAxiosError;
|
|
91
|
+
private formatHeaders;
|
|
83
92
|
/**
|
|
84
93
|
* Sets verbose mode.
|
|
85
94
|
* @param verboseMode - value of the verbose mode, can be true, false or bleached(without extra colors).
|
|
@@ -90,7 +99,7 @@ export declare class RequestClient implements HttpClient {
|
|
|
90
99
|
* @param successCallBack - function that should be triggered on every HTTP response with the status 2**.
|
|
91
100
|
* @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**.
|
|
92
101
|
*/
|
|
93
|
-
enableVerboseMode: (successCallBack?: (
|
|
102
|
+
enableVerboseMode: (successCallBack?: (response: AxiosResponse) => AxiosResponse<any, any>, errorCallBack?: (error: AxiosError) => AxiosError<unknown, any>) => void;
|
|
94
103
|
/**
|
|
95
104
|
* Turns off verbose mode to log every HTTP response.
|
|
96
105
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/src/SAS9ApiClient.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as https from 'https'
|
|
2
2
|
import { generateTimestamp } from '@sasjs/utils/time'
|
|
3
|
-
import
|
|
3
|
+
import NodeFormData from 'form-data'
|
|
4
4
|
import { Sas9RequestClient } from './request/Sas9RequestClient'
|
|
5
5
|
import { isUrl } from './utils'
|
|
6
6
|
|
package/src/SASViyaApiClient.ts
CHANGED
package/src/SASjs.ts
CHANGED
|
@@ -1170,8 +1170,8 @@ export default class SASjs {
|
|
|
1170
1170
|
* @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**.
|
|
1171
1171
|
*/
|
|
1172
1172
|
public enableVerboseMode(
|
|
1173
|
-
successCallBack?: (response: AxiosResponse
|
|
1174
|
-
errorCallBack?: (response:
|
|
1173
|
+
successCallBack?: (response: AxiosResponse) => AxiosResponse,
|
|
1174
|
+
errorCallBack?: (response: AxiosError) => AxiosError
|
|
1175
1175
|
) {
|
|
1176
1176
|
this.requestClient?.enableVerboseMode(successCallBack, errorCallBack)
|
|
1177
1177
|
}
|
package/src/SASjsApiClient.ts
CHANGED
|
@@ -3,9 +3,14 @@ import { WriteStream } from '../../types'
|
|
|
3
3
|
export const writeStream = async (
|
|
4
4
|
stream: WriteStream,
|
|
5
5
|
content: string
|
|
6
|
-
): Promise<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
): Promise<boolean> => {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
stream.write(content + '\n', (err: Error | null | undefined) => {
|
|
9
|
+
if (err) {
|
|
10
|
+
reject(err) // Reject on write error
|
|
11
|
+
} else {
|
|
12
|
+
resolve(true) // Resolve on successful write
|
|
13
|
+
}
|
|
14
|
+
})
|
|
11
15
|
})
|
|
16
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SasAuthResponse, ServerType } from '@sasjs/utils/types'
|
|
2
2
|
import { prefixMessage } from '@sasjs/utils/error'
|
|
3
|
-
import
|
|
3
|
+
import NodeFormData from 'form-data'
|
|
4
4
|
import { RequestClient } from '../request/RequestClient'
|
|
5
5
|
import { isNode } from '../utils'
|
|
6
6
|
import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as https from 'https'
|
|
2
2
|
import { ServerType } from '@sasjs/utils/types'
|
|
3
|
-
import
|
|
3
|
+
import NodeFormData from 'form-data'
|
|
4
4
|
import { ErrorResponse } from '../types/errors'
|
|
5
5
|
import { convertToCSV, isRelativePath } from '../utils'
|
|
6
6
|
import { BaseJobExecutor } from './JobExecutor'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import NodeFormData from 'form-data'
|
|
2
2
|
import {
|
|
3
3
|
AuthConfig,
|
|
4
4
|
ExtraResponseAttributes,
|
|
@@ -73,8 +73,10 @@ export class SasjsJobExecutor extends BaseJobExecutor {
|
|
|
73
73
|
/* The NodeFormData object does not set the request header - so, set it */
|
|
74
74
|
const contentType =
|
|
75
75
|
formData instanceof NodeFormData && typeof FormData === 'undefined'
|
|
76
|
-
? `multipart/form-data; boundary=${
|
|
77
|
-
|
|
76
|
+
? `multipart/form-data; boundary=${
|
|
77
|
+
formData.getHeaders()['content-type']
|
|
78
|
+
}`
|
|
79
|
+
: 'multipart/form-data'
|
|
78
80
|
|
|
79
81
|
const requestPromise = new Promise((resolve, reject) => {
|
|
80
82
|
this.requestClient!.post(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import NodeFormData from 'form-data'
|
|
2
2
|
import {
|
|
3
3
|
AuthConfig,
|
|
4
4
|
ExtraResponseAttributes,
|
|
@@ -150,8 +150,10 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|
|
150
150
|
/* The NodeFormData object does not set the request header - so, set it */
|
|
151
151
|
const contentType =
|
|
152
152
|
formData instanceof NodeFormData && typeof FormData === 'undefined'
|
|
153
|
-
? `multipart/form-data; boundary=${
|
|
154
|
-
|
|
153
|
+
? `multipart/form-data; boundary=${
|
|
154
|
+
formData.getHeaders()['content-type']
|
|
155
|
+
}`
|
|
156
|
+
: 'multipart/form-data'
|
|
155
157
|
|
|
156
158
|
const requestPromise = new Promise((resolve, reject) => {
|
|
157
159
|
this.requestClient!.post(
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
AxiosError,
|
|
3
3
|
AxiosInstance,
|
|
4
4
|
AxiosRequestConfig,
|
|
5
|
+
AxiosRequestHeaders,
|
|
5
6
|
AxiosResponse
|
|
6
7
|
} from 'axios'
|
|
7
|
-
import axios from 'axios'
|
|
8
8
|
import * as https from 'https'
|
|
9
9
|
import { CsrfToken } from '..'
|
|
10
10
|
import { isAuthorizeFormRequired, isLogInRequired } from '../auth'
|
|
@@ -160,7 +160,7 @@ export class RequestClient implements HttpClient {
|
|
|
160
160
|
const requestConfig: AxiosRequestConfig = {
|
|
161
161
|
headers,
|
|
162
162
|
responseType: contentType === 'text/plain' ? 'text' : 'json',
|
|
163
|
-
|
|
163
|
+
withXSRFToken: true
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
if (contentType === 'text/plain') {
|
|
@@ -191,6 +191,13 @@ export class RequestClient implements HttpClient {
|
|
|
191
191
|
})
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* @param contentType Newer version of Axios is more strict so if you don't
|
|
196
|
+
* set the contentType to `form data` while sending a FormData object
|
|
197
|
+
* application/json will be used by default, axios won’t treat it as FormData.
|
|
198
|
+
* Instead, it serializes data as JSON—resulting in a payload like
|
|
199
|
+
* {"sometable":{}} and we lose the multipart/form-data formatting.
|
|
200
|
+
*/
|
|
194
201
|
public async post<T>(
|
|
195
202
|
url: string,
|
|
196
203
|
data: any,
|
|
@@ -207,7 +214,7 @@ export class RequestClient implements HttpClient {
|
|
|
207
214
|
return this.httpClient
|
|
208
215
|
.post<T>(url, data, {
|
|
209
216
|
headers,
|
|
210
|
-
|
|
217
|
+
withXSRFToken: true,
|
|
211
218
|
...additionalSettings
|
|
212
219
|
})
|
|
213
220
|
.then((response) => {
|
|
@@ -234,7 +241,7 @@ export class RequestClient implements HttpClient {
|
|
|
234
241
|
}
|
|
235
242
|
|
|
236
243
|
return this.httpClient
|
|
237
|
-
.put<T>(url, data, { headers,
|
|
244
|
+
.put<T>(url, data, { headers, withXSRFToken: true })
|
|
238
245
|
.then((response) => {
|
|
239
246
|
throwIfError(response)
|
|
240
247
|
return this.parseResponse<T>(response)
|
|
@@ -253,7 +260,7 @@ export class RequestClient implements HttpClient {
|
|
|
253
260
|
const headers = this.getHeaders(accessToken, 'application/json')
|
|
254
261
|
|
|
255
262
|
return this.httpClient
|
|
256
|
-
.delete<T>(url, { headers,
|
|
263
|
+
.delete<T>(url, { headers, withXSRFToken: true })
|
|
257
264
|
.then((response) => {
|
|
258
265
|
throwIfError(response)
|
|
259
266
|
return this.parseResponse<T>(response)
|
|
@@ -271,7 +278,7 @@ export class RequestClient implements HttpClient {
|
|
|
271
278
|
const headers = this.getHeaders(accessToken, 'application/json')
|
|
272
279
|
|
|
273
280
|
return this.httpClient
|
|
274
|
-
.patch<T>(url, data, { headers,
|
|
281
|
+
.patch<T>(url, data, { headers, withXSRFToken: true })
|
|
275
282
|
.then((response) => {
|
|
276
283
|
throwIfError(response)
|
|
277
284
|
return this.parseResponse<T>(response)
|
|
@@ -413,95 +420,67 @@ export class RequestClient implements HttpClient {
|
|
|
413
420
|
return bodyLines.join('\n')
|
|
414
421
|
}
|
|
415
422
|
|
|
416
|
-
private
|
|
417
|
-
|
|
418
|
-
|
|
423
|
+
private handleAxiosResponse = (response: AxiosResponse) => {
|
|
424
|
+
const { status, config, request, data } = response
|
|
425
|
+
|
|
426
|
+
const reqHeaders = request?._header ?? 'Not provided\n'
|
|
427
|
+
const rawHeaders = request?.res?.rawHeaders ?? ['Not provided']
|
|
428
|
+
|
|
429
|
+
const resHeaders = this.formatHeaders(rawHeaders)
|
|
430
|
+
const parsedResBody = this.parseInterceptedBody(data)
|
|
431
|
+
|
|
432
|
+
process.logger?.info(`HTTP Request (first 50 lines):
|
|
433
|
+
${reqHeaders}${this.parseInterceptedBody(config.data)}
|
|
434
|
+
|
|
435
|
+
HTTP Response Code: ${this.prettifyString(status)}
|
|
436
|
+
|
|
437
|
+
HTTP Response (first 50 lines):
|
|
438
|
+
${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
|
439
|
+
`)
|
|
440
|
+
|
|
441
|
+
return response
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private handleAxiosError = (error: AxiosError) => {
|
|
419
445
|
// Message indicating absent value.
|
|
420
446
|
const noValueMessage = 'Not provided'
|
|
447
|
+
const { response, request, config } = error
|
|
421
448
|
|
|
422
449
|
// Fallback request object that can be safely used to form request summary.
|
|
423
|
-
type FallbackRequest = { _header?: string; res: { rawHeaders: string[] } }
|
|
424
450
|
// _header is not present in responses with status 1**
|
|
425
451
|
// rawHeaders are not present in responses with status 1**
|
|
426
|
-
let fallbackRequest
|
|
452
|
+
let fallbackRequest = {
|
|
427
453
|
_header: `${noValueMessage}\n`,
|
|
428
454
|
res: { rawHeaders: [noValueMessage] }
|
|
429
455
|
}
|
|
430
456
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
data?: unknown
|
|
437
|
-
}
|
|
438
|
-
let fallbackResponse: FallbackResponse = axiosResponse
|
|
439
|
-
|
|
440
|
-
if (axios.isAxiosError(axiosResponse)) {
|
|
441
|
-
const { response, request, config } = axiosResponse
|
|
442
|
-
|
|
443
|
-
// Try to use axiosResponse.response to form response summary.
|
|
444
|
-
if (response) {
|
|
445
|
-
fallbackResponse = response
|
|
446
|
-
} else {
|
|
447
|
-
// Try to use axiosResponse.request to form request summary.
|
|
448
|
-
if (request) {
|
|
449
|
-
const { _header, _currentRequest } = request
|
|
450
|
-
|
|
451
|
-
// Try to use axiosResponse.request._header to form request summary.
|
|
452
|
-
if (_header) {
|
|
453
|
-
fallbackRequest._header = _header
|
|
454
|
-
}
|
|
455
|
-
// Try to use axiosResponse.request._currentRequest._header to form request summary.
|
|
456
|
-
else if (_currentRequest && _currentRequest._header) {
|
|
457
|
-
fallbackRequest._header = _currentRequest._header
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const { res } = request
|
|
461
|
-
|
|
462
|
-
// Try to use axiosResponse.request.res.rawHeaders to form request summary.
|
|
463
|
-
if (res && res.rawHeaders) {
|
|
464
|
-
fallbackRequest.res.rawHeaders = res.rawHeaders
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Fallback config that can be safely used to form response summary.
|
|
469
|
-
const fallbackConfig = { data: noValueMessage }
|
|
470
|
-
|
|
471
|
-
fallbackResponse = {
|
|
472
|
-
status: noValueMessage,
|
|
473
|
-
request: fallbackRequest,
|
|
474
|
-
config: config || fallbackConfig,
|
|
475
|
-
data: noValueMessage
|
|
476
|
-
}
|
|
457
|
+
if (request) {
|
|
458
|
+
fallbackRequest = {
|
|
459
|
+
_header:
|
|
460
|
+
request._header ?? request._currentRequest?._header ?? noValueMessage,
|
|
461
|
+
res: { rawHeaders: request.res?.rawHeaders ?? [noValueMessage] }
|
|
477
462
|
}
|
|
478
463
|
}
|
|
479
464
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const resHeaders = rawHeaders.reduce(
|
|
488
|
-
(acc: string, value: string, i: number) => {
|
|
489
|
-
if (i % 2 === 0) {
|
|
490
|
-
acc += `${i === 0 ? '' : '\n'}${value}`
|
|
491
|
-
} else {
|
|
492
|
-
acc += `: ${value}`
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return acc
|
|
465
|
+
// Fallback response object that can be safely used to form response summary.
|
|
466
|
+
let fallbackResponse = response || {
|
|
467
|
+
status: noValueMessage,
|
|
468
|
+
request: fallbackRequest,
|
|
469
|
+
config: config || {
|
|
470
|
+
data: noValueMessage,
|
|
471
|
+
headers: {} as AxiosRequestHeaders
|
|
496
472
|
},
|
|
497
|
-
|
|
498
|
-
|
|
473
|
+
data: noValueMessage
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const { status, request: req, data: resData } = fallbackResponse
|
|
477
|
+
const { _header: reqHeaders, res } = req
|
|
499
478
|
|
|
479
|
+
const resHeaders = this.formatHeaders(res.rawHeaders)
|
|
500
480
|
const parsedResBody = this.parseInterceptedBody(resData)
|
|
501
481
|
|
|
502
|
-
// HTTP response summary.
|
|
503
482
|
process.logger?.info(`HTTP Request (first 50 lines):
|
|
504
|
-
${reqHeaders}${this.parseInterceptedBody(
|
|
483
|
+
${reqHeaders}${this.parseInterceptedBody(config?.data)}
|
|
505
484
|
|
|
506
485
|
HTTP Response Code: ${this.prettifyString(status)}
|
|
507
486
|
|
|
@@ -509,7 +488,20 @@ HTTP Response (first 50 lines):
|
|
|
509
488
|
${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
|
510
489
|
`)
|
|
511
490
|
|
|
512
|
-
return
|
|
491
|
+
return error
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Converts an array of strings into a single string with the following format:
|
|
495
|
+
// <headerName>: <headerValue>
|
|
496
|
+
private formatHeaders = (rawHeaders: string[]): string => {
|
|
497
|
+
return rawHeaders.reduce((acc, value, i) => {
|
|
498
|
+
if (i % 2 === 0) {
|
|
499
|
+
acc += `${i === 0 ? '' : '\n'}${value}`
|
|
500
|
+
} else {
|
|
501
|
+
acc += `: ${value}`
|
|
502
|
+
}
|
|
503
|
+
return acc
|
|
504
|
+
}, '')
|
|
513
505
|
}
|
|
514
506
|
|
|
515
507
|
/**
|
|
@@ -529,8 +521,8 @@ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
|
|
529
521
|
* @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**.
|
|
530
522
|
*/
|
|
531
523
|
public enableVerboseMode = (
|
|
532
|
-
successCallBack = this.
|
|
533
|
-
errorCallBack = this.
|
|
524
|
+
successCallBack = this.handleAxiosResponse,
|
|
525
|
+
errorCallBack = this.handleAxiosError
|
|
534
526
|
) => {
|
|
535
527
|
this.httpInterceptor = this.httpClient.interceptors.response.use(
|
|
536
528
|
successCallBack,
|
|
@@ -645,7 +637,7 @@ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
|
|
645
637
|
// Fetching root and creating CSRF cookie
|
|
646
638
|
await this.httpClient
|
|
647
639
|
.get('/', {
|
|
648
|
-
|
|
640
|
+
withXSRFToken: true
|
|
649
641
|
})
|
|
650
642
|
.then((response) => {
|
|
651
643
|
const cookie =
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as https from 'https'
|
|
2
2
|
import { AxiosRequestConfig } from 'axios'
|
|
3
|
-
import
|
|
3
|
+
import { wrapper } from 'axios-cookiejar-support'
|
|
4
4
|
import * as tough from 'tough-cookie'
|
|
5
5
|
import { prefixMessage } from '@sasjs/utils/error'
|
|
6
6
|
import { RequestClient, throwIfError } from './RequestClient'
|
|
@@ -17,8 +17,8 @@ export class Sas9RequestClient extends RequestClient {
|
|
|
17
17
|
this.httpClient.defaults.validateStatus = (status) =>
|
|
18
18
|
status >= 200 && status < 303
|
|
19
19
|
|
|
20
|
-
if (
|
|
21
|
-
|
|
20
|
+
if (wrapper) {
|
|
21
|
+
wrapper(this.httpClient)
|
|
22
22
|
this.httpClient.defaults.jar = new tough.CookieJar()
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -50,7 +50,7 @@ export class Sas9RequestClient extends RequestClient {
|
|
|
50
50
|
const requestConfig: AxiosRequestConfig = {
|
|
51
51
|
headers,
|
|
52
52
|
responseType: contentType === 'text/plain' ? 'text' : 'json',
|
|
53
|
-
|
|
53
|
+
withXSRFToken: true
|
|
54
54
|
}
|
|
55
55
|
if (contentType === 'text/plain') {
|
|
56
56
|
requestConfig.transformResponse = undefined
|
|
@@ -103,7 +103,7 @@ export class Sas9RequestClient extends RequestClient {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
return this.httpClient
|
|
106
|
-
.post<T>(url, data, { headers,
|
|
106
|
+
.post<T>(url, data, { headers, withXSRFToken: true })
|
|
107
107
|
.then(async (response) => {
|
|
108
108
|
if (response.status === 302) {
|
|
109
109
|
return await this.get(
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import express = require('express')
|
|
2
|
+
import cors from 'cors'
|
|
2
3
|
|
|
3
4
|
export const app = express()
|
|
4
5
|
|
|
6
|
+
app.use(
|
|
7
|
+
cors({
|
|
8
|
+
origin: 'http://localhost', // Allow requests only from this origin
|
|
9
|
+
credentials: true // Allow credentials (cookies, auth headers, etc.)
|
|
10
|
+
})
|
|
11
|
+
)
|
|
12
|
+
|
|
5
13
|
export const mockedAuthResponse = {
|
|
6
14
|
access_token: 'access_token',
|
|
7
15
|
token_type: 'bearer',
|
|
@@ -12,11 +20,11 @@ export const mockedAuthResponse = {
|
|
|
12
20
|
jti: 'jti'
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
app.get('/',
|
|
23
|
+
app.get('/', (req: any, res: any) => {
|
|
16
24
|
res.send('Hello World')
|
|
17
25
|
})
|
|
18
26
|
|
|
19
|
-
app.post('/SASLogon/oauth/token',
|
|
27
|
+
app.post('/SASLogon/oauth/token', (req: any, res: any) => {
|
|
20
28
|
let valid = true
|
|
21
29
|
|
|
22
30
|
// capture the encoded form data
|
package/src/types/WriteStream.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { WriteStream as FsWriteStream } from 'fs'
|
|
2
|
+
|
|
3
|
+
export interface WriteStream extends FsWriteStream {
|
|
4
|
+
write(
|
|
5
|
+
chunk: any,
|
|
6
|
+
encoding?: BufferEncoding | ((error: Error | null | undefined) => void),
|
|
7
|
+
cb?: (error: Error | null | undefined) => void
|
|
8
|
+
): boolean
|
|
9
|
+
path: string | Buffer
|
|
4
10
|
}
|