@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.
Files changed (55) hide show
  1. package/build/browser.d.ts +3 -2
  2. package/build/browser.js +2 -1
  3. package/build/browser.js.map +1 -1
  4. package/build/index.d.ts +3 -2
  5. package/build/index.js +2 -1
  6. package/build/index.js.map +1 -1
  7. package/build/src/Platform.d.ts +12 -0
  8. package/build/src/Platform.js +13 -0
  9. package/build/src/Platform.js.map +1 -0
  10. package/build/src/authorization/OAuth2Authorization.d.ts +1 -1
  11. package/build/src/authorization/OidcAuthorization.d.ts +1 -0
  12. package/build/src/authorization/OidcAuthorization.js +3 -0
  13. package/build/src/authorization/OidcAuthorization.js.map +1 -1
  14. package/build/src/authorization/Utils.js +1 -1
  15. package/build/src/authorization/Utils.js.map +1 -1
  16. package/build/src/authorization/lib/SecurityProcessor.d.ts +33 -0
  17. package/build/src/authorization/lib/SecurityProcessor.js +118 -0
  18. package/build/src/authorization/lib/SecurityProcessor.js.map +1 -0
  19. package/build/src/authorization/lib/Utils.d.ts +41 -0
  20. package/build/src/authorization/lib/Utils.js +85 -0
  21. package/build/src/authorization/lib/Utils.js.map +1 -0
  22. package/build/src/lib/transformers/PayloadSerializer.d.ts +85 -19
  23. package/build/src/lib/transformers/PayloadSerializer.js +172 -64
  24. package/build/src/lib/transformers/PayloadSerializer.js.map +1 -1
  25. package/build/src/models/RequestUiMeta.d.ts +2 -2
  26. package/build/src/models/SerializablePayload.js +2 -1
  27. package/build/src/models/SerializablePayload.js.map +1 -1
  28. package/build/src/models/legacy/Normalizer.js +1 -1
  29. package/build/src/models/legacy/Normalizer.js.map +1 -1
  30. package/build/src/runtime/http-engine/CoreEngine.js +3 -6
  31. package/build/src/runtime/http-engine/CoreEngine.js.map +1 -1
  32. package/build/src/runtime/http-engine/FormData.d.ts +36 -2
  33. package/build/src/runtime/http-engine/FormData.js +156 -55
  34. package/build/src/runtime/http-engine/FormData.js.map +1 -1
  35. package/build/src/runtime/http-engine/PayloadSupport.d.ts +4 -5
  36. package/build/src/runtime/http-engine/PayloadSupport.js +19 -12
  37. package/build/src/runtime/http-engine/PayloadSupport.js.map +1 -1
  38. package/package.json +3 -5
  39. package/src/Platform.ts +12 -0
  40. package/src/authorization/OAuth2Authorization.ts +1 -1
  41. package/src/authorization/OidcAuthorization.ts +4 -0
  42. package/src/authorization/Utils.ts +1 -1
  43. package/src/authorization/lib/SecurityProcessor.ts +141 -0
  44. package/src/authorization/lib/Utils.ts +89 -0
  45. package/src/lib/transformers/PayloadSerializer.ts +217 -73
  46. package/src/models/RequestUiMeta.ts +2 -2
  47. package/src/models/SerializablePayload.ts +2 -1
  48. package/src/models/legacy/Normalizer.ts +1 -1
  49. package/src/runtime/http-engine/CoreEngine.ts +3 -6
  50. package/src/runtime/http-engine/FormData.ts +184 -63
  51. package/src/runtime/http-engine/PayloadSupport.ts +23 -15
  52. package/build/src/lib/transformers/Utils.d.ts +0 -7
  53. package/build/src/lib/transformers/Utils.js +0 -19
  54. package/build/src/lib/transformers/Utils.js.map +0 -1
  55. 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 { blobToDataUrl } from './Utils.js';
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
- * When true a this entry represent a file part
9
+ * Whether the parameter is enabled. Default to true.
10
10
  */
11
- isFile: boolean;
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
- value: string;
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
- * Whether the parameter is enabled. Default to true.
48
+ * The blob's mime type.
31
49
  */
32
- enabled?: boolean;
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
- mime?: string;
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((payload as unknown) as Iterable<(string | File)[]>);
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
- * Converts blob data to base64 string.
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 File or blob object to be translated to string
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 typedFile = blob as File;
131
- const data = await blobToDataUrl(blob);
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
- mime: typedFile.type,
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
- if (payload.byteLength) {
164
- const view = new Uint8Array(payload);
165
- return {
166
- type: 'arraybuffer',
167
- data: Array.from(view),
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: Iterable<(string | File)[]>): Promise<ISafePayload> {
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 payload) {
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
- const value = await blobToDataUrl(file);
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.data as string);
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.data as string);
255
- case 'buffer': return PayloadSerializer.deserializeBuffer(payload.data as number[]);
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
- * Converts data-url string to blob
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 deserializeBlob(dataUrl: string): Blob | undefined {
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 data-url string to blob
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 deserializeBlobBuffer(dataUrl: string): Buffer {
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((part) => {
338
- const { isFile, name, value, type, fileName, enabled } = part;
339
- if (enabled === false) {
340
- return;
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
- let blob;
343
- if (isFile) {
344
- blob = PayloadSerializer.deserializeBlob(value);
345
- if (blob) {
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
- return fd;
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, hasBuffer } from '../lib/transformers/PayloadSerializer.js';
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
  /**