@api-client/core 0.6.2 → 0.6.5
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/build/browser.d.ts +3 -2
- package/build/browser.js +2 -1
- package/build/browser.js.map +1 -1
- package/build/index.d.ts +3 -2
- package/build/index.js +2 -1
- package/build/index.js.map +1 -1
- package/build/src/Platform.d.ts +12 -0
- package/build/src/Platform.js +13 -0
- package/build/src/Platform.js.map +1 -0
- package/build/src/authorization/OAuth2Authorization.d.ts +1 -1
- package/build/src/authorization/OidcAuthorization.d.ts +1 -0
- package/build/src/authorization/OidcAuthorization.js +3 -0
- package/build/src/authorization/OidcAuthorization.js.map +1 -1
- package/build/src/authorization/Utils.js +1 -1
- package/build/src/authorization/Utils.js.map +1 -1
- package/build/src/authorization/lib/SecurityProcessor.d.ts +33 -0
- package/build/src/authorization/lib/SecurityProcessor.js +118 -0
- package/build/src/authorization/lib/SecurityProcessor.js.map +1 -0
- package/build/src/authorization/lib/Utils.d.ts +41 -0
- package/build/src/authorization/lib/Utils.js +85 -0
- package/build/src/authorization/lib/Utils.js.map +1 -0
- package/build/src/lib/transformers/PayloadSerializer.d.ts +85 -19
- package/build/src/lib/transformers/PayloadSerializer.js +172 -64
- package/build/src/lib/transformers/PayloadSerializer.js.map +1 -1
- package/build/src/models/RequestUiMeta.d.ts +2 -2
- package/build/src/models/SerializablePayload.js +2 -1
- package/build/src/models/SerializablePayload.js.map +1 -1
- package/build/src/models/legacy/Normalizer.js +1 -1
- package/build/src/models/legacy/Normalizer.js.map +1 -1
- package/build/src/runtime/http-engine/CoreEngine.js +3 -6
- package/build/src/runtime/http-engine/CoreEngine.js.map +1 -1
- package/build/src/runtime/http-engine/FormData.d.ts +36 -2
- package/build/src/runtime/http-engine/FormData.js +156 -55
- package/build/src/runtime/http-engine/FormData.js.map +1 -1
- package/build/src/runtime/http-engine/PayloadSupport.d.ts +4 -5
- package/build/src/runtime/http-engine/PayloadSupport.js +19 -12
- package/build/src/runtime/http-engine/PayloadSupport.js.map +1 -1
- package/package.json +3 -5
- package/src/Platform.ts +12 -0
- package/src/authorization/OAuth2Authorization.ts +1 -1
- package/src/authorization/OidcAuthorization.ts +4 -0
- package/src/authorization/Utils.ts +1 -1
- package/src/authorization/lib/SecurityProcessor.ts +141 -0
- package/src/authorization/lib/Utils.ts +89 -0
- package/src/lib/transformers/PayloadSerializer.ts +217 -73
- package/src/models/RequestUiMeta.ts +2 -2
- package/src/models/SerializablePayload.ts +2 -1
- package/src/models/legacy/Normalizer.ts +1 -1
- package/src/runtime/http-engine/CoreEngine.ts +3 -6
- package/src/runtime/http-engine/FormData.ts +184 -63
- package/src/runtime/http-engine/PayloadSupport.ts +23 -15
- package/build/src/lib/transformers/Utils.d.ts +0 -7
- package/build/src/lib/transformers/Utils.js +0 -19
- package/build/src/lib/transformers/Utils.js.map +0 -1
- package/src/lib/transformers/Utils.ts +0 -18
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { IBasicAuthorization, IBearerAuthorization, IOAuth2Authorization, IOidcAuthorization, IOidcTokenInfo } from "../../models/Authorization.js";
|
|
2
|
+
import { IRequestAuthorization, RequestAuthorization } from "../../models/RequestAuthorization.js";
|
|
3
|
+
import { IHttpRequest } from "../../models/HttpRequest.js";
|
|
4
|
+
import { Headers } from '../../lib/headers/Headers.js';
|
|
5
|
+
import { UrlProcessor } from "../../lib/parsers/UrlProcessor.js";
|
|
6
|
+
import { UrlEncoder } from "../../lib/parsers/UrlEncoder.js";
|
|
7
|
+
import { hasBuffer } from '../../Platform.js';
|
|
8
|
+
import {
|
|
9
|
+
normalizeType,
|
|
10
|
+
METHOD_BASIC,
|
|
11
|
+
METHOD_BEARER,
|
|
12
|
+
METHOD_OAUTH2,
|
|
13
|
+
METHOD_OIDC,
|
|
14
|
+
} from "./Utils.js";
|
|
15
|
+
|
|
16
|
+
export interface IAuthApplyOptions {
|
|
17
|
+
/**
|
|
18
|
+
* When set it won't change the originating authorization objects.
|
|
19
|
+
* By default it sets the authorization's `enabled` property to `false` after applying the
|
|
20
|
+
* value to the request.
|
|
21
|
+
*/
|
|
22
|
+
immutable?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class SecurityProcessor {
|
|
26
|
+
/**
|
|
27
|
+
* Applies authorization configuration to the request object.
|
|
28
|
+
*/
|
|
29
|
+
static applyAuthorization(request: IHttpRequest, authorization: (IRequestAuthorization | RequestAuthorization)[], opts: IAuthApplyOptions = {}): void {
|
|
30
|
+
if (!Array.isArray(authorization) || !authorization.length) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const auth of authorization) {
|
|
35
|
+
if (!auth.enabled || !auth.config) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch (normalizeType(auth.type)) {
|
|
40
|
+
case METHOD_BASIC:
|
|
41
|
+
SecurityProcessor.applyBasicAuth(request, auth.config as IBasicAuthorization);
|
|
42
|
+
if (!opts.immutable) {
|
|
43
|
+
auth.enabled = false;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
case METHOD_OAUTH2:
|
|
47
|
+
SecurityProcessor.applyOAuth2(request, auth.config as IOAuth2Authorization);
|
|
48
|
+
if (!opts.immutable) {
|
|
49
|
+
auth.enabled = false;
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case METHOD_OIDC:
|
|
53
|
+
SecurityProcessor.applyOpenId(request, auth.config as IOidcAuthorization);
|
|
54
|
+
if (!opts.immutable) {
|
|
55
|
+
auth.enabled = false;
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
case METHOD_BEARER:
|
|
59
|
+
SecurityProcessor.applyBearer(request, auth.config as IBearerAuthorization);
|
|
60
|
+
if (!opts.immutable) {
|
|
61
|
+
auth.enabled = false;
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Injects basic auth header into the request headers.
|
|
71
|
+
*/
|
|
72
|
+
static applyBasicAuth(request: IHttpRequest, config: IBasicAuthorization): void {
|
|
73
|
+
const { username, password } = config;
|
|
74
|
+
if (!username) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const hash = `${username}:${password || ''}`;
|
|
78
|
+
const value = hasBuffer ? Buffer.from(hash).toString('base64') : btoa(hash);
|
|
79
|
+
|
|
80
|
+
const headers = new Headers(request.headers || '');
|
|
81
|
+
headers.append('authorization', `Basic ${value}`);
|
|
82
|
+
request.headers = headers.toString();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Injects oauth 2 auth header into the request headers.
|
|
87
|
+
*/
|
|
88
|
+
static applyOAuth2(request: IHttpRequest, config: IOAuth2Authorization): void {
|
|
89
|
+
const { accessToken, tokenType = 'Bearer', deliveryMethod = 'header', deliveryName = 'authorization' } = config;
|
|
90
|
+
if (!accessToken) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const value = `${tokenType} ${accessToken}`;
|
|
94
|
+
if (deliveryMethod === 'header') {
|
|
95
|
+
const headers = new Headers(request.headers || '');
|
|
96
|
+
headers.append(deliveryName, value);
|
|
97
|
+
request.headers = headers.toString();
|
|
98
|
+
} else if (deliveryMethod === 'query') {
|
|
99
|
+
const { url } = request;
|
|
100
|
+
try {
|
|
101
|
+
const parser = new UrlProcessor(url);
|
|
102
|
+
parser.search.append(UrlEncoder.encodeQueryString(deliveryName, true), UrlEncoder.encodeQueryString(value, true));
|
|
103
|
+
request.url = parser.toString();
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// ...
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Injects OpenID Connect auth header into the request headers.
|
|
112
|
+
*/
|
|
113
|
+
static applyOpenId(request: IHttpRequest, config: IOidcAuthorization): void {
|
|
114
|
+
const { accessToken, tokens, tokenInUse } = config;
|
|
115
|
+
if (accessToken) {
|
|
116
|
+
SecurityProcessor.applyOAuth2(request, config);
|
|
117
|
+
} else if (Array.isArray(tokens) && typeof tokenInUse === 'number') {
|
|
118
|
+
const data = tokens[tokenInUse] as IOidcTokenInfo;
|
|
119
|
+
if (data && data.accessToken) {
|
|
120
|
+
const copy = { ...config };
|
|
121
|
+
copy.accessToken = data.accessToken;
|
|
122
|
+
SecurityProcessor.applyOAuth2(request, copy);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Injects bearer auth header into the request headers.
|
|
129
|
+
*/
|
|
130
|
+
static applyBearer(request: IHttpRequest, config: IBearerAuthorization): void {
|
|
131
|
+
const { token } = config;
|
|
132
|
+
if (!token) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const value = `Bearer ${token}`;
|
|
136
|
+
|
|
137
|
+
const headers = new Headers(request.headers || '');
|
|
138
|
+
headers.append('authorization', value);
|
|
139
|
+
request.headers = headers.toString();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes type name to a string identifier.
|
|
3
|
+
* It casts input to a string and lowercase it.
|
|
4
|
+
* @param type Type value
|
|
5
|
+
* @return Normalized value.
|
|
6
|
+
*/
|
|
7
|
+
export const normalizeType = (type?: string): string => String(type).toLowerCase();
|
|
8
|
+
|
|
9
|
+
export const METHOD_BASIC = "basic";
|
|
10
|
+
export const METHOD_BEARER = "bearer";
|
|
11
|
+
export const METHOD_NTLM = "ntlm";
|
|
12
|
+
export const METHOD_DIGEST = "digest";
|
|
13
|
+
export const METHOD_OAUTH2 = "oauth 2";
|
|
14
|
+
export const METHOD_OIDC = "open id";
|
|
15
|
+
export const METHOD_CC = 'client certificate';
|
|
16
|
+
export const CUSTOM_CREDENTIALS = "Custom credentials";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param value The value to validate
|
|
20
|
+
* @returns True if the redirect URI can be considered valid.
|
|
21
|
+
*/
|
|
22
|
+
export function validateRedirectUri(value: unknown): boolean {
|
|
23
|
+
let result = true;
|
|
24
|
+
if (!value || typeof value !== 'string') {
|
|
25
|
+
result = false;
|
|
26
|
+
}
|
|
27
|
+
// the redirect URI can have any value, especially for installed apps which
|
|
28
|
+
// may use custom schemes. We do very basic sanity check for any script content
|
|
29
|
+
// validation to make sure we are not passing any script.
|
|
30
|
+
if (result && String(value).startsWith('javascript:')) {
|
|
31
|
+
result = false;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generates client nonce for Digest authorization.
|
|
38
|
+
*
|
|
39
|
+
* @return Generated client nonce.
|
|
40
|
+
*/
|
|
41
|
+
export function generateCnonce(): string {
|
|
42
|
+
const characters = 'abcdef0123456789';
|
|
43
|
+
let token = '';
|
|
44
|
+
for (let i = 0; i < 16; i++) {
|
|
45
|
+
const randNum = Math.round(Math.random() * characters.length);
|
|
46
|
+
token += characters.substring(randNum, 1);
|
|
47
|
+
}
|
|
48
|
+
return token;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generates `state` parameter for the OAuth2 call.
|
|
53
|
+
*
|
|
54
|
+
* @return Generated state string.
|
|
55
|
+
*/
|
|
56
|
+
export function generateState(): string {
|
|
57
|
+
let text = '';
|
|
58
|
+
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
59
|
+
for (let i = 0; i < 6; i++) {
|
|
60
|
+
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
61
|
+
}
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* When defined and the `url` is a relative path staring with `/` then it
|
|
67
|
+
* adds base URI to the path and returns concatenated value.
|
|
68
|
+
*
|
|
69
|
+
* @param url The URL to process
|
|
70
|
+
* @param baseUri Base URI to use.
|
|
71
|
+
* @returns Final URL value.
|
|
72
|
+
*/
|
|
73
|
+
export function readUrlValue(url?: string, baseUri?: string): string {
|
|
74
|
+
if (!url) {
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
const result = String(url);
|
|
78
|
+
if (!baseUri) {
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
if (result[0] === '/') {
|
|
82
|
+
let uri = baseUri;
|
|
83
|
+
if (uri[uri.length - 1] === '/') {
|
|
84
|
+
uri = uri.substring(0, uri.length - 1);
|
|
85
|
+
}
|
|
86
|
+
return `${uri}${result}`;
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hasBlob, hasBuffer, hasFormData } from '../../Platform.js';
|
|
2
2
|
|
|
3
3
|
export type PayloadTypes = 'string' | 'file' | 'blob' | 'buffer' | 'arraybuffer' | 'formdata' | 'x-www-form-urlencoded';
|
|
4
4
|
export type DeserializedPayload = string | Blob | File | FormData | Buffer | ArrayBuffer | undefined;
|
|
@@ -6,30 +6,55 @@ export const SupportedPayloadTypes: PayloadTypes[] = ['string', 'file', 'blob',
|
|
|
6
6
|
|
|
7
7
|
export interface IMultipartBody {
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Whether the parameter is enabled. Default to true.
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
enabled?: boolean;
|
|
12
12
|
/**
|
|
13
13
|
* The name of the filed
|
|
14
14
|
*/
|
|
15
15
|
name: string;
|
|
16
16
|
/**
|
|
17
|
-
* Converted value
|
|
17
|
+
* Converted value.
|
|
18
|
+
* When the part value was a string this is a string.
|
|
19
|
+
* When the previous value was a Blob or a Buffer, this will be a serialized payload.
|
|
20
|
+
*/
|
|
21
|
+
value: string | ISafePayload;
|
|
22
|
+
/**
|
|
23
|
+
* Aside from the `value` which is used to process the Payload, the purpose of this field to to keep
|
|
24
|
+
* the entered by the user string value for the "blob" item. This is primarily handled by the UI
|
|
25
|
+
* and ignored by the HTTP Engine.
|
|
26
|
+
*/
|
|
27
|
+
blobText?: string;
|
|
28
|
+
/**
|
|
29
|
+
* When `true` this entry represent a file part
|
|
30
|
+
* @deprecated This is only used for the compatibility with ARC. This information is encoded in the `value`.
|
|
18
31
|
*/
|
|
19
|
-
|
|
32
|
+
isFile?: boolean;
|
|
20
33
|
/**
|
|
21
34
|
* A content type entered by the user to the text part of the text part input.
|
|
22
35
|
* This can only be set when `isFile` is false.
|
|
36
|
+
* @deprecated This is only used for the compatibility with ARC. This information is encoded in the `value`.
|
|
23
37
|
*/
|
|
24
38
|
type?: string;
|
|
25
39
|
/**
|
|
26
40
|
* The original file name used with the part
|
|
41
|
+
* @deprecated This is only used for the compatibility with ARC. This information is encoded in the `value`.
|
|
27
42
|
*/
|
|
28
43
|
fileName?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface IBlobMeta {
|
|
29
47
|
/**
|
|
30
|
-
*
|
|
48
|
+
* The blob's mime type.
|
|
31
49
|
*/
|
|
32
|
-
|
|
50
|
+
mime: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface IFileMeta extends IBlobMeta {
|
|
54
|
+
/**
|
|
55
|
+
* The file name.
|
|
56
|
+
*/
|
|
57
|
+
name: string;
|
|
33
58
|
}
|
|
34
59
|
|
|
35
60
|
/**
|
|
@@ -43,13 +68,17 @@ export interface ISafePayload {
|
|
|
43
68
|
* The type od the originating payload object.
|
|
44
69
|
*/
|
|
45
70
|
type: PayloadTypes;
|
|
71
|
+
/**
|
|
72
|
+
* The payload contents. The data type depends on the `type`.
|
|
73
|
+
*/
|
|
46
74
|
data: string | number[] | IMultipartBody[];
|
|
47
75
|
/**
|
|
48
76
|
* Optionally the original mime type of the payload.
|
|
49
77
|
* This is used with files.
|
|
50
78
|
*/
|
|
51
|
-
|
|
79
|
+
meta?: IBlobMeta | IFileMeta;
|
|
52
80
|
}
|
|
81
|
+
|
|
53
82
|
/**
|
|
54
83
|
* The request payload. When not a string then it has to go through a
|
|
55
84
|
* transformation from a store safe object to the original data object.
|
|
@@ -59,10 +88,6 @@ export interface ISafePayload {
|
|
|
59
88
|
*/
|
|
60
89
|
export type Payload = string | ISafePayload;
|
|
61
90
|
|
|
62
|
-
export const hasFormData: boolean = typeof FormData === 'function';
|
|
63
|
-
export const hasBlob: boolean = typeof Blob === 'function';
|
|
64
|
-
export const hasBuffer: boolean = typeof Buffer === 'function';
|
|
65
|
-
|
|
66
91
|
export class PayloadSerializer {
|
|
67
92
|
/**
|
|
68
93
|
* Checked whether the passed payload can be safely stored in the data store.
|
|
@@ -84,6 +109,17 @@ export class PayloadSerializer {
|
|
|
84
109
|
return false;
|
|
85
110
|
}
|
|
86
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Tests whether the given input should be processed by the `serialize()`.
|
|
114
|
+
*/
|
|
115
|
+
static needsSerialization(input: unknown): boolean {
|
|
116
|
+
const typedSerialized = input as ISafePayload;
|
|
117
|
+
if (typedSerialized.type && SupportedPayloadTypes.includes(typedSerialized.type)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
87
123
|
/**
|
|
88
124
|
* Transforms the payload into a data store safe object.
|
|
89
125
|
*/
|
|
@@ -100,6 +136,10 @@ export class PayloadSerializer {
|
|
|
100
136
|
// if (typeof payload === 'string') {
|
|
101
137
|
// return payload;
|
|
102
138
|
// }
|
|
139
|
+
|
|
140
|
+
if (hasBlob && payload instanceof File) {
|
|
141
|
+
return PayloadSerializer.stringifyFile(payload);
|
|
142
|
+
}
|
|
103
143
|
if (hasBlob && payload instanceof Blob) {
|
|
104
144
|
return PayloadSerializer.stringifyBlob(payload);
|
|
105
145
|
}
|
|
@@ -111,7 +151,7 @@ export class PayloadSerializer {
|
|
|
111
151
|
}
|
|
112
152
|
if (hasFormData && payload instanceof FormData) {
|
|
113
153
|
try {
|
|
114
|
-
const result = await PayloadSerializer.stringifyFormData(
|
|
154
|
+
const result = await PayloadSerializer.stringifyFormData(payload);
|
|
115
155
|
return result;
|
|
116
156
|
} catch (e: unknown) {
|
|
117
157
|
console.warn(`Unable to transform FormData: ${(e as Error).message}`);
|
|
@@ -121,18 +161,38 @@ export class PayloadSerializer {
|
|
|
121
161
|
}
|
|
122
162
|
|
|
123
163
|
/**
|
|
124
|
-
*
|
|
164
|
+
* Stringifies a file object.
|
|
165
|
+
*/
|
|
166
|
+
static async stringifyFile(file: File): Promise<ISafePayload> {
|
|
167
|
+
const buffer = await file.arrayBuffer();
|
|
168
|
+
const view = new Uint8Array(buffer);
|
|
169
|
+
const meta: IFileMeta = {
|
|
170
|
+
mime: file.type,
|
|
171
|
+
name: file.name,
|
|
172
|
+
};
|
|
173
|
+
const result: ISafePayload = {
|
|
174
|
+
type: 'file',
|
|
175
|
+
data: [...view],
|
|
176
|
+
meta,
|
|
177
|
+
};
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Stringifies a blob object.
|
|
125
183
|
*
|
|
126
|
-
* @param blob
|
|
127
|
-
* @return Promise resolved to a base64 string data from the file.
|
|
184
|
+
* @param blob Blob object to be translated to string
|
|
128
185
|
*/
|
|
129
186
|
static async stringifyBlob(blob: Blob): Promise<ISafePayload> {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
187
|
+
const buffer = await blob.arrayBuffer();
|
|
188
|
+
const view = new Uint8Array(buffer);
|
|
189
|
+
const meta: IBlobMeta = {
|
|
190
|
+
mime: blob.type,
|
|
191
|
+
};
|
|
132
192
|
const result: ISafePayload = {
|
|
133
193
|
type: 'blob',
|
|
134
|
-
data,
|
|
135
|
-
|
|
194
|
+
data: [...view],
|
|
195
|
+
meta,
|
|
136
196
|
};
|
|
137
197
|
return result;
|
|
138
198
|
}
|
|
@@ -160,14 +220,11 @@ export class PayloadSerializer {
|
|
|
160
220
|
* @returns The buffer metadata or undefined if the passed argument is not an ArrayBuffer.
|
|
161
221
|
*/
|
|
162
222
|
static stringifyArrayBuffer(payload: ArrayBuffer): ISafePayload | undefined {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
return undefined;
|
|
223
|
+
const view = new Uint8Array(payload);
|
|
224
|
+
return {
|
|
225
|
+
type: 'arraybuffer',
|
|
226
|
+
data: Array.from(view),
|
|
227
|
+
};
|
|
171
228
|
}
|
|
172
229
|
|
|
173
230
|
/**
|
|
@@ -176,9 +233,11 @@ export class PayloadSerializer {
|
|
|
176
233
|
* @param payload A `FormData` object
|
|
177
234
|
* @return A promise resolved to a datastore safe entries.
|
|
178
235
|
*/
|
|
179
|
-
static async stringifyFormData(payload:
|
|
236
|
+
static async stringifyFormData(payload: FormData): Promise<ISafePayload> {
|
|
237
|
+
// TS apparently doesn't know that FormData is iterable.
|
|
238
|
+
const iterable = (payload as unknown) as Iterable<(string | File)[]>;
|
|
180
239
|
const promises: Promise<IMultipartBody>[] = [];
|
|
181
|
-
for (const part of
|
|
240
|
+
for (const part of iterable) {
|
|
182
241
|
promises.push(PayloadSerializer.serializeFormDataEntry(part[0] as string, part[1]));
|
|
183
242
|
}
|
|
184
243
|
const items = await Promise.all(promises);
|
|
@@ -195,33 +254,27 @@ export class PayloadSerializer {
|
|
|
195
254
|
* @param file The part value
|
|
196
255
|
* @returns Transformed FormData part to a datastore safe entry.
|
|
197
256
|
*/
|
|
198
|
-
static async serializeFormDataEntry(name: string, file: string | File): Promise<IMultipartBody> {
|
|
257
|
+
static async serializeFormDataEntry(name: string, file: string | File | Blob): Promise<IMultipartBody> {
|
|
199
258
|
if (typeof file === 'string') {
|
|
200
|
-
// when adding an item to the FormData object without 3rd parameter of the append function
|
|
201
|
-
// then the value is a string.
|
|
202
259
|
return {
|
|
203
|
-
isFile: false,
|
|
204
260
|
name,
|
|
205
261
|
value: file,
|
|
206
262
|
enabled: true,
|
|
207
263
|
};
|
|
208
264
|
}
|
|
209
|
-
|
|
210
|
-
|
|
265
|
+
let value: ISafePayload;
|
|
266
|
+
// API Client adds the "blob" when adding a text value with a mime type.
|
|
267
|
+
// This is recognized by the UI to restore the entry as the text and not a file.
|
|
268
|
+
if (file instanceof File && file.name !== 'blob') {
|
|
269
|
+
value = await PayloadSerializer.stringifyFile(file);
|
|
270
|
+
} else {
|
|
271
|
+
value = await PayloadSerializer.stringifyBlob(file);
|
|
272
|
+
}
|
|
211
273
|
const part: IMultipartBody = {
|
|
212
|
-
isFile: false,
|
|
213
274
|
name,
|
|
214
275
|
value,
|
|
215
276
|
enabled: true,
|
|
216
277
|
};
|
|
217
|
-
if (file.name === 'blob') {
|
|
218
|
-
// API Client adds the "blob" filename when the content type is set on the editor.
|
|
219
|
-
// otherwise it wouldn't be possible to set the content type value.
|
|
220
|
-
part.type = file.type;
|
|
221
|
-
} else {
|
|
222
|
-
part.isFile = true;
|
|
223
|
-
part.fileName = file.name;
|
|
224
|
-
}
|
|
225
278
|
return part;
|
|
226
279
|
}
|
|
227
280
|
|
|
@@ -240,8 +293,8 @@ export class PayloadSerializer {
|
|
|
240
293
|
// We mostly gonna return a Buffer here.
|
|
241
294
|
switch (payload.type) {
|
|
242
295
|
case 'string': return payload.data as string;
|
|
243
|
-
case 'file':
|
|
244
|
-
case 'blob': return PayloadSerializer.deserializeBlobBuffer(payload
|
|
296
|
+
case 'file': return PayloadSerializer.deserializeFileBuffer(payload);
|
|
297
|
+
case 'blob': return PayloadSerializer.deserializeBlobBuffer(payload);
|
|
245
298
|
case 'buffer': return PayloadSerializer.deserializeBuffer(payload.data as number[]);
|
|
246
299
|
case 'arraybuffer': return PayloadSerializer.deserializeArrayBufferBuffer(payload.data as number[]);
|
|
247
300
|
case 'formdata': return undefined;
|
|
@@ -250,9 +303,9 @@ export class PayloadSerializer {
|
|
|
250
303
|
}
|
|
251
304
|
switch (payload.type) {
|
|
252
305
|
case 'string': return payload.data as string;
|
|
253
|
-
case 'file':
|
|
254
|
-
case 'blob': return PayloadSerializer.deserializeBlob(payload
|
|
255
|
-
case 'buffer': return PayloadSerializer.
|
|
306
|
+
case 'file': return PayloadSerializer.deserializeFile(payload);
|
|
307
|
+
case 'blob': return PayloadSerializer.deserializeBlob(payload);
|
|
308
|
+
case 'buffer': return PayloadSerializer.deserializeArrayBuffer(payload.data as number[]);
|
|
256
309
|
case 'arraybuffer': return PayloadSerializer.deserializeArrayBuffer(payload.data as number[]);
|
|
257
310
|
case 'formdata': return PayloadSerializer.deserializeFormData(payload.data as IMultipartBody[]);
|
|
258
311
|
default: return undefined;
|
|
@@ -260,12 +313,47 @@ export class PayloadSerializer {
|
|
|
260
313
|
}
|
|
261
314
|
|
|
262
315
|
/**
|
|
263
|
-
*
|
|
316
|
+
* Deserializes previously serialized file object.
|
|
317
|
+
*
|
|
318
|
+
* @param payload The serialized payload with a file.
|
|
319
|
+
*/
|
|
320
|
+
static deserializeFile(payload: ISafePayload): File {
|
|
321
|
+
const data = payload.data as number[];
|
|
322
|
+
const meta = payload.meta as IFileMeta;
|
|
323
|
+
const { mime, name } = meta;
|
|
324
|
+
const { buffer } = new Uint8Array(data);
|
|
325
|
+
return new File([buffer], name, {
|
|
326
|
+
type: mime,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Deserializes previously serialized blob object.
|
|
332
|
+
*
|
|
333
|
+
* In previous versions of ARC the data was a string as data URL. In API client this is a buffer.
|
|
264
334
|
*
|
|
335
|
+
* @param payload The serialized payload.
|
|
336
|
+
* @return Restored blob value
|
|
337
|
+
*/
|
|
338
|
+
static deserializeBlob(payload: ISafePayload): Blob | undefined {
|
|
339
|
+
if (typeof payload.data === 'string') {
|
|
340
|
+
return this.deserializeBlobLegacy(payload.data);
|
|
341
|
+
}
|
|
342
|
+
const data = payload.data as number[];
|
|
343
|
+
const meta = payload.meta as IBlobMeta;
|
|
344
|
+
const { mime } = meta;
|
|
345
|
+
const { buffer } = new Uint8Array(data);
|
|
346
|
+
return new Blob([buffer], { type: mime });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* The old implementation of the blob deserializer.
|
|
351
|
+
*
|
|
352
|
+
* @deprecated
|
|
265
353
|
* @param dataUrl Data url from blob value.
|
|
266
354
|
* @return Restored blob value
|
|
267
355
|
*/
|
|
268
|
-
static
|
|
356
|
+
static deserializeBlobLegacy(dataUrl: string): Blob | undefined {
|
|
269
357
|
const arr = dataUrl.split(',');
|
|
270
358
|
const matchedMime = arr[0].match(/:(.*?);/);
|
|
271
359
|
if (!matchedMime) {
|
|
@@ -282,12 +370,40 @@ export class PayloadSerializer {
|
|
|
282
370
|
}
|
|
283
371
|
|
|
284
372
|
/**
|
|
285
|
-
* Converts
|
|
373
|
+
* Converts previously serialized File to a Buffer.
|
|
374
|
+
*
|
|
375
|
+
* @param payload The serialized payload.
|
|
376
|
+
* @return Restored File value as Buffer
|
|
377
|
+
*/
|
|
378
|
+
static deserializeFileBuffer(payload: ISafePayload): Buffer {
|
|
379
|
+
const data = payload.data as number[];
|
|
380
|
+
const ab = this.deserializeArrayBuffer(data);
|
|
381
|
+
return Buffer.from(ab);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Converts data-url string to buffer
|
|
286
386
|
*
|
|
387
|
+
* @param payload The serialized payload.
|
|
388
|
+
* @return Restored blob value
|
|
389
|
+
*/
|
|
390
|
+
static deserializeBlobBuffer(payload: ISafePayload): Buffer {
|
|
391
|
+
if (typeof payload.data === 'string') {
|
|
392
|
+
return this.deserializeBlobBufferLegacy(payload.data);
|
|
393
|
+
}
|
|
394
|
+
const data = payload.data as number[];
|
|
395
|
+
const ab = this.deserializeArrayBuffer(data);
|
|
396
|
+
return Buffer.from(ab);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Converts data-url string to buffer
|
|
401
|
+
*
|
|
402
|
+
* @deprecated
|
|
287
403
|
* @param dataUrl Data url from blob value.
|
|
288
404
|
* @return Restored blob value
|
|
289
405
|
*/
|
|
290
|
-
static
|
|
406
|
+
static deserializeBlobBufferLegacy(dataUrl: string): Buffer {
|
|
291
407
|
const arr = dataUrl.split(',');
|
|
292
408
|
const value = arr[1];
|
|
293
409
|
return Buffer.from(value, 'base64url');
|
|
@@ -334,26 +450,54 @@ export class PayloadSerializer {
|
|
|
334
450
|
if (!Array.isArray(parts) || !parts.length) {
|
|
335
451
|
return fd;
|
|
336
452
|
}
|
|
337
|
-
parts.forEach(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
453
|
+
parts.forEach(part => this.deserializeFormDataPart(fd, part));
|
|
454
|
+
return fd;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private static deserializeFormDataPart(form: FormData, part: IMultipartBody): void {
|
|
458
|
+
if (part.enabled === false) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
// the compatibility with old ARC.
|
|
462
|
+
if (typeof part.isFile === 'boolean') {
|
|
463
|
+
this.deserializeFormDataLegacy(form, part);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const { name, value } = part;
|
|
467
|
+
if (typeof value === 'string') {
|
|
468
|
+
form.append(name, value);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (value.type === 'string') {
|
|
472
|
+
form.append(name, value.data as string);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (value.type === 'file') {
|
|
476
|
+
const file = this.deserializeFile(value);
|
|
477
|
+
form.append(name, file);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const blob = this.deserializeBlob(value) as Blob;
|
|
481
|
+
form.append(name, blob);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* @deprecated This is only for compatibility with ARC.
|
|
486
|
+
*/
|
|
487
|
+
private static deserializeFormDataLegacy(form: FormData, part: IMultipartBody): void {
|
|
488
|
+
let blob;
|
|
489
|
+
if (part.isFile) {
|
|
490
|
+
blob = PayloadSerializer.deserializeBlobLegacy(part.value as string);
|
|
491
|
+
if (blob) {
|
|
492
|
+
form.append(part.name, blob, part.fileName);
|
|
341
493
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
fd.append(name, blob, fileName);
|
|
347
|
-
}
|
|
348
|
-
} else if (type) {
|
|
349
|
-
blob = PayloadSerializer.deserializeBlob(value);
|
|
350
|
-
if (blob) {
|
|
351
|
-
fd.append(name, blob, 'blob');
|
|
352
|
-
}
|
|
353
|
-
} else {
|
|
354
|
-
fd.append(name, value);
|
|
494
|
+
} else if (part.type) {
|
|
495
|
+
blob = PayloadSerializer.deserializeBlobLegacy(part.value as string);
|
|
496
|
+
if (blob) {
|
|
497
|
+
form.append(part.name, blob, 'blob');
|
|
355
498
|
}
|
|
356
|
-
}
|
|
357
|
-
|
|
499
|
+
} else {
|
|
500
|
+
form.append(part.name, part.value as string);
|
|
501
|
+
}
|
|
358
502
|
}
|
|
359
503
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IProperty, Property } from './Property.js';
|
|
2
|
-
import { IMultipartBody } from '../lib/transformers/PayloadSerializer.js';
|
|
2
|
+
import { IMultipartBody, ISafePayload } from '../lib/transformers/PayloadSerializer.js';
|
|
3
3
|
import { RequestUiMeta as LegacyRequestUiMeta } from './legacy/request/ArcRequest.js';
|
|
4
4
|
|
|
5
5
|
export const Kind = 'Core#RequestUiMeta';
|
|
@@ -70,7 +70,7 @@ export interface IBodyMetaModel {
|
|
|
70
70
|
/**
|
|
71
71
|
* Generated view model.
|
|
72
72
|
*/
|
|
73
|
-
viewModel: (IProperty | IMultipartBody | IRawBody)[];
|
|
73
|
+
viewModel: (IProperty | ISafePayload | IMultipartBody | IRawBody)[];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { PayloadSerializer, Payload, DeserializedPayload
|
|
1
|
+
import { PayloadSerializer, Payload, DeserializedPayload } from '../lib/transformers/PayloadSerializer.js';
|
|
2
|
+
import { hasBuffer } from '../Platform.js';
|
|
2
3
|
|
|
3
4
|
export class SerializablePayload {
|
|
4
5
|
/**
|