@optionfactory/ful 0.80.0 → 0.91.0

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/dist/ful.iife.js CHANGED
@@ -73,171 +73,563 @@ var ful = (function (exports) {
73
73
  }
74
74
  }
75
75
 
76
- class ContextInterceptor {
77
- constructor() {
78
- const context = document.querySelector("meta[name='context']").getAttribute("content");
79
- this.context = context.endsWith("/") ? context.substring(0, context.length - 1) : context;
76
+ /**
77
+ * @typedef {{ type: string; context: string?; reason: string; details: any?; }} Problem
78
+ */
79
+ class Failure extends Error {
80
+ /**
81
+ *
82
+ * @param {string} message
83
+ * @param {Problem[]} problems
84
+ * @param {*} cause
85
+ */
86
+ constructor(message, problems, cause) {
87
+ super(message, { cause });
88
+ this.name = 'Failure';
89
+ this.problems = problems;
80
90
  }
81
- async intercept(request, chain){
82
- const separator = request.resource.startsWith("/") ? "" : "/";
83
- request.resource = this.context + separator + request.resource;
84
- return await chain.proceed(request);
91
+ }
92
+
93
+ /**
94
+ * @typedef {Int8Array| Uint8Array| Uint8ClampedArray| Int16Array| Uint16Array| Int32Array| Uint32Array| Float32Array| Float64Array| BigInt64Array| BigUint64Array} TypedArray
95
+ */
96
+ /**
97
+ * @typedef HttpInterceptor
98
+ * @property {function(Request,HttpInterceptorChain):Promise<Response>} intercept
99
+ */
100
+
101
+ class HttpClientError extends Failure {
102
+ /**
103
+ * @param {string} message
104
+ * @param {number} status
105
+ * @param {{ type: string; context: string?; reason: string; details: any?; }[]} problems
106
+ * @param {Error|undefined} [cause]
107
+ */
108
+ constructor(message, status, problems, cause) {
109
+ super(message, problems, cause);
110
+ this.name = 'HttpClientError';
111
+ this.status = status;
112
+ }
113
+ /**
114
+ *
115
+ * @param {string} type
116
+ * @param {any} cause
117
+ * @returns
118
+ */
119
+ static of(type, cause) {
120
+ return new HttpClientError(cause.message, 0, [{
121
+ type,
122
+ context: null,
123
+ reason: cause.message,
124
+ details: null
125
+ }], cause);
126
+ }
127
+ /**
128
+ * Creates an HttpClientError from a Response.
129
+ * @param {Response} response
130
+ * @returns an HttpClientError
131
+ */
132
+ static async fromResponse(response) {
133
+ const text = await response.text();
134
+ const message = `${response.status} ${response.statusText}: ${text}`;
135
+ const fallback = [{
136
+ type: "GENERIC_PROBLEM",
137
+ context: null,
138
+ reason: message,
139
+ details: null
140
+ }];
141
+ try {
142
+ return new HttpClientError(message, response.status, text ? JSON.parse(text) : fallback);
143
+ } catch (e) {
144
+ return new HttpClientError(message, response.status, fallback);
145
+ }
85
146
  }
86
147
  }
87
148
 
149
+ /**
150
+ * @implements {HttpInterceptor}
151
+ */
88
152
  class CsrfTokenInterceptor {
153
+ #k; #v;
89
154
  constructor() {
90
- this.k = document.querySelector("meta[name='_csrf_header']").getAttribute("content");
91
- this.v = document.querySelector("meta[name='_csrf']").getAttribute("content");
92
- }
93
- async intercept(request, chain){
94
- const headers = new Headers(request.options.headers);
95
- headers.set(this.k, this.v);
96
- request.options.headers = headers;
155
+ this.#k = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content");
156
+ this.#v = document.querySelector("meta[name='_csrf']")?.getAttribute("content");
157
+ }
158
+ async intercept(request, chain) {
159
+ if(this.#k && this.#v) {
160
+ request.headers.set(this.#k, this.#v);
161
+ }
97
162
  return await chain.proceed(request);
98
163
  }
99
164
  }
100
-
165
+ /**
166
+ * @implements {HttpInterceptor}
167
+ */
101
168
  class RedirectOnUnauthorizedInterceptor {
169
+ #redirectUri;
170
+ /**
171
+ * @param {string} redirectUri
172
+ */
102
173
  constructor(redirectUri) {
103
- this.redirectUri = redirectUri;
174
+ this.#redirectUri = redirectUri;
104
175
  }
105
- async intercept(request, chain){
106
- const response = await chain.proceed(request);
107
- if (response.status !== 401) {
108
- return response;
109
- }
110
- window.location.href = this.redirectUri;
111
- }
112
- }
113
-
114
- class Failure extends Error {
115
- static parseProblems(status, text) {
116
- const def = [{
117
- type: "GENERIC_PROBLEM",
118
- context: null,
119
- reason: `${status}: ${text}`,
120
- details: null
121
- }];
122
- try {
123
- return text ? JSON.parse(text) : def;
124
- } catch (e) {
125
- return def;
176
+ async intercept(request, chain) {
177
+ const response = await chain.proceed(request);
178
+ if (response.status === 401) {
179
+ window.location.href = this.#redirectUri;
126
180
  }
127
- }
128
- static fromResponse(status, text) {
129
- return new Failure(status, Failure.parseProblems(status, text));
130
- }
131
- constructor(status, problems) {
132
- super(JSON.stringify(problems));
133
- this.name = `Failure:${status}`;
134
- this.status = status;
135
- this.problems = problems;
181
+ return response;
136
182
  }
137
183
  }
138
184
 
139
185
  class HttpClientBuilder {
186
+ /**
187
+ * @type {HttpInterceptor[]}
188
+ */
189
+ #interceptors;
140
190
  constructor() {
141
- this.interceptors = [];
142
- }
143
- withContext() {
144
- this.interceptors.push(new ContextInterceptor());
145
- return this;
191
+ this.#interceptors = [];
146
192
  }
147
193
  withCsrfToken() {
148
- this.interceptors.push(new CsrfTokenInterceptor());
194
+ this.#interceptors.push(new CsrfTokenInterceptor());
149
195
  return this;
150
196
  }
151
197
  withRedirectOnUnauthorized(redirectUri) {
152
- this.interceptors.push(new RedirectOnUnauthorizedInterceptor(redirectUri));
198
+ this.#interceptors.push(new RedirectOnUnauthorizedInterceptor(redirectUri));
153
199
  return this;
154
200
  }
201
+ /**
202
+ * @param {...HttpInterceptor} interceptors
203
+ */
155
204
  withInterceptors(...interceptors) {
156
- this.interceptors.push(...interceptors);
205
+ this.#interceptors.push(...interceptors);
157
206
  return this;
158
207
  }
159
208
  build() {
160
- const interceptors = this.interceptors;
161
- return new HttpClient({interceptors});
209
+ return new HttpClient(this.#interceptors);
162
210
  }
163
211
  }
164
212
 
213
+ /**
214
+ * @implements {HttpInterceptor}
215
+ */
165
216
  class HttpCall {
166
- async intercept(request, chain){
167
- return await fetch(request.resource, request.options);
168
- }
217
+ async intercept(request, chain) {
218
+ return await fetch(request);
219
+ }
169
220
  }
170
221
 
171
222
  class HttpInterceptorChain {
172
- constructor(interceptors, current){
173
- this.interceptors = interceptors;
174
- this.current = current;
223
+ #interceptors;
224
+ #current;
225
+ /**
226
+ *
227
+ * @param {HttpInterceptor[]} interceptors
228
+ * @param {number} current
229
+ */
230
+ constructor(interceptors, current) {
231
+ this.#interceptors = interceptors;
232
+ this.#current = current;
175
233
  }
176
- async proceed(request){
177
- const interceptor = this.interceptors[this.current];
178
- return await interceptor.intercept(request, new HttpInterceptorChain(this.interceptors, this.current + 1));
234
+ /**
235
+ *
236
+ * @param {Request} request
237
+ * @returns {Promise<Response>} the response
238
+ */
239
+ async proceed(request) {
240
+ const interceptor = this.#interceptors[this.#current];
241
+ return await interceptor.intercept(request, new HttpInterceptorChain(this.#interceptors, this.#current + 1));
179
242
  }
180
243
  }
181
244
 
182
-
183
245
  class HttpClient {
246
+ #interceptors;
247
+ /**
248
+ * Creates a builder for an HttpClient.
249
+ * @returns {HttpClientBuilder} the client builder
250
+ */
184
251
  static builder() {
185
252
  return new HttpClientBuilder();
186
253
  }
187
- constructor({interceptors}){
188
- this.interceptors = interceptors || [];
254
+ /**
255
+ * Creates an HttpClient.
256
+ * @param {HttpInterceptor[]|undefined} interceptors - a list of interceptors to be registered for every request performed by the created client.
257
+ */
258
+ constructor(interceptors) {
259
+ this.#interceptors = interceptors || [];
189
260
  }
190
- async exchange(resource, options) {
191
- const opts = options || {};
192
- const interceptors = [...this.interceptors, ...opts.interceptors || [], new HttpCall()];
193
- const chain = new HttpInterceptorChain(interceptors, 0);
194
- return await chain.proceed({resource, options: opts});
261
+ /**
262
+ * Performs an HTTP exchange.
263
+ * @async
264
+ * @param {string} uri - the (possibly relative) request url
265
+ * @param {RequestInit|undefined} options - fetch options
266
+ * @param {HttpInterceptor[]|undefined} interceptors - the HttpInterceptors to be registered for this exchange.
267
+ * @returns {Promise<Response>} the response
268
+ */
269
+ async exchange(uri, options, interceptors) {
270
+ const is = [...this.#interceptors, ...interceptors || [], new HttpCall()];
271
+ const chain = new HttpInterceptorChain(is, 0);
272
+ return await chain.proceed(new Request(uri, options));
195
273
  }
196
- async fetch(resource, options) {
197
- const response = await this.exchange(resource, options);
198
- if (!response.ok) {
199
- const message = await response.text();
200
- throw Failure.fromResponse(response.status, message);
201
- }
202
- return response;
274
+ /**
275
+ * Creates a request builder.
276
+ * @param {string} method - the HTTP method to be used
277
+ * @param {string} uri - the (possibly relative) request url
278
+ * @returns {HttpRequestBuilder} the request builder
279
+ */
280
+ request(method, uri) {
281
+ return HttpRequestBuilder.create(this, method, uri);
282
+ }
283
+ /**
284
+ * Creates a request builder.
285
+ * @param {string} uri - the (possibly relative) request url
286
+ * @returns {HttpRequestBuilder} the request builder
287
+ */
288
+ get(uri) {
289
+ return HttpRequestBuilder.create(this, 'GET', uri);
290
+ }
291
+ /**
292
+ * Creates a request builder.
293
+ * @param {string} uri - the (possibly relative) request url
294
+ * @returns {HttpRequestBuilder} the request builder
295
+ */
296
+ head(uri) {
297
+ return HttpRequestBuilder.create(this, 'HEAD', uri);
298
+ }
299
+ /**
300
+ * Creates a request builder.
301
+ * @param {string} uri - the (possibly relative) request url
302
+ * @returns {HttpRequestBuilder} the request builder
303
+ */
304
+ post(uri) {
305
+ return HttpRequestBuilder.create(this, 'POST', uri);
306
+ }
307
+ /**
308
+ * Creates a request builder.
309
+ * @param {string} uri - the (possibly relative) request url
310
+ * @returns {HttpRequestBuilder} the request builder
311
+ */
312
+ put(uri) {
313
+ return HttpRequestBuilder.create(this, 'PUT', uri);
314
+ }
315
+ /**
316
+ * Creates a request builder.
317
+ * @param {string} uri - the (possibly relative) request url
318
+ * @returns {HttpRequestBuilder} the request builder
319
+ */
320
+ patch(uri) {
321
+ return HttpRequestBuilder.create(this, 'PATCH', uri);
322
+ }
323
+ /**
324
+ * Creates a request builder.
325
+ * @param {string} uri - the (possibly relative) request url
326
+ * @returns {HttpRequestBuilder} the request builder
327
+ */
328
+ delete(uri) {
329
+ return HttpRequestBuilder.create(this, 'DELETE', uri);
330
+ }
331
+ }
332
+
333
+ /**
334
+ *
335
+ * @param {Response} response
336
+ * @param {'text'|'json'|'blob'|'arrayBuffer'} type
337
+ * @returns
338
+ */
339
+ const unmarshal = async (response, type) => {
340
+ try {
341
+ return await response[type]();
342
+ } catch (ex) {
343
+ throw HttpClientError.of("UNMARSHALING_PROBLEM", ex);
344
+ }
345
+ };
346
+
347
+
348
+ class HttpRequestBuilder {
349
+ #client;
350
+ #method;
351
+ #uri;
352
+ #params;
353
+ #headers;
354
+ #body;
355
+ #options;
356
+ #interceptors;
357
+ /**
358
+ * Creates an HttpRequestBuilder.
359
+ * @param {HttpClient} client
360
+ * @param {string} method - the HTTP method to be used
361
+ * @param {string} uri - the (possibly relative) request url
362
+ * @returns {HttpRequestBuilder} the builder
363
+ */
364
+ static create(client, method, uri) {
365
+ return new HttpRequestBuilder(
366
+ client,
367
+ method,
368
+ uri,
369
+ new URLSearchParams(),
370
+ new Headers(),
371
+ undefined,
372
+ {},
373
+ []
374
+ );
375
+ }
376
+ /**
377
+ * Creates an HttpRequestBuilder.
378
+ * @param {HttpClient} client
379
+ * @param {string} method - the HTTP method to be used
380
+ * @param {string} uri - the (possibly relative) request url
381
+ * @param {URLSearchParams} params
382
+ * @param {Headers} headers
383
+ * @param {any} body
384
+ * @param {Omit<RequestInit,"headers"|"method"|"body">} options
385
+ * @param {HttpInterceptor[]} interceptors
386
+ */
387
+ constructor(client, method, uri, params, headers, body, options, interceptors) {
388
+ this.#client = client;
389
+ this.#method = method;
390
+ this.#uri = uri;
391
+ this.#params = params;
392
+ this.#body = body;
393
+ this.#headers = headers;
394
+ this.#options = options;
395
+ this.#interceptors = interceptors;
396
+ }
397
+ /**
398
+ * Add all passed headers to the request, overriding existing ones if that key already exists.
399
+ * @param {HeadersInit} hs
400
+ * @returns {HttpRequestBuilder} this builder
401
+ */
402
+ headers(hs) {
403
+ for (const [k, v] of new Headers(hs).entries()) {
404
+ this.#headers.set(k, v);
405
+ }
406
+ return this;
407
+ }
408
+ /**
409
+ * Adds an header to the request, overriding it if it already exists.
410
+ * @param {string} k
411
+ * @param {string} v
412
+ * @returns {HttpRequestBuilder} this builder
413
+ */
414
+ header(k, v) {
415
+ this.#headers.set(k, v);
416
+ return this;
417
+ }
418
+ /**
419
+ * Add all query parameters to the request, overriding existing ones if that key already exists.
420
+ * @param {URLSearchParams|Record<string,string>|string[][]|string} ps
421
+ * @returns {HttpRequestBuilder} this builder
422
+ */
423
+ params(ps) {
424
+ for (const [k, v] of new URLSearchParams(ps).entries()) {
425
+ this.#params.set(k, v);
426
+ }
427
+ return this;
428
+ }
429
+ /**
430
+ * Adds a query parameter to the request, overriding it if it already exists.
431
+ * @param {string} k
432
+ * @param {string} v
433
+ * @returns {HttpRequestBuilder} this builder
434
+ */
435
+ param(k, v) {
436
+ this.#params.set(k, v);
437
+ return this;
203
438
  }
204
- async json(resource, options) {
439
+ /**
440
+ * Sets the request body.
441
+ * `Content-Type: multipart/form-data` header is automatically added by fetch when data is a FormData instance if not explicitly set.
442
+ * `Content-Type: application/x-www-form-urlencoded` header is automatically added by fetch when data is an URLSearchParams instance if not explicitly set.
443
+ * `Content-Type: text/plain` header is automatically added by fetch when data is a string instance if not explicitly set.
444
+ * @param {string|ArrayBuffer|Blob|DataView|File|FormData|TypedArray|URLSearchParams|ReadableStream} data
445
+ * @returns {HttpRequestBuilder} this builder
446
+ */
447
+ body(data) {
448
+ this.#body = data;
449
+ return this;
450
+ }
451
+ /**
452
+ * Sets the request body that will be serialized as json. Calling this method adds the `Content-Type application/json` header for the request.
453
+ * @param {any} body - the body to be serialized as json
454
+ * @returns {HttpRequestBuilder} this builder
455
+ */
456
+ json(body) {
457
+ this.#headers.set("Content-Type", "application/json");
458
+ this.#body = JSON.stringify(body);
459
+ return this;
460
+ }
461
+ /**
462
+ * Sets the request body as a FormData configured using the callback.
463
+ * `Content-Type: multipart/form-data` header is automatically added by fetch if not explicitly set.
464
+ * @param {function(HttpMultipartRequestCustomizer):void} callback
465
+ */
466
+ multipart(callback) {
467
+ const formData = new FormData();
468
+ const builder = new HttpMultipartRequestCustomizer(formData);
469
+ callback(builder);
470
+ this.#body = formData;
471
+ }
472
+ /**
473
+ * Sets a fetch options for the request.
474
+ * @param {Omit<RequestInit,"headers"|"method"|"body">} kvs
475
+ * @returns {HttpRequestBuilder} this builder
476
+ */
477
+ options(kvs) {
478
+ for (const [k, v] of Object.entries(kvs)) {
479
+ // @ts-ignore
480
+ this.#options[k] = v;
481
+ }
482
+ return this;
483
+ }
484
+ /**
485
+ * Sets a fetch option for the request.
486
+ * @param {keyof Omit<RequestInit,"headers"|"method"|"body">} k
487
+ * @param {*} v
488
+ * @returns {HttpRequestBuilder} this builder
489
+ */
490
+ option(k, v) {
491
+ this.#options[k] = v;
492
+ return this;
493
+ }
494
+ /**
495
+ * Adds interceptors to the request.
496
+ * @param {[HttpInterceptor]} is - the interceptor to be regisered
497
+ * @returns {HttpRequestBuilder} this builder
498
+ */
499
+ interceptors(is) {
500
+ for (const i of is) {
501
+ this.#interceptors.push(i);
502
+ }
503
+ return this;
504
+ }
505
+ /**
506
+ * Adds an interceptor to the request.
507
+ * @param {HttpInterceptor} i - the interceptor to be regisered
508
+ * @returns {HttpRequestBuilder} this builder
509
+ */
510
+ interceptor(i) {
511
+ this.#interceptors.push(i);
512
+ return this;
513
+ }
514
+ /**
515
+ * Performs an HTTP exchange using the configured client, request and interceptors.
516
+ * @returns {Promise<Response>} the response
517
+ */
518
+ async exchange() {
519
+ const uri = this.#params.size ? `${this.#uri}?${this.#params}` : this.#uri;
520
+ const opts = {
521
+ ...this.#options,
522
+ headers: this.#headers,
523
+ method: this.#method,
524
+ body: this.#body,
525
+ };
526
+ return await this.#client.exchange(uri, opts, this.#interceptors);
527
+ }
528
+ /**
529
+ * Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
530
+ * @returns {Promise<Response>} the response
531
+ */
532
+ async fetch() {
533
+ const uri = this.#params.size ? `${this.#uri}?${this.#params}` : this.#uri;
534
+ const opts = {
535
+ ...this.#options,
536
+ headers: this.#headers,
537
+ method: this.#method,
538
+ body: this.#body,
539
+ };
205
540
  try {
206
- const response = await this.fetch(resource, options);
207
- const text = await response.text();
208
- return text ? JSON.parse(text) : undefined;
209
- } catch (e) {
210
- if (e instanceof Failure) {
211
- throw e;
541
+ const response = await this.#client.exchange(uri, opts, this.#interceptors);
542
+ if (!response.ok) {
543
+ throw await HttpClientError.fromResponse(response);
544
+ }
545
+ return response;
546
+ } catch (ex) {
547
+ if (ex instanceof Failure) {
548
+ throw ex;
212
549
  }
213
- throw new Failure(0, [{
214
- type: "CONNECTION_PROBLEM",
215
- context: null,
216
- reason: e.message,
217
- details: null
218
- }]);
550
+ throw HttpClientError.of("CONNECTION_PROBLEM", ex);
219
551
  }
220
552
  }
553
+ /**
554
+ * Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
555
+ * @returns {Promise<string>} the response body, as text
556
+ */
557
+ async fetchText() {
558
+ const response = await this.fetch();
559
+ return await unmarshal(response, 'text');
560
+ }
561
+ /**
562
+ * Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
563
+ * @returns {Promise<any>} the response body, deserialized as JSON
564
+ */
565
+ async fetchJson() {
566
+ const response = await this.fetch();
567
+ return await unmarshal(response, 'json');
568
+ }
569
+ /**
570
+ * Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
571
+ * @returns {Promise<Blob>} the response body, as a Blob
572
+ */
573
+ async fetchBlob() {
574
+ const response = await this.fetch();
575
+ return await unmarshal(response, 'blob');
576
+ }
577
+ /**
578
+ * Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
579
+ * @returns {Promise<ArrayBuffer>} the response body, as an ArrayBuffer
580
+ */
581
+ async fetchArrayBuffer() {
582
+ const response = await this.fetch();
583
+ return await unmarshal(response, 'arrayBuffer');
584
+ }
221
585
  }
222
586
 
223
- function jsonRequest(method, body, headers){
224
- return {
225
- headers: {
226
- "Content-Type": "application/json",
227
- ...headers
228
- },
229
- method: method,
230
- body: JSON.stringify(body)
587
+
588
+ class HttpMultipartRequestCustomizer {
589
+ #formData;
590
+ /**
591
+ *
592
+ * @param {FormData} formData
593
+ */
594
+ constructor(formData){
595
+ this.#formData = formData;
596
+ }
597
+ /**
598
+ * Appends a value to the FormData.
599
+ * @param {string} name
600
+ * @param {*} value
601
+ * @returns this builder
602
+ */
603
+ field(name, value){
604
+ this.#formData.append(name, value);
605
+ return this;
606
+ }
607
+ /**
608
+ * Appends a Blob to the FormData.
609
+ * If `filename` is omitted, FormData defaults are applied:
610
+ * The default filename for Blob objects is "blob";
611
+ * The default filename for File objects is the file's filename.
612
+ * @param {string} name
613
+ * @param {Blob} value
614
+ * @param {string|undefined} filename
615
+ * @returns this builder
616
+ */
617
+ blob(name, value, filename){
618
+ this.#formData.append(name, value, filename);
619
+ return this;
620
+ }
621
+ /**
622
+ * Appends a JSON serialized blob to the FormData.
623
+ * @param {string} name
624
+ * @param {any} value
625
+ * @param {string|undefined} filename
626
+ * @returns this builder
627
+ */
628
+ json(name, value, filename){
629
+ const blob = new Blob([JSON.stringify(value)], {type: 'application/json'});
630
+ this.#formData.append(name, blob, filename);
631
+ return this;
231
632
  }
232
- }
233
- function jsonPost(body, headers){
234
- return jsonRequest('POST', body, headers);
235
- }
236
- function jsonPut(body, headers){
237
- return jsonRequest('PUT', body, headers);
238
- }
239
- function jsonPatch(body, headers){
240
- return jsonRequest('PATCH', body, headers);
241
633
  }
242
634
 
243
635
  class Storage {
@@ -336,7 +728,7 @@ var ful = (function (exports) {
336
728
  Object.entries(additionalParams || {}).forEach(kv => {
337
729
  url.searchParams.set(kv[0], kv[1]);
338
730
  });
339
- window.location = url;
731
+ window.location.href = url.toString();
340
732
  }
341
733
  async registration(additionalParams){
342
734
  await this.action(this.uri.registration, additionalParams);
@@ -346,7 +738,7 @@ var ful = (function (exports) {
346
738
  kc_action: kcAction
347
739
  });
348
740
  }
349
- async _tokenExchange(code, state) {
741
+ async #tokenExchange(code, state) {
350
742
  window.history.replaceState('', "", this.uri.redirect);
351
743
  const stateAndVerifier = this.storage.pop(AuthorizationCodeFlow.PKCE_AND_STATE_KEY);
352
744
  if (stateAndVerifier.state !== state) {
@@ -379,7 +771,7 @@ var ful = (function (exports) {
379
771
  if (code && this.storage.load(AuthorizationCodeFlow.PKCE_AND_STATE_KEY)) {
380
772
  //if callback from keycloak and we have our state still stored
381
773
  const state = url.searchParams.get("state");
382
- return await this._tokenExchange(code, state);
774
+ return await this.#tokenExchange(code, state);
383
775
  }
384
776
  //if not authorized
385
777
  await this.action(this.uri.auth, {});
@@ -391,10 +783,10 @@ var ful = (function (exports) {
391
783
  class AuthorizationCodeFlowSession {
392
784
  static parseToken(token) {
393
785
  const [rawHeader, rawPayload, signature] = token.split(".");
394
- const ut8decoder = new TextDecoder("utf-8");
786
+ const utf8decoder = new TextDecoder("utf-8");
395
787
  return {
396
- header: JSON.parse(ut8decoder.decode(Base64.decode(rawHeader, Base64.STANDARD))),
397
- payload: JSON.parse(ut8decoder.decode(Base64.decode(rawPayload, Base64.STANDARD))),
788
+ header: JSON.parse(utf8decoder.decode(Base64.decode(rawHeader, Base64.STANDARD))),
789
+ payload: JSON.parse(utf8decoder.decode(Base64.decode(rawPayload, Base64.STANDARD))),
398
790
  signature: signature
399
791
  };
400
792
  }
@@ -422,7 +814,8 @@ var ful = (function (exports) {
422
814
  ])
423
815
  });
424
816
  if (!response.ok) {
425
- throw new Error("Error:" + response.status + ": " + response.text());
817
+ const text = await response.text();
818
+ throw new Error("Error:" + response.status + ": " + text);
426
819
  }
427
820
  const token = await response.json();
428
821
  this.token = token;
@@ -449,7 +842,7 @@ var ful = (function (exports) {
449
842
  const url = new URL(this.uri.logout);
450
843
  url.searchParams.set("post_logout_redirect_uri", this.uri.redirect);
451
844
  url.searchParams.set("id_token_hint", this.token.id_token);
452
- window.location = url;
845
+ window.location.href = url.toString();
453
846
  }
454
847
 
455
848
  bearerToken() {
@@ -462,18 +855,19 @@ var ful = (function (exports) {
462
855
  }
463
856
 
464
857
  class AuthorizationCodeFlowInterceptor {
858
+ #session;
859
+ #gracePeriodBefore;
860
+ #gracePeriodAfter;
465
861
  constructor(session, gracePeriodBefore, gracePeriodAfter) {
466
- this.session = session;
467
- this.gracePeriodBefore = gracePeriodBefore || 2000;
468
- this.gracePeriodAfter = gracePeriodAfter || 30000;
862
+ this.#session = session;
863
+ this.#gracePeriodBefore = gracePeriodBefore || 2000;
864
+ this.#gracePeriodAfter = gracePeriodAfter || 30000;
469
865
  }
470
866
  async intercept(request, chain) {
471
- await this.session.refreshIf(this.gracePeriodBefore);
472
- const headers = new Headers(request.options.headers);
473
- headers.set("Authorization", this.session.bearerToken());
474
- request.options.headers = headers;
867
+ await this.#session.refreshIf(this.#gracePeriodBefore);
868
+ request.headers.set("Authorization", this.#session.bearerToken());
475
869
  const response = await chain.proceed(request);
476
- await this.session.refreshIf(this.gracePeriodAfter);
870
+ await this.#session.refreshIf(this.#gracePeriodAfter);
477
871
  return response;
478
872
  }
479
873
  }
@@ -507,7 +901,7 @@ var ful = (function (exports) {
507
901
  };
508
902
 
509
903
  return function () {
510
- args = arguments;
904
+ args = [...arguments];
511
905
  previousTimestamp = new Date().getTime();
512
906
  if (tid === null) {
513
907
  tid = setTimeout(later, timeoutMs);
@@ -541,7 +935,7 @@ var ful = (function (exports) {
541
935
  previousTimestamp = now;
542
936
  }
543
937
  const remaining = timeoutMs - (now - previousTimestamp);
544
- args = arguments;
938
+ args = [...arguments];
545
939
  if (remaining <= 0 || remaining > timeoutMs) {
546
940
  if (tid !== null) {
547
941
  clearTimeout(tid);
@@ -647,16 +1041,18 @@ var ful = (function (exports) {
647
1041
  .forEach(a => {
648
1042
  const target = a.substring(prefix.length);
649
1043
  if (target === 'class') {
650
- to.classList.add(...from.getAttribute(prefix + "class").split(" ").filter(a => a.length));
1044
+ const classes = from.getAttribute(prefix + "class")?.split(" ").filter(a => a.length) ?? [];
1045
+ to.classList.add(...classes);
651
1046
  return;
652
1047
  }
1048
+ // @ts-ignore
653
1049
  to.setAttribute(target, from.getAttribute(a));
654
1050
  });
655
1051
  }
656
1052
  /**
657
1053
  *
658
1054
  * @param {HTMLElement} el
659
- * @param {stirng} attr
1055
+ * @param {string} attr
660
1056
  * @param {boolean} value
661
1057
  */
662
1058
  static toggle(el, attr, value) {
@@ -683,19 +1079,21 @@ var ful = (function (exports) {
683
1079
  * @returns the slots
684
1080
  */
685
1081
  static from(el) {
1082
+ /** @type [string, Element][] */
686
1083
  const namedSlots = Array.from(el.childNodes)
687
- .filter(el => el.matches && el.matches('[slot]'))
1084
+ .filter(el => el instanceof Element)
1085
+ .filter(el => el.matches('[slot]'))
688
1086
  .map(el => {
689
1087
  el.remove();
690
1088
  const slot = el.getAttribute("slot");
691
1089
  el.removeAttribute("slot");
692
- return [slot, el];
1090
+ return [slot ?? 'unnamed', el];
693
1091
  });
694
1092
  const slots = {};
695
1093
  slots.default = new DocumentFragment();
696
1094
  slots.default.append(...el.childNodes);
697
- for(const [name,el] of namedSlots){
698
- if(!(name in slots)){
1095
+ for (const [name, el] of namedSlots) {
1096
+ if (!(name in slots)) {
699
1097
  slots[name] = new DocumentFragment();
700
1098
  }
701
1099
  slots[name].append(el);
@@ -721,7 +1119,8 @@ var ful = (function (exports) {
721
1119
  #ec;
722
1120
  put(k, fragment) {
723
1121
  if (this.#ec) {
724
- this.#idToTemplate[k] = Template.fromFragment(fragment, ec);
1122
+ // @ts-ignore
1123
+ this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, this.#ec);
725
1124
  return;
726
1125
  }
727
1126
  this.#idToFragment[k] = fragment;
@@ -740,6 +1139,7 @@ var ful = (function (exports) {
740
1139
  this.#ec = ec;
741
1140
  for (const [k, fragment] of Object.entries(this.#idToFragment)) {
742
1141
  delete this.#idToFragment[k];
1142
+ // @ts-ignore
743
1143
  this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, ec, ...data);
744
1144
  }
745
1145
  }
@@ -843,8 +1243,8 @@ var ful = (function (exports) {
843
1243
  #initialized;
844
1244
  #reflecting;
845
1245
  #internals;
846
- constructor(...args) {
847
- super(...args);
1246
+ constructor() {
1247
+ super();
848
1248
  this.#internals = this.attachInternals();
849
1249
  }
850
1250
  get initialized() {
@@ -872,6 +1272,7 @@ var ful = (function (exports) {
872
1272
  observer.disconnect();
873
1273
  upgradeQueue.enqueue(this);
874
1274
  });
1275
+ // @ts-ignore
875
1276
  observer.observe(this.parentNode, { childList: true, subtree: true });
876
1277
  }
877
1278
  attributeChangedCallback(attr, oldValue, newValue) {
@@ -897,6 +1298,7 @@ var ful = (function (exports) {
897
1298
  return;
898
1299
  }
899
1300
  this.#parsed = true;
1301
+ // @ts-ignore
900
1302
  await this.render(elements.template(templateId), slots ? LightSlots.from(this) : undefined);
901
1303
 
902
1304
  for (const [attr, mapper] of attrsAndMappers) {
@@ -925,8 +1327,6 @@ var ful = (function (exports) {
925
1327
  return k;
926
1328
  };
927
1329
 
928
- /* global Infinity, CSS */
929
-
930
1330
  function flatten(obj, prefix) {
931
1331
  return Object.keys(obj).reduce((acc, k) => {
932
1332
  const pre = prefix.length ? prefix + '.' : '';
@@ -1050,7 +1450,7 @@ var ful = (function (exports) {
1050
1450
  }
1051
1451
  get values() {
1052
1452
  return Array.from(this.querySelectorAll('[name]'))
1053
- .filter((el) => {
1453
+ .filter(el => {
1054
1454
  if (el.dataset['fulBindInclude'] === 'never') {
1055
1455
  return false;
1056
1456
  }
@@ -1061,14 +1461,14 @@ var ful = (function (exports) {
1061
1461
  }, {});
1062
1462
  }
1063
1463
  set errors(es) {
1064
- const fieldErrors = es.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
1065
- const globalErrors = es.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
1464
+ const fieldErrors = es.filter(e => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
1465
+ const globalErrors = es.filter(e => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
1066
1466
  this.querySelectorAll(`.${Form.INVALID_CLASS}`).forEach(el => el.classList.remove(Form.INVALID_CLASS));
1067
1467
  this.querySelectorAll("ful-errors").forEach(el => {
1068
1468
  el.replaceChildren();
1069
1469
  el.setAttribute('hidden', '');
1070
1470
  });
1071
- fieldErrors.forEach((e) => {
1471
+ fieldErrors.forEach(e => {
1072
1472
  const name = e.context.replace("[", ".").replace("].", ".");
1073
1473
  const validationTargetsSelector = `[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`;
1074
1474
  this.querySelectorAll(validationTargetsSelector).forEach(input => input.classList.add(Form.INVALID_CLASS));
@@ -1084,7 +1484,7 @@ var ful = (function (exports) {
1084
1484
  if (!this.hasAttribute('scroll-on-error')) {
1085
1485
  return;
1086
1486
  }
1087
- const ys = Array.from(this.querySelectorAll(`[ful-validated-field]:has(.${Form.INVALID_CLASS}) ful-field-error`))
1487
+ const ys = Array.from(this.querySelectorAll(`ful-errors:not([hidden]), [ful-validated-field]:has(.${Form.INVALID_CLASS}) ful-field-error`))
1088
1488
  .map(el => el.parentElement ? el.parentElement : el)
1089
1489
  .map(el => el.getBoundingClientRect().y + window.scrollY);
1090
1490
  const miny = Math.min(...ys);
@@ -1139,6 +1539,7 @@ var ful = (function (exports) {
1139
1539
  slots: true,
1140
1540
  template: INPUT_TEMPLATE
1141
1541
  }){
1542
+ input;
1142
1543
  render(template, slots) {
1143
1544
  const fragment = makeInputFragment(this, template, slots);
1144
1545
  this.replaceChildren(fragment);
@@ -1309,27 +1710,40 @@ var ful = (function (exports) {
1309
1710
  input.addEventListener('change', evt => {
1310
1711
  evt.stopPropagation();
1311
1712
  //change is not cancelable
1312
- this.dispatchEvent(new CustomEvent('change', {
1313
- bubbles: true,
1314
- cancelable: false,
1713
+ this.dispatchEvent(new CustomEvent('change', {
1714
+ bubbles: true,
1715
+ cancelable: false,
1315
1716
  detail: {
1316
1717
  value: this.value
1317
1718
  }
1318
- }));
1319
- });
1719
+ }));
1720
+ });
1320
1721
  const label = Fragments.fromChildNodes(el);
1321
1722
  return [input, label];
1322
1723
  });
1323
1724
 
1324
1725
  radioEls.forEach(el => el.remove());
1325
- template.renderTo(this, {name, slots, inputsAndLabels});
1726
+ template.renderTo(this, { name, slots, inputsAndLabels });
1326
1727
  }
1327
1728
  get value() {
1729
+ /** @type {HTMLInputElement|null} */
1328
1730
  const checked = this.querySelector('input[type=radio]:checked');
1329
1731
  return checked ? checked.value : null;
1330
1732
  }
1331
1733
  set value(value) {
1332
- this.querySelector(`input[type=radio][value=${CSS.escape(value)}]`).checked = true;
1734
+ if (value === null) {
1735
+ /** @type {HTMLInputElement[]} */
1736
+ this.querySelectorAll(`input[type=radio]`).forEach(el => {
1737
+ // @ts-ignore
1738
+ el.checked = false;
1739
+ });
1740
+ return;
1741
+ }
1742
+ /** @type {HTMLInputElement|null} */
1743
+ const el = this.querySelector(`input[type=radio][value=${CSS.escape(value)}]`);
1744
+ if (el) {
1745
+ el.checked = true;
1746
+ }
1333
1747
  }
1334
1748
  }
1335
1749
 
@@ -1359,6 +1773,7 @@ var ful = (function (exports) {
1359
1773
  exports.Fragments = Fragments;
1360
1774
  exports.Hex = Hex;
1361
1775
  exports.HttpClient = HttpClient;
1776
+ exports.HttpClientError = HttpClientError;
1362
1777
  exports.INPUT_TEMPLATE = INPUT_TEMPLATE;
1363
1778
  exports.Input = Input;
1364
1779
  exports.LightSlots = LightSlots;
@@ -1372,15 +1787,9 @@ var ful = (function (exports) {
1372
1787
  exports.TemplatesRegistry = TemplatesRegistry;
1373
1788
  exports.VersionedStorage = VersionedStorage;
1374
1789
  exports.elements = elements;
1375
- exports.jsonPatch = jsonPatch;
1376
- exports.jsonPost = jsonPost;
1377
- exports.jsonPut = jsonPut;
1378
- exports.jsonRequest = jsonRequest;
1379
1790
  exports.makeInputFragment = makeInputFragment;
1380
1791
  exports.timing = timing;
1381
1792
 
1382
- Object.defineProperty(exports, '__esModule', { value: true });
1383
-
1384
1793
  return exports;
1385
1794
 
1386
1795
  })({});