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