@twin.org/web 0.0.1-next.1
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/LICENSE +201 -0
- package/README.md +21 -0
- package/dist/cjs/index.cjs +1056 -0
- package/dist/esm/index.mjs +1046 -0
- package/dist/types/errors/fetchError.d.ts +22 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/models/IFetchOptions.d.ts +30 -0
- package/dist/types/models/IHttpHeaders.d.ts +6 -0
- package/dist/types/models/IJwk.d.ts +62 -0
- package/dist/types/models/IJwtHeader.d.ts +22 -0
- package/dist/types/models/IJwtPayload.d.ts +37 -0
- package/dist/types/models/headerTypes.d.ts +41 -0
- package/dist/types/models/httpMethod.d.ts +18 -0
- package/dist/types/models/httpStatusCode.d.ts +257 -0
- package/dist/types/models/jwtAlgorithms.d.ts +17 -0
- package/dist/types/models/mimeTypes.d.ts +93 -0
- package/dist/types/utils/fetchHelper.d.ts +52 -0
- package/dist/types/utils/jwt.d.ts +85 -0
- package/dist/types/utils/mimeTypeHelper.d.ts +17 -0
- package/docs/changelog.md +5 -0
- package/docs/examples.md +1 -0
- package/docs/reference/classes/FetchError.md +379 -0
- package/docs/reference/classes/FetchHelper.md +185 -0
- package/docs/reference/classes/Jwt.md +313 -0
- package/docs/reference/classes/MimeTypeHelper.md +53 -0
- package/docs/reference/index.md +32 -0
- package/docs/reference/interfaces/IFetchOptions.md +53 -0
- package/docs/reference/interfaces/IHttpHeaders.md +7 -0
- package/docs/reference/interfaces/IJwk.md +111 -0
- package/docs/reference/interfaces/IJwtHeader.md +31 -0
- package/docs/reference/interfaces/IJwtPayload.md +63 -0
- package/docs/reference/type-aliases/HeaderTypes.md +5 -0
- package/docs/reference/type-aliases/HttpMethod.md +5 -0
- package/docs/reference/type-aliases/HttpStatusCode.md +5 -0
- package/docs/reference/type-aliases/JwtAlgorithms.md +5 -0
- package/docs/reference/type-aliases/MimeTypes.md +5 -0
- package/docs/reference/variables/HeaderTypes.md +55 -0
- package/docs/reference/variables/HttpMethod.md +43 -0
- package/docs/reference/variables/HttpStatusCode.md +379 -0
- package/docs/reference/variables/JwtAlgorithms.md +19 -0
- package/docs/reference/variables/MimeTypes.md +133 -0
- package/locales/en.json +18 -0
- package/package.json +65 -0
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
import { BaseError, StringHelper, Guards, Is, AsyncCache, ObjectHelper, Converter, GeneralError, ArrayHelper } from '@twin.org/core';
|
|
2
|
+
import { HmacSha256, Ed25519 } from '@twin.org/crypto';
|
|
3
|
+
|
|
4
|
+
// Copyright 2024 IOTA Stiftung.
|
|
5
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
6
|
+
/**
|
|
7
|
+
* Class to represent errors from fetch.
|
|
8
|
+
*/
|
|
9
|
+
class FetchError extends BaseError {
|
|
10
|
+
/**
|
|
11
|
+
* Runtime name for the class.
|
|
12
|
+
*/
|
|
13
|
+
static CLASS_NAME = "FetchError";
|
|
14
|
+
/**
|
|
15
|
+
* Create a new instance of FetchError.
|
|
16
|
+
* @param source The source of the error.
|
|
17
|
+
* @param message The message as a code.
|
|
18
|
+
* @param httpStatus The http status code.
|
|
19
|
+
* @param properties Any additional information for the error.
|
|
20
|
+
* @param inner The inner error if we have wrapped another error.
|
|
21
|
+
*/
|
|
22
|
+
constructor(source, message, httpStatus, properties, inner) {
|
|
23
|
+
super(FetchError.CLASS_NAME, source, message, {
|
|
24
|
+
httpStatus,
|
|
25
|
+
...properties
|
|
26
|
+
}, inner);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Copyright 2024 IOTA Stiftung.
|
|
31
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
32
|
+
/**
|
|
33
|
+
* Common http header types.
|
|
34
|
+
*/
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
36
|
+
const HeaderTypes = {
|
|
37
|
+
/**
|
|
38
|
+
* Content Type.
|
|
39
|
+
*/
|
|
40
|
+
ContentType: "Content-Type",
|
|
41
|
+
/**
|
|
42
|
+
* Content Length.
|
|
43
|
+
*/
|
|
44
|
+
ContentLength: "Content-Length",
|
|
45
|
+
/**
|
|
46
|
+
* Content Disposition.
|
|
47
|
+
*/
|
|
48
|
+
ContentDisposition: "Content-Disposition",
|
|
49
|
+
/**
|
|
50
|
+
* Accept.
|
|
51
|
+
*/
|
|
52
|
+
Accept: "Accept",
|
|
53
|
+
/**
|
|
54
|
+
* Authorization.
|
|
55
|
+
*/
|
|
56
|
+
Authorization: "Authorization",
|
|
57
|
+
/**
|
|
58
|
+
* Cookie.
|
|
59
|
+
*/
|
|
60
|
+
Cookie: "Cookie",
|
|
61
|
+
/**
|
|
62
|
+
* Set Cookie.
|
|
63
|
+
*/
|
|
64
|
+
SetCookie: "Set-Cookie",
|
|
65
|
+
/**
|
|
66
|
+
* Location
|
|
67
|
+
*/
|
|
68
|
+
Location: "Location"
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Copyright 2024 IOTA Stiftung.
|
|
72
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
73
|
+
/**
|
|
74
|
+
* The names of the HTTP Methods.
|
|
75
|
+
*/
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
77
|
+
const HttpMethod = {
|
|
78
|
+
GET: "GET",
|
|
79
|
+
POST: "POST",
|
|
80
|
+
PUT: "PUT",
|
|
81
|
+
PATCH: "PATCH",
|
|
82
|
+
DELETE: "DELETE",
|
|
83
|
+
OPTIONS: "OPTIONS",
|
|
84
|
+
HEAD: "HEAD",
|
|
85
|
+
CONNECT: "CONNECT",
|
|
86
|
+
TRACE: "TRACE"
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Copyright 2024 IOTA Stiftung.
|
|
90
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
91
|
+
/**
|
|
92
|
+
* Standard HTTP status codes.
|
|
93
|
+
*/
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
95
|
+
const HttpStatusCode = {
|
|
96
|
+
/**
|
|
97
|
+
* Continue status code.
|
|
98
|
+
*/
|
|
99
|
+
continue: 100,
|
|
100
|
+
/**
|
|
101
|
+
* Switching Protocols status code.
|
|
102
|
+
*/
|
|
103
|
+
switchingProtocols: 101,
|
|
104
|
+
/**
|
|
105
|
+
* Processing status code.
|
|
106
|
+
*/
|
|
107
|
+
processing: 102,
|
|
108
|
+
/**
|
|
109
|
+
* Early Hints status code.
|
|
110
|
+
*/
|
|
111
|
+
earlyHints: 103,
|
|
112
|
+
/**
|
|
113
|
+
* OK status code.
|
|
114
|
+
*/
|
|
115
|
+
ok: 200,
|
|
116
|
+
/**
|
|
117
|
+
* Created status code.
|
|
118
|
+
*/
|
|
119
|
+
created: 201,
|
|
120
|
+
/**
|
|
121
|
+
* Accepted status code.
|
|
122
|
+
*/
|
|
123
|
+
accepted: 202,
|
|
124
|
+
/**
|
|
125
|
+
* Non-Authoritative Information status code.
|
|
126
|
+
*/
|
|
127
|
+
nonAuthoritativeInformation: 203,
|
|
128
|
+
/**
|
|
129
|
+
* No Content status code.
|
|
130
|
+
*/
|
|
131
|
+
noContent: 204,
|
|
132
|
+
/**
|
|
133
|
+
* Reset Content status code.
|
|
134
|
+
*/
|
|
135
|
+
resetContent: 205,
|
|
136
|
+
/**
|
|
137
|
+
* Partial Content status code.
|
|
138
|
+
*/
|
|
139
|
+
partialContent: 206,
|
|
140
|
+
/**
|
|
141
|
+
* Multi-Status status code.
|
|
142
|
+
*/
|
|
143
|
+
multiStatus: 207,
|
|
144
|
+
/**
|
|
145
|
+
* Already Reported status code.
|
|
146
|
+
*/
|
|
147
|
+
alreadyReported: 208,
|
|
148
|
+
/**
|
|
149
|
+
* IM Used status code.
|
|
150
|
+
*/
|
|
151
|
+
imUsed: 226,
|
|
152
|
+
/**
|
|
153
|
+
* Multiple Choices status code.
|
|
154
|
+
*/
|
|
155
|
+
multipleChoices: 300,
|
|
156
|
+
/**
|
|
157
|
+
* Moved Permanently status code.
|
|
158
|
+
*/
|
|
159
|
+
movedPermanently: 301,
|
|
160
|
+
/**
|
|
161
|
+
* Found status code.
|
|
162
|
+
*/
|
|
163
|
+
found: 302,
|
|
164
|
+
/**
|
|
165
|
+
* See Other status code.
|
|
166
|
+
*/
|
|
167
|
+
seeOther: 303,
|
|
168
|
+
/**
|
|
169
|
+
* Not Modified status code.
|
|
170
|
+
*/
|
|
171
|
+
notModified: 304,
|
|
172
|
+
/**
|
|
173
|
+
* Use Proxy status code.
|
|
174
|
+
*/
|
|
175
|
+
useProxy: 305,
|
|
176
|
+
/**
|
|
177
|
+
* Temporary Redirect status code.
|
|
178
|
+
*/
|
|
179
|
+
temporaryRedirect: 307,
|
|
180
|
+
/**
|
|
181
|
+
* Permanent Redirect status code.
|
|
182
|
+
*/
|
|
183
|
+
permanentRedirect: 308,
|
|
184
|
+
/**
|
|
185
|
+
* Bad Request status code.
|
|
186
|
+
*/
|
|
187
|
+
badRequest: 400,
|
|
188
|
+
/**
|
|
189
|
+
* Unauthorized status code.
|
|
190
|
+
*/
|
|
191
|
+
unauthorized: 401,
|
|
192
|
+
/**
|
|
193
|
+
* Payment Required status code.
|
|
194
|
+
*/
|
|
195
|
+
paymentRequired: 402,
|
|
196
|
+
/**
|
|
197
|
+
* Forbidden status code.
|
|
198
|
+
*/
|
|
199
|
+
forbidden: 403,
|
|
200
|
+
/**
|
|
201
|
+
* Not Found status code.
|
|
202
|
+
*/
|
|
203
|
+
notFound: 404,
|
|
204
|
+
/**
|
|
205
|
+
* Method Not Allowed status code.
|
|
206
|
+
*/
|
|
207
|
+
methodNotAllowed: 405,
|
|
208
|
+
/**
|
|
209
|
+
* Not Acceptable status code.
|
|
210
|
+
*/
|
|
211
|
+
notAcceptable: 406,
|
|
212
|
+
/**
|
|
213
|
+
* Proxy Authentication Required status code.
|
|
214
|
+
*/
|
|
215
|
+
proxyAuthenticationRequired: 407,
|
|
216
|
+
/**
|
|
217
|
+
* Request Timeout status code.
|
|
218
|
+
*/
|
|
219
|
+
requestTimeout: 408,
|
|
220
|
+
/**
|
|
221
|
+
* Conflict status code.
|
|
222
|
+
*/
|
|
223
|
+
conflict: 409,
|
|
224
|
+
/**
|
|
225
|
+
* Gone status code.
|
|
226
|
+
*/
|
|
227
|
+
gone: 410,
|
|
228
|
+
/**
|
|
229
|
+
* Length Required status code.
|
|
230
|
+
*/
|
|
231
|
+
lengthRequired: 411,
|
|
232
|
+
/**
|
|
233
|
+
* Precondition Failed status code.
|
|
234
|
+
*/
|
|
235
|
+
preconditionFailed: 412,
|
|
236
|
+
/**
|
|
237
|
+
* Payload Too Large status code.
|
|
238
|
+
*/
|
|
239
|
+
payloadTooLarge: 413,
|
|
240
|
+
/**
|
|
241
|
+
* URI Too Long status code.
|
|
242
|
+
*/
|
|
243
|
+
uriTooLong: 414,
|
|
244
|
+
/**
|
|
245
|
+
* Unsupported Media Type status code.
|
|
246
|
+
*/
|
|
247
|
+
unsupportedMediaType: 415,
|
|
248
|
+
/**
|
|
249
|
+
* Range Not Satisfiable status code.
|
|
250
|
+
*/
|
|
251
|
+
rangeNotSatisfiable: 416,
|
|
252
|
+
/**
|
|
253
|
+
* Expectation Failed status code.
|
|
254
|
+
*/
|
|
255
|
+
expectationFailed: 417,
|
|
256
|
+
/**
|
|
257
|
+
* I'm a Teapot status code.
|
|
258
|
+
*/
|
|
259
|
+
imATeapot: 418,
|
|
260
|
+
/**
|
|
261
|
+
* Misdirected Request status code.
|
|
262
|
+
*/
|
|
263
|
+
misdirectedRequest: 421,
|
|
264
|
+
/**
|
|
265
|
+
* Unprocessable Entity status code.
|
|
266
|
+
*/
|
|
267
|
+
unprocessableEntity: 422,
|
|
268
|
+
/**
|
|
269
|
+
* Locked status code.
|
|
270
|
+
*/
|
|
271
|
+
locked: 423,
|
|
272
|
+
/**
|
|
273
|
+
* Failed Dependency status code.
|
|
274
|
+
*/
|
|
275
|
+
failedDependency: 424,
|
|
276
|
+
/**
|
|
277
|
+
* Too Early status code.
|
|
278
|
+
*/
|
|
279
|
+
tooEarly: 425,
|
|
280
|
+
/**
|
|
281
|
+
* Upgrade Required status code.
|
|
282
|
+
*/
|
|
283
|
+
upgradeRequired: 426,
|
|
284
|
+
/**
|
|
285
|
+
* Precondition Required status code.
|
|
286
|
+
*/
|
|
287
|
+
preconditionRequired: 428,
|
|
288
|
+
/**
|
|
289
|
+
* Too Many Requests status code.
|
|
290
|
+
*/
|
|
291
|
+
tooManyRequests: 429,
|
|
292
|
+
/**
|
|
293
|
+
* Request Header Fields Too Large status code.
|
|
294
|
+
*/
|
|
295
|
+
requestHeaderFieldsTooLarge: 431,
|
|
296
|
+
/**
|
|
297
|
+
* Unavailable For Legal Reasons status code.
|
|
298
|
+
*/
|
|
299
|
+
unavailableForLegalReasons: 451,
|
|
300
|
+
/**
|
|
301
|
+
* Internal Server Error status code.
|
|
302
|
+
*/
|
|
303
|
+
internalServerError: 500,
|
|
304
|
+
/**
|
|
305
|
+
* Not Implemented status code.
|
|
306
|
+
*/
|
|
307
|
+
notImplemented: 501,
|
|
308
|
+
/**
|
|
309
|
+
* Bad Gateway status code.
|
|
310
|
+
*/
|
|
311
|
+
badGateway: 502,
|
|
312
|
+
/**
|
|
313
|
+
* Service Unavailable status code.
|
|
314
|
+
*/
|
|
315
|
+
serviceUnavailable: 503,
|
|
316
|
+
/**
|
|
317
|
+
* Gateway Timeout status code.
|
|
318
|
+
*/
|
|
319
|
+
gatewayTimeout: 504,
|
|
320
|
+
/**
|
|
321
|
+
* HTTP Version Not Supported status code.
|
|
322
|
+
*/
|
|
323
|
+
httpVersionNotSupported: 505,
|
|
324
|
+
/**
|
|
325
|
+
* Variant Also Negotiates status code.
|
|
326
|
+
*/
|
|
327
|
+
variantAlsoNegotiates: 506,
|
|
328
|
+
/**
|
|
329
|
+
* Insufficient Storage status code.
|
|
330
|
+
*/
|
|
331
|
+
insufficientStorage: 507,
|
|
332
|
+
/**
|
|
333
|
+
* Loop Detected status code.
|
|
334
|
+
*/
|
|
335
|
+
loopDetected: 508,
|
|
336
|
+
/**
|
|
337
|
+
* Not Extended status code.
|
|
338
|
+
*/
|
|
339
|
+
notExtended: 510,
|
|
340
|
+
/**
|
|
341
|
+
* Network Authentication Required status code.
|
|
342
|
+
*/
|
|
343
|
+
networkAuthenticationRequired: 511
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// Copyright 2024 IOTA Stiftung.
|
|
347
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
348
|
+
/**
|
|
349
|
+
* The cryptographic algorithms supported for JSON Web Tokens and JSON Web Keys.
|
|
350
|
+
*/
|
|
351
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
352
|
+
const JwtAlgorithms = {
|
|
353
|
+
/**
|
|
354
|
+
* HMAC using SHA-256.
|
|
355
|
+
*/
|
|
356
|
+
HS256: "HS256",
|
|
357
|
+
/**
|
|
358
|
+
* EdDSA using Ed25519.
|
|
359
|
+
*/
|
|
360
|
+
EdDSA: "EdDSA"
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Copyright 2024 IOTA Stiftung.
|
|
364
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
365
|
+
/**
|
|
366
|
+
* Common mime types.
|
|
367
|
+
*/
|
|
368
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
369
|
+
const MimeTypes = {
|
|
370
|
+
/**
|
|
371
|
+
* Plaint Text - text/plain
|
|
372
|
+
*/
|
|
373
|
+
PlainText: "text/plain",
|
|
374
|
+
/**
|
|
375
|
+
* HTML - text/html
|
|
376
|
+
*/
|
|
377
|
+
Html: "text/html",
|
|
378
|
+
/**
|
|
379
|
+
* Javascript - text/javascript
|
|
380
|
+
*/
|
|
381
|
+
Javascript: "text/javascript",
|
|
382
|
+
/**
|
|
383
|
+
* JSON - application/json
|
|
384
|
+
*/
|
|
385
|
+
Json: "application/json",
|
|
386
|
+
/**
|
|
387
|
+
* JSON-LD - application/ld+json
|
|
388
|
+
*/
|
|
389
|
+
JsonLd: "application/ld+json",
|
|
390
|
+
/**
|
|
391
|
+
* XML - application/xml
|
|
392
|
+
*/
|
|
393
|
+
Xml: "application/xml",
|
|
394
|
+
/**
|
|
395
|
+
* Application Octet Stream, arbitrary binary - application/octet-stream
|
|
396
|
+
*/
|
|
397
|
+
OctetStream: "application/octet-stream",
|
|
398
|
+
/**
|
|
399
|
+
* Application GZIP - application/gzip
|
|
400
|
+
*/
|
|
401
|
+
Gzip: "application/gzip",
|
|
402
|
+
/**
|
|
403
|
+
* Application BZIP2 - application/x-bzip2
|
|
404
|
+
*/
|
|
405
|
+
Bzip2: "application/x-bzip2",
|
|
406
|
+
/**
|
|
407
|
+
* Application ZIP - application/zip
|
|
408
|
+
*/
|
|
409
|
+
Zip: "application/zip",
|
|
410
|
+
/**
|
|
411
|
+
* Application PDF - application/pdf
|
|
412
|
+
*/
|
|
413
|
+
Pdf: "application/pdf",
|
|
414
|
+
/**
|
|
415
|
+
* Image GIF - image/gif
|
|
416
|
+
*/
|
|
417
|
+
Gif: "image/gif",
|
|
418
|
+
/**
|
|
419
|
+
* Image BMP - image/bmp
|
|
420
|
+
*/
|
|
421
|
+
Bmp: "image/bmp",
|
|
422
|
+
/**
|
|
423
|
+
* Image JPEG - image/jpeg
|
|
424
|
+
*/
|
|
425
|
+
Jpeg: "image/jpeg",
|
|
426
|
+
/**
|
|
427
|
+
* Image PNG - image/png
|
|
428
|
+
*/
|
|
429
|
+
Png: "image/png",
|
|
430
|
+
/**
|
|
431
|
+
* Image Tiff - image/tiff
|
|
432
|
+
*/
|
|
433
|
+
Tiff: "image/tiff",
|
|
434
|
+
/**
|
|
435
|
+
* Image SVG - image/svg+xml
|
|
436
|
+
*/
|
|
437
|
+
Svg: "image/svg+xml",
|
|
438
|
+
/**
|
|
439
|
+
* Image WEBP - image/webp
|
|
440
|
+
*/
|
|
441
|
+
WebP: "image/webp",
|
|
442
|
+
/**
|
|
443
|
+
* Video MP4 - video/mp4
|
|
444
|
+
*/
|
|
445
|
+
Mp4: "video/mp4",
|
|
446
|
+
/**
|
|
447
|
+
* Audio/Video MPEG - video/mpeg
|
|
448
|
+
*/
|
|
449
|
+
Mpeg: "video/mpeg",
|
|
450
|
+
/**
|
|
451
|
+
* Video WEBM - video/webm
|
|
452
|
+
*/
|
|
453
|
+
Webm: "video/webm"
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Copyright 2024 IOTA Stiftung.
|
|
457
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
458
|
+
/**
|
|
459
|
+
* Class to helper with fetch operations.
|
|
460
|
+
*/
|
|
461
|
+
class FetchHelper {
|
|
462
|
+
/**
|
|
463
|
+
* Runtime name for the class.
|
|
464
|
+
* @internal
|
|
465
|
+
*/
|
|
466
|
+
static _CLASS_NAME = "FetchHelper";
|
|
467
|
+
/**
|
|
468
|
+
* Prefix to use for cache entries.
|
|
469
|
+
* @internal
|
|
470
|
+
*/
|
|
471
|
+
static _CACHE_PREFIX = "fetch_";
|
|
472
|
+
/**
|
|
473
|
+
* Runtime name for the class.
|
|
474
|
+
* @internal
|
|
475
|
+
*/
|
|
476
|
+
static _CLASS_NAME_CAMEL_CASE = StringHelper.camelCase("FetchHelper");
|
|
477
|
+
/**
|
|
478
|
+
* Perform a fetch request.
|
|
479
|
+
* @param source The source for the request.
|
|
480
|
+
* @param url The url for the request.
|
|
481
|
+
* @param method The http method.
|
|
482
|
+
* @param body Request to send to the endpoint.
|
|
483
|
+
* @param options Options for sending the requests.
|
|
484
|
+
* @returns The response.
|
|
485
|
+
*/
|
|
486
|
+
static async fetch(source, url, method, body, options) {
|
|
487
|
+
Guards.string(FetchHelper._CLASS_NAME, "source", source);
|
|
488
|
+
Guards.string(FetchHelper._CLASS_NAME, "url", url);
|
|
489
|
+
Guards.arrayOneOf(FetchHelper._CLASS_NAME, "method", method, Object.values(HttpMethod));
|
|
490
|
+
if (!Is.undefined(body) && !Is.uint8Array(body)) {
|
|
491
|
+
Guards.string(FetchHelper._CLASS_NAME, "body", body);
|
|
492
|
+
}
|
|
493
|
+
if (!Is.undefined(options)) {
|
|
494
|
+
Guards.object(FetchHelper._CLASS_NAME, "options", options);
|
|
495
|
+
if (!Is.undefined(options.headers)) {
|
|
496
|
+
Guards.object(FetchHelper._CLASS_NAME, "options.headers", options.headers);
|
|
497
|
+
}
|
|
498
|
+
if (!Is.undefined(options.timeoutMs)) {
|
|
499
|
+
Guards.integer(FetchHelper._CLASS_NAME, "options.timeoutMs", options.timeoutMs);
|
|
500
|
+
}
|
|
501
|
+
if (!Is.undefined(options.includeCredentials)) {
|
|
502
|
+
Guards.boolean(FetchHelper._CLASS_NAME, "options.includeCredentials", options.includeCredentials);
|
|
503
|
+
}
|
|
504
|
+
if (!Is.undefined(options.retryCount)) {
|
|
505
|
+
Guards.integer(FetchHelper._CLASS_NAME, "options.retryCount", options.retryCount);
|
|
506
|
+
}
|
|
507
|
+
if (!Is.undefined(options.retryDelayMs)) {
|
|
508
|
+
Guards.integer(FetchHelper._CLASS_NAME, "options.retryDelayMs", options.retryDelayMs);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
let controller;
|
|
512
|
+
let timerId;
|
|
513
|
+
const retryCount = options?.retryCount ?? 1;
|
|
514
|
+
const baseDelayMilliseconds = options?.retryDelayMs ?? 3000;
|
|
515
|
+
let lastError;
|
|
516
|
+
let attempt;
|
|
517
|
+
for (attempt = 0; attempt < retryCount; attempt++) {
|
|
518
|
+
if (attempt > 0) {
|
|
519
|
+
const exponentialBackoffDelay = baseDelayMilliseconds * Math.pow(2, attempt - 1);
|
|
520
|
+
await new Promise(resolve => globalThis.setTimeout(resolve, exponentialBackoffDelay));
|
|
521
|
+
}
|
|
522
|
+
if (options?.timeoutMs !== undefined) {
|
|
523
|
+
controller = new AbortController();
|
|
524
|
+
timerId = globalThis.setTimeout(() => {
|
|
525
|
+
if (controller) {
|
|
526
|
+
controller.abort();
|
|
527
|
+
}
|
|
528
|
+
}, options?.timeoutMs);
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
const requestOptions = {
|
|
532
|
+
method,
|
|
533
|
+
headers: options?.headers,
|
|
534
|
+
body: method === HttpMethod.POST || method === HttpMethod.PUT ? body : undefined,
|
|
535
|
+
signal: controller ? controller.signal : undefined
|
|
536
|
+
};
|
|
537
|
+
if (Is.boolean(options?.includeCredentials)) {
|
|
538
|
+
requestOptions.credentials = "include";
|
|
539
|
+
}
|
|
540
|
+
const response = await fetch(url, requestOptions);
|
|
541
|
+
if (!response.ok && retryCount > 1) {
|
|
542
|
+
lastError = new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.general`, response.status ?? HttpStatusCode.internalServerError, {
|
|
543
|
+
url,
|
|
544
|
+
statusText: response.statusText
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
return response;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
catch (err) {
|
|
552
|
+
const isErr = Is.object(err);
|
|
553
|
+
if (isErr && Is.stringValue(err.message) && err.message.includes("Failed to fetch")) {
|
|
554
|
+
lastError = new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.connectivity`, HttpStatusCode.serviceUnavailable, {
|
|
555
|
+
url
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
const isAbort = isErr && err.name === "AbortError";
|
|
560
|
+
const props = { url };
|
|
561
|
+
let httpStatus = HttpStatusCode.internalServerError;
|
|
562
|
+
if (isAbort) {
|
|
563
|
+
httpStatus = HttpStatusCode.requestTimeout;
|
|
564
|
+
}
|
|
565
|
+
else if (isErr && "httpStatus" in err) {
|
|
566
|
+
httpStatus = err.httpStatus;
|
|
567
|
+
}
|
|
568
|
+
if (isErr && "statusText" in err) {
|
|
569
|
+
props.statusText = err.statusText;
|
|
570
|
+
}
|
|
571
|
+
lastError = new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.${isAbort ? "timeout" : "general"}`, httpStatus, props);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
finally {
|
|
575
|
+
if (timerId) {
|
|
576
|
+
globalThis.clearTimeout(timerId);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (retryCount > 1 && attempt === retryCount) {
|
|
581
|
+
// False positive as FetchError is derived from Error
|
|
582
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
583
|
+
throw new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.retryLimitExceeded`, HttpStatusCode.internalServerError, { url }, lastError);
|
|
584
|
+
}
|
|
585
|
+
throw lastError;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Perform a request in json format.
|
|
589
|
+
* @param source The source for the request.
|
|
590
|
+
* @param url The url for the request.
|
|
591
|
+
* @param method The http method.
|
|
592
|
+
* @param requestData Request to send to the endpoint.
|
|
593
|
+
* @param options Options for sending the requests.
|
|
594
|
+
* @returns The response.
|
|
595
|
+
*/
|
|
596
|
+
static async fetchJson(source, url, method, requestData, options) {
|
|
597
|
+
if (Is.integer(options?.cacheTtlMs) && options.cacheTtlMs >= 0) {
|
|
598
|
+
// The cache option is set, so call the same method again but without
|
|
599
|
+
// the cache option to get the result and cache it.
|
|
600
|
+
const cacheResponse = AsyncCache.exec(`${FetchHelper._CACHE_PREFIX}${url}`, options.cacheTtlMs, async () => FetchHelper.fetchJson(source, url, method, requestData, ObjectHelper.omit(options, ["cacheTtlMs"])));
|
|
601
|
+
// If the return value is a promise return it, otherwise continue
|
|
602
|
+
// with the regular processing.
|
|
603
|
+
if (Is.promise(cacheResponse)) {
|
|
604
|
+
return cacheResponse;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
options ??= {};
|
|
608
|
+
options.headers ??= {};
|
|
609
|
+
if (Is.undefined(options.headers[HeaderTypes.ContentType]) &&
|
|
610
|
+
(method === HttpMethod.POST || method === HttpMethod.PUT || method === HttpMethod.PATCH)) {
|
|
611
|
+
options.headers[HeaderTypes.ContentType] = MimeTypes.Json;
|
|
612
|
+
}
|
|
613
|
+
const response = await FetchHelper.fetch(source, url, method, requestData ? JSON.stringify(requestData) : undefined, options);
|
|
614
|
+
if (response.ok) {
|
|
615
|
+
if (response.status === HttpStatusCode.noContent) {
|
|
616
|
+
return {};
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
return (await response.json());
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
// False positive as FetchError is derived from Error
|
|
623
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
624
|
+
throw new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.decodingJSON`, HttpStatusCode.badRequest, { url }, err);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const errorResponseData = await response.json();
|
|
628
|
+
// False positive as FetchError is derived from Error
|
|
629
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
630
|
+
throw new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.failureStatusText`, response.status, {
|
|
631
|
+
statusText: response.statusText,
|
|
632
|
+
url
|
|
633
|
+
}, errorResponseData);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Perform a request for binary data.
|
|
637
|
+
* @param source The source for the request.
|
|
638
|
+
* @param url The url for the request.
|
|
639
|
+
* @param method The http method.
|
|
640
|
+
* @param requestData Request to send to the endpoint.
|
|
641
|
+
* @param options Options for sending the requests.
|
|
642
|
+
* @returns The response.
|
|
643
|
+
*/
|
|
644
|
+
static async fetchBinary(source, url, method, requestData, options) {
|
|
645
|
+
if (Is.integer(options?.cacheTtlMs) && options.cacheTtlMs >= 0) {
|
|
646
|
+
// The cache option is set, so call the same method again but without
|
|
647
|
+
// the cache option to get the result and cache it.
|
|
648
|
+
const cacheResponse = AsyncCache.exec(`${FetchHelper._CACHE_PREFIX}${url}`, options.cacheTtlMs * 1000, async () => FetchHelper.fetchBinary(source, url, method, requestData, ObjectHelper.omit(options, ["cacheTtlMs"])));
|
|
649
|
+
// If the return value is a promise return it, otherwise continue
|
|
650
|
+
// with the regular processing.
|
|
651
|
+
if (Is.promise(cacheResponse)) {
|
|
652
|
+
return cacheResponse;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
options ??= {};
|
|
656
|
+
options.headers ??= {};
|
|
657
|
+
if (Is.undefined(options.headers[HeaderTypes.ContentType])) {
|
|
658
|
+
options.headers[HeaderTypes.ContentType] = MimeTypes.OctetStream;
|
|
659
|
+
}
|
|
660
|
+
const response = await this.fetch(source, url, method, requestData, options);
|
|
661
|
+
if (response.ok) {
|
|
662
|
+
if (method === HttpMethod.GET) {
|
|
663
|
+
if (response.status === HttpStatusCode.noContent) {
|
|
664
|
+
return new Uint8Array();
|
|
665
|
+
}
|
|
666
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
return (await response.json());
|
|
670
|
+
}
|
|
671
|
+
catch (err) {
|
|
672
|
+
// False positive as FetchError is derived from Error
|
|
673
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
674
|
+
throw new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.decodingJSON`, HttpStatusCode.badRequest, { url }, err);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const errorResponseData = await response.json();
|
|
678
|
+
// False positive as FetchError is derived from Error
|
|
679
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
680
|
+
throw new FetchError(source, `${FetchHelper._CLASS_NAME_CAMEL_CASE}.failureStatusText`, response.status, {
|
|
681
|
+
statusText: response.statusText,
|
|
682
|
+
url
|
|
683
|
+
}, errorResponseData);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Clears the cache.
|
|
687
|
+
*/
|
|
688
|
+
static clearCache() {
|
|
689
|
+
AsyncCache.clearCache(FetchHelper._CACHE_PREFIX);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get a cache entry.
|
|
693
|
+
* @param url The url for the request.
|
|
694
|
+
* @returns The cache entry if it exists.
|
|
695
|
+
*/
|
|
696
|
+
static async getCacheEntry(url) {
|
|
697
|
+
return AsyncCache.get(`${FetchHelper._CACHE_PREFIX}${url}`);
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Remove a cache entry.
|
|
701
|
+
* @param url The url for the request.
|
|
702
|
+
*/
|
|
703
|
+
static removeCacheEntry(url) {
|
|
704
|
+
AsyncCache.remove(`${FetchHelper._CACHE_PREFIX}${url}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Copyright 2024 IOTA Stiftung.
|
|
709
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
710
|
+
/**
|
|
711
|
+
* Class to encode and decode JSON Web Tokens.
|
|
712
|
+
*/
|
|
713
|
+
class Jwt {
|
|
714
|
+
/**
|
|
715
|
+
* Runtime name for the class.
|
|
716
|
+
* @internal
|
|
717
|
+
*/
|
|
718
|
+
static _CLASS_NAME = "Jwt";
|
|
719
|
+
/**
|
|
720
|
+
* Encode a token.
|
|
721
|
+
* @param header The header to encode.
|
|
722
|
+
* @param payload The payload to encode.
|
|
723
|
+
* @param key The key for signing the token, can be omitted if a signer is provided.
|
|
724
|
+
* @returns The encoded token.
|
|
725
|
+
*/
|
|
726
|
+
static async encode(header, payload, key) {
|
|
727
|
+
Guards.object(Jwt._CLASS_NAME, "header", header);
|
|
728
|
+
Guards.arrayOneOf(Jwt._CLASS_NAME, "header.alg", header.alg, Object.values(JwtAlgorithms));
|
|
729
|
+
Guards.object(Jwt._CLASS_NAME, "payload", payload);
|
|
730
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "key", key);
|
|
731
|
+
return Jwt.internalEncode(header, payload, key);
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Encode a token.
|
|
735
|
+
* @param header The header to encode.
|
|
736
|
+
* @param payload The payload to encode.
|
|
737
|
+
* @param signer Custom signer method.
|
|
738
|
+
* @returns The encoded token.
|
|
739
|
+
*/
|
|
740
|
+
static async encodeWithSigner(header, payload, signer) {
|
|
741
|
+
Guards.object(Jwt._CLASS_NAME, "header", header);
|
|
742
|
+
Guards.arrayOneOf(Jwt._CLASS_NAME, "header.alg", header.alg, Object.values(JwtAlgorithms));
|
|
743
|
+
Guards.object(Jwt._CLASS_NAME, "payload", payload);
|
|
744
|
+
Guards.function(Jwt._CLASS_NAME, "signer", signer);
|
|
745
|
+
return Jwt.internalEncode(header, payload, undefined, signer);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Decode a token.
|
|
749
|
+
* @param token The token to decode.
|
|
750
|
+
* @returns The decoded payload.
|
|
751
|
+
*/
|
|
752
|
+
static async decode(token) {
|
|
753
|
+
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
754
|
+
let header;
|
|
755
|
+
let payload;
|
|
756
|
+
let signature;
|
|
757
|
+
const segments = token.split(".");
|
|
758
|
+
if (segments.length > 0) {
|
|
759
|
+
try {
|
|
760
|
+
const bytesHeader = Converter.base64UrlToBytes(segments[0]);
|
|
761
|
+
header = JSON.parse(Converter.bytesToUtf8(bytesHeader));
|
|
762
|
+
}
|
|
763
|
+
catch { }
|
|
764
|
+
}
|
|
765
|
+
if (segments.length > 1) {
|
|
766
|
+
try {
|
|
767
|
+
const bytesPayload = Converter.base64UrlToBytes(segments[1]);
|
|
768
|
+
payload = JSON.parse(Converter.bytesToUtf8(bytesPayload));
|
|
769
|
+
}
|
|
770
|
+
catch { }
|
|
771
|
+
}
|
|
772
|
+
if (segments.length > 2) {
|
|
773
|
+
signature = Converter.base64UrlToBytes(segments[2]);
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
header,
|
|
777
|
+
payload,
|
|
778
|
+
signature
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Verify a token.
|
|
783
|
+
* @param token The token to verify.
|
|
784
|
+
* @param key The key for verifying the token
|
|
785
|
+
* @returns The decoded payload.
|
|
786
|
+
*/
|
|
787
|
+
static async verify(token, key) {
|
|
788
|
+
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
789
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "key", key);
|
|
790
|
+
const decoded = await Jwt.decode(token);
|
|
791
|
+
const verified = await Jwt.verifySignature(decoded.header, decoded.payload, decoded.signature, key);
|
|
792
|
+
return {
|
|
793
|
+
verified,
|
|
794
|
+
...decoded
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Verify a token.
|
|
799
|
+
* @param token The token to verify.
|
|
800
|
+
* @param verifier Custom verification method.
|
|
801
|
+
* @returns The decoded payload.
|
|
802
|
+
*/
|
|
803
|
+
static async verifyWithVerifier(token, verifier) {
|
|
804
|
+
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
805
|
+
Guards.function(Jwt._CLASS_NAME, "verifier", verifier);
|
|
806
|
+
Guards.stringValue(Jwt._CLASS_NAME, "token", token);
|
|
807
|
+
const decoded = await Jwt.decode(token);
|
|
808
|
+
const verified = await Jwt.verifySignature(decoded.header, decoded.payload, decoded.signature, undefined, verifier);
|
|
809
|
+
return {
|
|
810
|
+
verified,
|
|
811
|
+
...decoded
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Verify a token by parts.
|
|
816
|
+
* @param header The header to verify.
|
|
817
|
+
* @param payload The payload to verify.
|
|
818
|
+
* @param signature The signature to verify.
|
|
819
|
+
* @param key The key for verifying the token, if not provided no verification occurs.
|
|
820
|
+
* @param verifier Custom verification method.
|
|
821
|
+
* @returns True if the parts are verified.
|
|
822
|
+
*/
|
|
823
|
+
static async verifySignature(header, payload, signature, key, verifier) {
|
|
824
|
+
const hasKey = Is.notEmpty(key);
|
|
825
|
+
const hasVerifier = Is.notEmpty(verifier);
|
|
826
|
+
if (!hasKey && !hasVerifier) {
|
|
827
|
+
throw new GeneralError(Jwt._CLASS_NAME, "noKeyOrVerifier");
|
|
828
|
+
}
|
|
829
|
+
let verified = false;
|
|
830
|
+
if (Is.object(header) &&
|
|
831
|
+
Is.object(payload) &&
|
|
832
|
+
Is.uint8Array(signature) &&
|
|
833
|
+
Is.arrayOneOf(header.alg, Object.values(JwtAlgorithms))) {
|
|
834
|
+
const segments = [];
|
|
835
|
+
const headerBytes = Converter.utf8ToBytes(JSON.stringify(header));
|
|
836
|
+
segments.push(Converter.bytesToBase64Url(headerBytes));
|
|
837
|
+
const payloadBytes = Converter.utf8ToBytes(JSON.stringify(payload));
|
|
838
|
+
segments.push(Converter.bytesToBase64Url(payloadBytes));
|
|
839
|
+
const jwtHeaderAndPayload = Converter.utf8ToBytes(segments.join("."));
|
|
840
|
+
verifier ??= async (alg, k, p, s) => Jwt.defaultVerifier(alg, k, p, s);
|
|
841
|
+
verified = await verifier(header.alg, key, jwtHeaderAndPayload, signature);
|
|
842
|
+
}
|
|
843
|
+
return verified;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* The default signer for the JWT.
|
|
847
|
+
* @param alg The algorithm to use.
|
|
848
|
+
* @param key The key to sign with.
|
|
849
|
+
* @param payload The payload to sign.
|
|
850
|
+
* @returns The signature.
|
|
851
|
+
*/
|
|
852
|
+
static async defaultSigner(alg, key, payload) {
|
|
853
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "key", key);
|
|
854
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "payload", payload);
|
|
855
|
+
if (alg === "HS256") {
|
|
856
|
+
const algo = new HmacSha256(key);
|
|
857
|
+
return algo.update(payload).digest();
|
|
858
|
+
}
|
|
859
|
+
return Ed25519.sign(key, payload);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* The default verifier for the JWT.
|
|
863
|
+
* @param alg The algorithm to use.
|
|
864
|
+
* @param key The key to verify with.
|
|
865
|
+
* @param payload The payload to verify.
|
|
866
|
+
* @param signature The signature to verify.
|
|
867
|
+
* @returns True if the signature was verified.
|
|
868
|
+
*/
|
|
869
|
+
static async defaultVerifier(alg, key, payload, signature) {
|
|
870
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "key", key);
|
|
871
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "payload", payload);
|
|
872
|
+
Guards.uint8Array(Jwt._CLASS_NAME, "signature", signature);
|
|
873
|
+
if (alg === "HS256") {
|
|
874
|
+
const algo = new HmacSha256(key);
|
|
875
|
+
const sigBytes = algo.update(payload).digest();
|
|
876
|
+
return ArrayHelper.matches(sigBytes, signature);
|
|
877
|
+
}
|
|
878
|
+
return Ed25519.verify(key, payload, signature);
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Encode a token.
|
|
882
|
+
* @param header The header to encode.
|
|
883
|
+
* @param payload The payload to encode.
|
|
884
|
+
* @param key The key for signing the token, can be omitted if a signer is provided.
|
|
885
|
+
* @param signer Custom signer method.
|
|
886
|
+
* @returns The encoded token.
|
|
887
|
+
* @internal
|
|
888
|
+
*/
|
|
889
|
+
static async internalEncode(header, payload, key, signer) {
|
|
890
|
+
const hasKey = Is.notEmpty(key);
|
|
891
|
+
const hasSigner = Is.notEmpty(signer);
|
|
892
|
+
if (!hasKey && !hasSigner) {
|
|
893
|
+
throw new GeneralError(Jwt._CLASS_NAME, "noKeyOrSigner");
|
|
894
|
+
}
|
|
895
|
+
signer ??= async (alg, k, p) => Jwt.defaultSigner(alg, k, p);
|
|
896
|
+
if (Is.undefined(header.typ)) {
|
|
897
|
+
header.typ = "JWT";
|
|
898
|
+
}
|
|
899
|
+
const segments = [];
|
|
900
|
+
const headerBytes = Converter.utf8ToBytes(JSON.stringify(header));
|
|
901
|
+
segments.push(Converter.bytesToBase64Url(headerBytes));
|
|
902
|
+
const payloadBytes = Converter.utf8ToBytes(JSON.stringify(payload));
|
|
903
|
+
segments.push(Converter.bytesToBase64Url(payloadBytes));
|
|
904
|
+
const jwtHeaderAndPayload = Converter.utf8ToBytes(segments.join("."));
|
|
905
|
+
const sigBytes = await signer(header.alg, key, jwtHeaderAndPayload);
|
|
906
|
+
segments.push(Converter.bytesToBase64Url(sigBytes));
|
|
907
|
+
return segments.join(".");
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Copyright 2024 IOTA Stiftung.
|
|
912
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
913
|
+
/**
|
|
914
|
+
* Class to help with mime types.
|
|
915
|
+
*/
|
|
916
|
+
class MimeTypeHelper {
|
|
917
|
+
/**
|
|
918
|
+
* Detect the mime type from a byte array.
|
|
919
|
+
* @param data The data to test.
|
|
920
|
+
* @returns The mime type if detected.
|
|
921
|
+
*/
|
|
922
|
+
static async detect(data) {
|
|
923
|
+
if (!Is.uint8Array(data)) {
|
|
924
|
+
return undefined;
|
|
925
|
+
}
|
|
926
|
+
// Image
|
|
927
|
+
if (MimeTypeHelper.checkBytes(data, [0x47, 0x49, 0x46])) {
|
|
928
|
+
return MimeTypes.Gif;
|
|
929
|
+
}
|
|
930
|
+
if (MimeTypeHelper.checkBytes(data, [0x42, 0x4d])) {
|
|
931
|
+
return MimeTypes.Bmp;
|
|
932
|
+
}
|
|
933
|
+
if (MimeTypeHelper.checkBytes(data, [0xff, 0xd8, 0xff])) {
|
|
934
|
+
return MimeTypes.Jpeg;
|
|
935
|
+
}
|
|
936
|
+
if (MimeTypeHelper.checkBytes(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
|
937
|
+
return MimeTypes.Png;
|
|
938
|
+
}
|
|
939
|
+
if (MimeTypeHelper.checkBytes(data, [0x49, 0x49, 0x2a, 0x00]) ||
|
|
940
|
+
MimeTypeHelper.checkBytes(data, [0x4d, 0x4d, 0x00, 0x2a])) {
|
|
941
|
+
return MimeTypes.Tiff;
|
|
942
|
+
}
|
|
943
|
+
// Compression
|
|
944
|
+
if (MimeTypeHelper.checkBytes(data, [0x1f, 0x8b, 0x8])) {
|
|
945
|
+
return MimeTypes.Gzip;
|
|
946
|
+
}
|
|
947
|
+
if (MimeTypeHelper.checkBytes(data, [0x42, 0x5a, 0x68])) {
|
|
948
|
+
return MimeTypes.Bzip2;
|
|
949
|
+
}
|
|
950
|
+
if (MimeTypeHelper.checkBytes(data, [0x50, 0x4b, 0x3, 0x4])) {
|
|
951
|
+
return MimeTypes.Zip;
|
|
952
|
+
}
|
|
953
|
+
// Documents
|
|
954
|
+
if (MimeTypeHelper.checkText(data, ["%PDF"])) {
|
|
955
|
+
return MimeTypes.Pdf;
|
|
956
|
+
}
|
|
957
|
+
// Lookup svg before xml, as svg are xml files as well
|
|
958
|
+
const asText = Converter.bytesToUtf8(data);
|
|
959
|
+
if (asText.includes("<svg")) {
|
|
960
|
+
return MimeTypes.Svg;
|
|
961
|
+
}
|
|
962
|
+
if (MimeTypeHelper.checkText(data, ["<?xml ", "<message"])) {
|
|
963
|
+
return MimeTypes.Xml;
|
|
964
|
+
}
|
|
965
|
+
if (MimeTypeHelper.checkBytes(data, [0xef, 0xbb, 0xbf]) &&
|
|
966
|
+
MimeTypeHelper.checkText(data, ["<?xml "], 3)) {
|
|
967
|
+
// UTF-8-BOM
|
|
968
|
+
return MimeTypes.Xml;
|
|
969
|
+
}
|
|
970
|
+
if (StringHelper.isUtf8(data)) {
|
|
971
|
+
try {
|
|
972
|
+
JSON.parse(new TextDecoder().decode(data));
|
|
973
|
+
return MimeTypes.Json;
|
|
974
|
+
}
|
|
975
|
+
catch {
|
|
976
|
+
return MimeTypes.PlainText;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Return the default extension for a mime type.
|
|
982
|
+
* @param mimeType The mimetype to get the extension for.
|
|
983
|
+
* @returns The extension for the mime type.
|
|
984
|
+
*/
|
|
985
|
+
static defaultExtension(mimeType) {
|
|
986
|
+
if (!Is.stringValue(mimeType)) {
|
|
987
|
+
return undefined;
|
|
988
|
+
}
|
|
989
|
+
const lookup = {
|
|
990
|
+
[MimeTypes.PlainText]: "txt",
|
|
991
|
+
[MimeTypes.Html]: "html",
|
|
992
|
+
[MimeTypes.Javascript]: "js",
|
|
993
|
+
[MimeTypes.Json]: "json",
|
|
994
|
+
[MimeTypes.JsonLd]: "jsonld",
|
|
995
|
+
[MimeTypes.Xml]: "xml",
|
|
996
|
+
[MimeTypes.OctetStream]: "bin",
|
|
997
|
+
[MimeTypes.Gzip]: "gzip",
|
|
998
|
+
[MimeTypes.Bzip2]: "bz2",
|
|
999
|
+
[MimeTypes.Zip]: "zip",
|
|
1000
|
+
[MimeTypes.Pdf]: "pfd",
|
|
1001
|
+
[MimeTypes.Gif]: "gif",
|
|
1002
|
+
[MimeTypes.Bmp]: "bmp",
|
|
1003
|
+
[MimeTypes.Jpeg]: "jpeg",
|
|
1004
|
+
[MimeTypes.Png]: "png",
|
|
1005
|
+
[MimeTypes.Tiff]: "tif",
|
|
1006
|
+
[MimeTypes.Svg]: "svg",
|
|
1007
|
+
[MimeTypes.WebP]: "webp",
|
|
1008
|
+
[MimeTypes.Mp4]: "mp4",
|
|
1009
|
+
[MimeTypes.Mpeg]: "mpg",
|
|
1010
|
+
[MimeTypes.Webm]: "webm"
|
|
1011
|
+
};
|
|
1012
|
+
return lookup[mimeType];
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Check if the bytes match.
|
|
1016
|
+
* @param data The data to look at.
|
|
1017
|
+
* @param bytes The bytes to try and match.
|
|
1018
|
+
* @param startOffset Start offset in the data.
|
|
1019
|
+
* @returns True if the bytes match.
|
|
1020
|
+
* @internal
|
|
1021
|
+
*/
|
|
1022
|
+
static checkBytes(data, bytes, startOffset = 0) {
|
|
1023
|
+
if (data.length - startOffset < bytes.length) {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1027
|
+
if (data[i + startOffset] !== bytes[i]) {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Check if the text matches.
|
|
1035
|
+
* @param data The data to look at.
|
|
1036
|
+
* @param texts The text to try and match.
|
|
1037
|
+
* @param startOffset Start offset in the data.
|
|
1038
|
+
* @returns True if the bytes match.
|
|
1039
|
+
* @internal
|
|
1040
|
+
*/
|
|
1041
|
+
static checkText(data, texts, startOffset = 0) {
|
|
1042
|
+
return texts.some(text => MimeTypeHelper.checkBytes(data, Array.from(Converter.utf8ToBytes(text)), startOffset));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
export { FetchError, FetchHelper, HeaderTypes, HttpMethod, HttpStatusCode, Jwt, JwtAlgorithms, MimeTypeHelper, MimeTypes };
|