@optionfactory/ful 0.25.0 → 0.27.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.css +1 -1
- package/dist/ful.css.map +1 -1
- package/dist/ful.iife.js +638 -559
- package/dist/ful.iife.js.map +1 -1
- package/dist/ful.iife.min.js +1 -1
- package/dist/ful.iife.min.js.map +1 -1
- package/dist/ful.min.mjs +1 -1
- package/dist/ful.min.mjs.map +1 -1
- package/dist/ful.mjs +633 -559
- package/dist/ful.mjs.map +1 -1
- package/package.json +7 -6
package/dist/ful.iife.js
CHANGED
|
@@ -73,8 +73,9 @@ var ful = (function (exports) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
constructor() {
|
|
76
|
+
const Observable = (SuperClass) => class extends SuperClass {
|
|
77
|
+
constructor(...args) {
|
|
78
|
+
super(...args);
|
|
78
79
|
this.listeners = {};
|
|
79
80
|
}
|
|
80
81
|
fireSync(event, data, initialAcc) {
|
|
@@ -101,18 +102,8 @@ var ful = (function (exports) {
|
|
|
101
102
|
const listeners = this.listeners[event] || [];
|
|
102
103
|
const idx = listeners.indexOf(listener);
|
|
103
104
|
return idx === -1 ? [] : listeners.splice(idx, 1);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
self.listeners = {};
|
|
107
|
-
}
|
|
108
|
-
static mixin(ctor) {
|
|
109
|
-
ctor.prototype.fireSync = Observable.prototype.fireSync;
|
|
110
|
-
ctor.prototype.fire = Observable.prototype.fire;
|
|
111
|
-
ctor.prototype.on = Observable.prototype.on;
|
|
112
|
-
ctor.prototype.un = Observable.prototype.un;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
116
107
|
|
|
117
108
|
class ContextInterceptor {
|
|
118
109
|
constructor() {
|
|
@@ -281,285 +272,491 @@ var ful = (function (exports) {
|
|
|
281
272
|
return jsonRequest('PATCH', body, headers);
|
|
282
273
|
}
|
|
283
274
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
static uid(prefix) {
|
|
289
|
-
return `${prefix}-${++CustomElements.id}`;
|
|
275
|
+
class Storage {
|
|
276
|
+
constructor(prefix, storage) {
|
|
277
|
+
this.prefix = prefix;
|
|
278
|
+
this.storage = storage;
|
|
290
279
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
.filter(a => except.indexOf(a) === -1)
|
|
294
|
-
.filter(a => a[0] === '@')
|
|
295
|
-
.forEach(a => {
|
|
296
|
-
if (a === '@class') {
|
|
297
|
-
to.classList.add(...from.getAttribute("@class").split(" ").filter(a => a.length));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
to.setAttribute(a.substring(1), from.getAttribute(a));
|
|
301
|
-
});
|
|
280
|
+
save(k, v) {
|
|
281
|
+
this.storage.setItem(`${this.prefix}-${k}`, JSON.stringify(v));
|
|
302
282
|
}
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
const slot = el.getAttribute("slot");
|
|
307
|
-
el.removeAttribute("slot");
|
|
308
|
-
return [slot, el];
|
|
309
|
-
}));
|
|
310
|
-
slotted.default = new DocumentFragment();
|
|
311
|
-
slotted.default.append(...el.childNodes);
|
|
312
|
-
return slotted;
|
|
283
|
+
load(k) {
|
|
284
|
+
const got = this.storage.getItem(`${this.prefix}-${k}`);
|
|
285
|
+
return got === undefined ? undefined : JSON.parse(got);
|
|
313
286
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
* <label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
322
|
-
* </div>
|
|
323
|
-
* <span data-tpl-if="slotted.after" class="input-group-text">{{{{ slotted.after }}}}</span>
|
|
324
|
-
* <ful-field-error data-tpl-field="name"></ful-field-error>
|
|
325
|
-
* </div>
|
|
326
|
-
*/
|
|
327
|
-
const label = document.createElement("label");
|
|
328
|
-
label.setAttribute("for", id);
|
|
329
|
-
label.classList.add('form-label');
|
|
330
|
-
label.append(slotted.default);
|
|
331
|
-
|
|
332
|
-
const ff = document.createElement('div');
|
|
333
|
-
ff.classList.add("form-floating");
|
|
334
|
-
ff.append(slotted.input, label);
|
|
335
|
-
|
|
336
|
-
const ffe = document.createElement('ful-field-error');
|
|
337
|
-
ffe.setAttribute("field", name);
|
|
338
|
-
|
|
339
|
-
const ig = document.createElement("div");
|
|
340
|
-
ig.classList.add('input-group', 'has-validtion');
|
|
341
|
-
|
|
342
|
-
if (slotted.before) {
|
|
343
|
-
ig.append(slotted.before);
|
|
344
|
-
} else if (slotted.ibefore) {
|
|
345
|
-
const igt = document.createElement('div');
|
|
346
|
-
igt.classList.add('input-group-text');
|
|
347
|
-
igt.append(slotted.ibefore);
|
|
348
|
-
ig.append(igt);
|
|
349
|
-
}
|
|
350
|
-
ig.append(ff);
|
|
351
|
-
if (slotted.after) {
|
|
352
|
-
ig.append(slotted.after);
|
|
353
|
-
} else if (slotted.iafter) {
|
|
354
|
-
const igt = document.createElement('div');
|
|
355
|
-
igt.classList.add('input-group-text');
|
|
356
|
-
igt.append(slotted.iafter);
|
|
357
|
-
ig.append(igt);
|
|
358
|
-
}
|
|
359
|
-
ig.append(ffe);
|
|
360
|
-
return ig;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
<label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
364
|
-
<div class="input-group has-validation">
|
|
365
|
-
<span data-tpl-if="slotted.before" class="input-group-text">{{{{ slotted.before }}}}</span>
|
|
366
|
-
{{{{ slotted.input }}}}
|
|
367
|
-
<span data-tpl-if="slotted.after" class="input-group-text">{{{{ slotted.after }}}}</span>
|
|
368
|
-
<ful-field-error data-tpl-field="name"></ful-field-error>
|
|
369
|
-
</div>
|
|
370
|
-
*/
|
|
371
|
-
|
|
372
|
-
const label = document.createElement("label");
|
|
373
|
-
label.setAttribute("for", name);
|
|
374
|
-
label.classList.add('form-label');
|
|
375
|
-
label.append(slotted.default);
|
|
376
|
-
|
|
377
|
-
const ffe = document.createElement('ful-field-error');
|
|
378
|
-
ffe.setAttribute("field", name);
|
|
379
|
-
|
|
380
|
-
const ig = document.createElement("div");
|
|
381
|
-
ig.classList.add('input-group', 'has-validation');
|
|
382
|
-
|
|
383
|
-
if (slotted.before) {
|
|
384
|
-
ig.append(slotted.before);
|
|
385
|
-
} else if (slotted.ibefore) {
|
|
386
|
-
const igt = document.createElement('div');
|
|
387
|
-
igt.classList.add('input-group-text');
|
|
388
|
-
igt.append(slotted.ibefore);
|
|
389
|
-
ig.append(igt);
|
|
390
|
-
}
|
|
391
|
-
ig.append(slotted.input);
|
|
392
|
-
if (slotted.after) {
|
|
393
|
-
ig.append(slotted.after);
|
|
394
|
-
} else if (slotted.iafter) {
|
|
395
|
-
const igt = document.createElement('div');
|
|
396
|
-
igt.classList.add('input-group-text');
|
|
397
|
-
igt.append(slotted.iafter);
|
|
398
|
-
ig.append(igt);
|
|
399
|
-
}
|
|
400
|
-
ig.append(ffe);
|
|
401
|
-
|
|
402
|
-
const fragment = new DocumentFragment();
|
|
403
|
-
fragment.append(label, ig);
|
|
404
|
-
return fragment;
|
|
287
|
+
remove(k) {
|
|
288
|
+
this.storage.removeItem(`${this.prefix}-${k}`);
|
|
289
|
+
}
|
|
290
|
+
pop(k) {
|
|
291
|
+
const decoded = this.load(k);
|
|
292
|
+
this.remove(k);
|
|
293
|
+
return decoded;
|
|
405
294
|
}
|
|
406
|
-
|
|
407
295
|
}
|
|
408
296
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
super();
|
|
413
|
-
}
|
|
414
|
-
connectedCallback() {
|
|
415
|
-
this.classList.add('invalid-feedback');
|
|
297
|
+
class LocalStorage extends Storage {
|
|
298
|
+
constructor(prefix) {
|
|
299
|
+
super(prefix, localStorage);
|
|
416
300
|
}
|
|
417
|
-
|
|
418
|
-
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
class SessionStorage extends Storage {
|
|
304
|
+
constructor(prefix) {
|
|
305
|
+
super(prefix, sessionStorage);
|
|
419
306
|
}
|
|
420
307
|
}
|
|
421
308
|
|
|
422
|
-
class
|
|
423
|
-
constructor()
|
|
424
|
-
|
|
309
|
+
class VersionedStorage {
|
|
310
|
+
constructor(storage, key, dataSupplier){
|
|
311
|
+
this.storage = storage;
|
|
312
|
+
this.key = key;
|
|
313
|
+
this.dataSupplier = dataSupplier;
|
|
314
|
+
this.cache = null;
|
|
315
|
+
|
|
425
316
|
}
|
|
426
|
-
|
|
427
|
-
this.
|
|
317
|
+
async load(revision){
|
|
318
|
+
const saved = this.storage.load(this.key);
|
|
319
|
+
if (!!saved && saved.revision === revision) {
|
|
320
|
+
this.cache = saved.value;
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const freshData = await this.dataSupplier(revision, this.key);
|
|
324
|
+
this.storage.save(this.key, {
|
|
325
|
+
revision: revision,
|
|
326
|
+
value: freshData
|
|
327
|
+
});
|
|
328
|
+
this.cache = freshData;
|
|
428
329
|
}
|
|
429
|
-
|
|
430
|
-
|
|
330
|
+
data(){
|
|
331
|
+
return this.cache;
|
|
431
332
|
}
|
|
432
|
-
|
|
433
333
|
}
|
|
434
334
|
|
|
435
|
-
class
|
|
436
|
-
|
|
437
|
-
|
|
335
|
+
class AuthorizationCodeFlow {
|
|
336
|
+
static forKeycloak(clientId, realmBaseUrl, redirectUri){
|
|
337
|
+
const scope = "openid profile";
|
|
338
|
+
return new AuthorizationCodeFlow(clientId, scope, {
|
|
339
|
+
auth: new URL("protocol/openid-connect/auth", realmBaseUrl),
|
|
340
|
+
token: new URL("protocol/openid-connect/token", realmBaseUrl),
|
|
341
|
+
logout: new URL("protocol/openid-connect/logout", realmBaseUrl),
|
|
342
|
+
registration: new URL("protocol/openid-connect/registrations", realmBaseUrl),
|
|
343
|
+
redirect: redirectUri
|
|
344
|
+
});
|
|
438
345
|
}
|
|
439
|
-
|
|
440
|
-
this.
|
|
441
|
-
this.
|
|
346
|
+
constructor(clientId, scope, {auth, token, registration, logout, redirect}) {
|
|
347
|
+
this.storage = new SessionStorage(clientId);
|
|
348
|
+
this.clientId = clientId;
|
|
349
|
+
this.scope = scope;
|
|
350
|
+
this.uri = {auth, token, registration, logout, redirect};
|
|
442
351
|
}
|
|
443
|
-
|
|
444
|
-
|
|
352
|
+
async action(uri, additionalParams){
|
|
353
|
+
const pkceVerifier = Base64.encode(crypto.getRandomValues(new Uint8Array(32)).buffer);
|
|
354
|
+
const pkceChallenge = Base64.encode(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(pkceVerifier)));
|
|
355
|
+
const state = this.clientId + Base64.encode(crypto.getRandomValues(new Uint8Array(16)).buffer);
|
|
356
|
+
this.storage.save(AuthorizationCodeFlow.PKCE_AND_STATE_KEY, {
|
|
357
|
+
state: state,
|
|
358
|
+
verifier: pkceVerifier
|
|
359
|
+
});
|
|
360
|
+
const url = new URL(uri);
|
|
361
|
+
url.searchParams.set("client_id", this.clientId);
|
|
362
|
+
url.searchParams.set("redirect_uri", this.uri.redirect);
|
|
363
|
+
url.searchParams.set("response_type", 'code');
|
|
364
|
+
url.searchParams.set("scope", this.scope);
|
|
365
|
+
url.searchParams.set("state", state);
|
|
366
|
+
url.searchParams.set("code_challenge", pkceChallenge);
|
|
367
|
+
url.searchParams.set("code_challenge_method", 'S256');
|
|
368
|
+
Object.entries(additionalParams || {}).forEach(kv => {
|
|
369
|
+
url.searchParams.set(kv[0], kv[1]);
|
|
370
|
+
});
|
|
371
|
+
window.location = url;
|
|
445
372
|
}
|
|
446
|
-
|
|
447
|
-
this.
|
|
373
|
+
async registration(additionalParams){
|
|
374
|
+
await this.action(this.uri.registration, additionalParams);
|
|
448
375
|
}
|
|
449
|
-
|
|
450
|
-
|
|
376
|
+
async applicationInitiatedAction(kcAction){
|
|
377
|
+
await this.action(this.uri.auth, {
|
|
378
|
+
kc_action: kcAction
|
|
379
|
+
});
|
|
451
380
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
381
|
+
async _tokenExchange(code, state) {
|
|
382
|
+
window.history.replaceState('', "", this.uri.redirect);
|
|
383
|
+
const stateAndVerifier = this.storage.pop(AuthorizationCodeFlow.PKCE_AND_STATE_KEY);
|
|
384
|
+
if (stateAndVerifier.state !== state) {
|
|
385
|
+
throw new Error("State mismatch");
|
|
386
|
+
}
|
|
387
|
+
const response = await fetch(this.uri.token, {
|
|
388
|
+
method: "POST",
|
|
389
|
+
headers: {
|
|
390
|
+
"Content-Type": 'application/x-www-form-urlencoded'
|
|
391
|
+
},
|
|
392
|
+
body: new URLSearchParams([
|
|
393
|
+
["client_id", this.clientId],
|
|
394
|
+
["code", code],
|
|
395
|
+
["grant_type", "authorization_code"],
|
|
396
|
+
["code_verifier", stateAndVerifier.verifier],
|
|
397
|
+
["state", stateAndVerifier.state],
|
|
398
|
+
["redirect_uri", this.uri.redirect]
|
|
399
|
+
])
|
|
400
|
+
});
|
|
401
|
+
if (!response.ok) {
|
|
402
|
+
const text = await response.text();
|
|
403
|
+
throw new Error("Error:" + response.status + ": " + text);
|
|
404
|
+
}
|
|
405
|
+
const token = await response.json();
|
|
406
|
+
return new AuthorizationCodeFlowSession(this.clientId, token, this.uri);
|
|
476
407
|
}
|
|
477
|
-
|
|
478
|
-
|
|
408
|
+
async ensureLoggedIn() {
|
|
409
|
+
const url = new URL(window.location.href);
|
|
410
|
+
const code = url.searchParams.get("code");
|
|
411
|
+
if (code && this.storage.load(AuthorizationCodeFlow.PKCE_AND_STATE_KEY)) {
|
|
412
|
+
//if callback from keycloak and we have our state still stored
|
|
413
|
+
const state = url.searchParams.get("state");
|
|
414
|
+
return await this._tokenExchange(code, state);
|
|
415
|
+
}
|
|
416
|
+
//if not authorized
|
|
417
|
+
await this.action(this.uri.auth, {});
|
|
418
|
+
return null;
|
|
479
419
|
}
|
|
480
420
|
}
|
|
421
|
+
AuthorizationCodeFlow.PKCE_AND_STATE_KEY = "state-and-verifier";
|
|
481
422
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
})();
|
|
500
|
-
CustomElements.forwardAttributes(this, slotted.input, ['@floating', '@remote']);
|
|
501
|
-
const attrIfMissing = (el, k, v) => !el.hasAttribute(k) && el.setAttribute(k, v);
|
|
502
|
-
attrIfMissing(slotted.input, "name", id);
|
|
503
|
-
attrIfMissing(slotted.input, "id", id);
|
|
504
|
-
attrIfMissing(slotted.input, "placeholder", " ");
|
|
505
|
-
this.innerHTML = '';
|
|
506
|
-
this.append(CustomElements.labelAndInputGroup(id, name || id, floating, slotted));
|
|
507
|
-
this.loaded = !remote;
|
|
508
|
-
this.ts = new TomSelect(slotted.input, Object.assign(remote ? {
|
|
509
|
-
preload: 'focus',
|
|
510
|
-
load: async (query, callback) => {
|
|
511
|
-
if (this.loaded) {
|
|
512
|
-
callback();
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
const data = await this.fire('load', query, []);
|
|
516
|
-
this.loaded = true;
|
|
517
|
-
callback(data);
|
|
518
|
-
}
|
|
519
|
-
} : {}, tsConfig));
|
|
520
|
-
slotted.input.setValue = this.setValue.bind(this);
|
|
521
|
-
slotted.input.getValue = this.getValue.bind(this);
|
|
522
|
-
}
|
|
523
|
-
async setValue(v){
|
|
524
|
-
if(!this.loaded){
|
|
525
|
-
await this.ts.load();
|
|
526
|
-
}
|
|
527
|
-
this.ts.setValue(v);
|
|
423
|
+
class AuthorizationCodeFlowSession {
|
|
424
|
+
static parseToken(token) {
|
|
425
|
+
const [rawHeader, rawPayload, signature] = token.split(".");
|
|
426
|
+
const ut8decoder = new TextDecoder("utf-8");
|
|
427
|
+
return {
|
|
428
|
+
header: JSON.parse(ut8decoder.decode(Base64.decode(rawHeader, Base64.STANDARD))),
|
|
429
|
+
payload: JSON.parse(ut8decoder.decode(Base64.decode(rawPayload, Base64.STANDARD))),
|
|
430
|
+
signature: signature
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
constructor(clientId, t, {token, logout, redirect}) {
|
|
434
|
+
this.clientId = clientId;
|
|
435
|
+
this.token = t;
|
|
436
|
+
this.accessToken = AuthorizationCodeFlowSession.parseToken(t.access_token);
|
|
437
|
+
this.refreshToken = AuthorizationCodeFlowSession.parseToken(t.refresh_token);
|
|
438
|
+
this.uri = { token, logout, redirect };
|
|
439
|
+
this.refreshCallback = null;
|
|
528
440
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
return v === '' ? null : v;
|
|
441
|
+
onRefresh(callback) {
|
|
442
|
+
this.refreshCallback = callback;
|
|
532
443
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
444
|
+
async refresh() {
|
|
445
|
+
const response = await fetch(this.uri.token, {
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: {
|
|
448
|
+
"Content-Type": 'application/x-www-form-urlencoded'
|
|
449
|
+
},
|
|
450
|
+
body: new URLSearchParams([
|
|
451
|
+
["client_id", this.clientId],
|
|
452
|
+
["grant_type", "refresh_token"],
|
|
453
|
+
["refresh_token", this.token.refresh_token]
|
|
454
|
+
])
|
|
538
455
|
});
|
|
456
|
+
if (!response.ok) {
|
|
457
|
+
throw new Error("Error:" + response.status + ": " + response.text());
|
|
458
|
+
}
|
|
459
|
+
const token = await response.json();
|
|
460
|
+
this.token = token;
|
|
461
|
+
this.accessToken = AuthorizationCodeFlowSession.parseToken(token.access_token);
|
|
462
|
+
this.refreshToken = AuthorizationCodeFlowSession.parseToken(token.refresh_token);
|
|
463
|
+
if (this.refreshCallback) {
|
|
464
|
+
this.refreshCallback(this.token, this.accessToken, this.refreshToken);
|
|
465
|
+
}
|
|
539
466
|
}
|
|
540
|
-
|
|
541
|
-
|
|
467
|
+
shouldBeRefreshed(gracePeriod) {
|
|
468
|
+
const now = new Date().getTime();
|
|
469
|
+
const refreshTokenExpiresAt = this.refreshToken.payload.exp * 1000;
|
|
470
|
+
const expired = now > refreshTokenExpiresAt;
|
|
471
|
+
const shouldRefresh = now - gracePeriod > refreshTokenExpiresAt;
|
|
472
|
+
return !expired && shouldRefresh;
|
|
473
|
+
}
|
|
474
|
+
async refreshIf(gracePeriod) {
|
|
475
|
+
if (!this.shouldBeRefreshed(gracePeriod)) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
await this.refresh();
|
|
479
|
+
}
|
|
480
|
+
logout() {
|
|
481
|
+
const url = new URL(this.uri.logout);
|
|
482
|
+
url.searchParams.set("post_logout_redirect_uri", this.uri.redirect);
|
|
483
|
+
url.searchParams.set("id_token_hint", this.token.id_token);
|
|
484
|
+
window.location = url;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
bearerToken() {
|
|
488
|
+
return `Bearer ${this.token.access_token}`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
interceptor(gracePeriodBefore, gracePeriodAfter){
|
|
492
|
+
return new AuthorizationCodeFlowInterceptor(this, gracePeriodBefore, gracePeriodAfter);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
class AuthorizationCodeFlowInterceptor {
|
|
497
|
+
constructor(session, gracePeriodBefore, gracePeriodAfter) {
|
|
498
|
+
this.session = session;
|
|
499
|
+
this.gracePeriodBefore = gracePeriodBefore || 2000;
|
|
500
|
+
this.gracePeriodAfter = gracePeriodAfter || 30000;
|
|
501
|
+
}
|
|
502
|
+
async intercept(request, chain) {
|
|
503
|
+
await this.session.refreshIf(this.gracePeriodBefore);
|
|
504
|
+
const headers = new Headers(request.options.headers);
|
|
505
|
+
headers.set("Authorization", this.session.bearerToken());
|
|
506
|
+
request.options.headers = headers;
|
|
507
|
+
const response = await chain.proceed(request);
|
|
508
|
+
await this.session.refreshIf(this.gracePeriodAfter);
|
|
509
|
+
return response;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const timing = {
|
|
514
|
+
sleep(ms) {
|
|
515
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
516
|
+
},
|
|
517
|
+
DEBOUNCE_DEFAULT: 0,
|
|
518
|
+
DEBOUNCE_IMMEDIATE: 1,
|
|
519
|
+
debounce(timeoutMs, func, options) {
|
|
520
|
+
let tid = null;
|
|
521
|
+
let args = [];
|
|
522
|
+
let previousTimestamp = 0;
|
|
523
|
+
let opts = options || timing.DEBOUNCE_DEFAULT;
|
|
524
|
+
|
|
525
|
+
const later = () => {
|
|
526
|
+
const elapsed = new Date().getTime() - previousTimestamp;
|
|
527
|
+
if (timeoutMs > elapsed) {
|
|
528
|
+
tid = setTimeout(later, timeoutMs - elapsed);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
tid = null;
|
|
532
|
+
if (opts !== timing.DEBOUNCE_IMMEDIATE) {
|
|
533
|
+
func(...args);
|
|
534
|
+
}
|
|
535
|
+
// This check is needed because `func` can recursively invoke `debounced`.
|
|
536
|
+
if (tid === null) {
|
|
537
|
+
args = [];
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
return function () {
|
|
542
|
+
args = arguments;
|
|
543
|
+
previousTimestamp = new Date().getTime();
|
|
544
|
+
if (tid === null) {
|
|
545
|
+
tid = setTimeout(later, timeoutMs);
|
|
546
|
+
if (opts === timing.DEBOUNCE_IMMEDIATE) {
|
|
547
|
+
func(...args);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
},
|
|
552
|
+
THROTTLE_DEFAULT: 0,
|
|
553
|
+
THROTTLE_NO_LEADING: 1,
|
|
554
|
+
THROTTLE_NO_TRAILING: 2,
|
|
555
|
+
throttle(timeoutMs, func, options) {
|
|
556
|
+
let tid = null;
|
|
557
|
+
let args = [];
|
|
558
|
+
let previousTimestamp = 0;
|
|
559
|
+
let opts = options || timing.THROTTLE_DEFAULT;
|
|
560
|
+
|
|
561
|
+
const later = () => {
|
|
562
|
+
previousTimestamp = (opts & timing.THROTTLE_NO_LEADING) ? 0 : new Date().getTime();
|
|
563
|
+
tid = null;
|
|
564
|
+
func(...args);
|
|
565
|
+
if (tid === null) {
|
|
566
|
+
args = [];
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
return function () {
|
|
571
|
+
const now = new Date().getTime();
|
|
572
|
+
if (!previousTimestamp && (opts & timing.THROTTLE_NO_LEADING)) {
|
|
573
|
+
previousTimestamp = now;
|
|
574
|
+
}
|
|
575
|
+
const remaining = timeoutMs - (now - previousTimestamp);
|
|
576
|
+
args = arguments;
|
|
577
|
+
if (remaining <= 0 || remaining > timeoutMs) {
|
|
578
|
+
if (tid !== null) {
|
|
579
|
+
clearTimeout(tid);
|
|
580
|
+
tid = null;
|
|
581
|
+
}
|
|
582
|
+
previousTimestamp = now;
|
|
583
|
+
func(...args);
|
|
584
|
+
if (tid === null) {
|
|
585
|
+
args = [];
|
|
586
|
+
}
|
|
587
|
+
} else if (tid === null && !(opts & timing.THROTTLE_NO_TRAILING)) {
|
|
588
|
+
tid = setTimeout(later, remaining);
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
class Fragments {
|
|
596
|
+
static fromHtml(...html) {
|
|
597
|
+
const el = document.createElement('div');
|
|
598
|
+
el.innerHTML = html.join("");
|
|
599
|
+
const fragment = new DocumentFragment();
|
|
600
|
+
Array.from(el.childNodes).forEach(node => {
|
|
601
|
+
fragment.appendChild(node);
|
|
602
|
+
});
|
|
603
|
+
return fragment;
|
|
604
|
+
}
|
|
605
|
+
static toHtml(fragment) {
|
|
606
|
+
var r = document.createElement("root");
|
|
607
|
+
r.appendChild(fragment);
|
|
608
|
+
return r.innerHTML;
|
|
609
|
+
}
|
|
610
|
+
static from(...nodes) {
|
|
611
|
+
const fragment = new DocumentFragment();
|
|
612
|
+
for (let i = 0; i !== nodes.length; ++i) {
|
|
613
|
+
fragment.appendChild(nodes[i]);
|
|
614
|
+
}
|
|
615
|
+
return fragment;
|
|
616
|
+
}
|
|
617
|
+
static fromChildNodes(el) {
|
|
618
|
+
const nodes = Array.from(el.childNodes);
|
|
619
|
+
const fragment = new DocumentFragment();
|
|
620
|
+
for (let i = 0; i !== nodes.length; ++i) {
|
|
621
|
+
fragment.appendChild(nodes[i]);
|
|
622
|
+
}
|
|
623
|
+
return fragment;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
class Attributes {
|
|
628
|
+
static id = 0;
|
|
629
|
+
static uid(prefix) {
|
|
630
|
+
return `${prefix}-${++Attributes.id}`;
|
|
631
|
+
}
|
|
632
|
+
static asBoolean(value) {
|
|
633
|
+
return value !== null && value !== undefined && value !== false;
|
|
634
|
+
}
|
|
635
|
+
static defaultValue(el, k, v) {
|
|
636
|
+
if (!el.hasAttribute(k)) {
|
|
637
|
+
el.setAttribute(k, v);
|
|
638
|
+
}
|
|
639
|
+
return el.getAttribute(k);
|
|
640
|
+
}
|
|
641
|
+
static forward(prefix, from, to) {
|
|
642
|
+
from.getAttributeNames()
|
|
643
|
+
.filter(a => a.startsWith(prefix))
|
|
644
|
+
.forEach(a => {
|
|
645
|
+
const target = a.substring(prefix.length);
|
|
646
|
+
if (target === 'class') {
|
|
647
|
+
to.classList.add(...from.getAttribute(prefix + "class").split(" ").filter(a => a.length));
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
to.setAttribute(target, from.getAttribute(a));
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
class Slots {
|
|
656
|
+
static from(el) {
|
|
657
|
+
const slotted = Object.fromEntries(Array.from(el.querySelectorAll("[slot]")).map(el => {
|
|
658
|
+
el.parentElement.removeChild(el);
|
|
659
|
+
const slot = el.getAttribute("slot");
|
|
660
|
+
el.removeAttribute("slot");
|
|
661
|
+
return [slot, el];
|
|
662
|
+
}));
|
|
663
|
+
slotted.default = new DocumentFragment();
|
|
664
|
+
slotted.default.append(...el.childNodes);
|
|
665
|
+
return slotted;
|
|
542
666
|
}
|
|
543
667
|
|
|
544
668
|
}
|
|
545
669
|
|
|
546
|
-
|
|
670
|
+
const Templated = (SuperClass, template) => {
|
|
671
|
+
return class extends SuperClass {
|
|
672
|
+
#rendered;
|
|
673
|
+
async connectedCallback() {
|
|
674
|
+
if (this.#rendered) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const slotted = Slots.from(this);
|
|
678
|
+
const fragment = await Promise.resolve(this.render(slotted, template));
|
|
679
|
+
this.innerHTML = '';
|
|
680
|
+
if (fragment) {
|
|
681
|
+
this.appendChild(fragment);
|
|
682
|
+
}
|
|
683
|
+
this.#rendered = true;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
const Stateful = (SuperClass, flags, others) => {
|
|
689
|
+
|
|
690
|
+
const all = [].concat(flags).concat(others || []);
|
|
691
|
+
|
|
692
|
+
return class extends SuperClass {
|
|
693
|
+
static get observedAttributes() {
|
|
694
|
+
return all;
|
|
695
|
+
}
|
|
696
|
+
constructor(...args) {
|
|
697
|
+
super(...args);
|
|
698
|
+
this.internals_ = this.internals_ || this.attachInternals();
|
|
699
|
+
for (const flag of flags) {
|
|
700
|
+
Object.defineProperty(this, flag, {
|
|
701
|
+
get() {
|
|
702
|
+
return this.hasAttribute(flag);
|
|
703
|
+
},
|
|
704
|
+
set(value) {
|
|
705
|
+
//see https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet#using_double_dash_prefixed_idents
|
|
706
|
+
if (Attributes.asBoolean(value)) {
|
|
707
|
+
this.internals_.states.add(`--${flag}`);
|
|
708
|
+
this.setAttribute(flag, '');
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
this.internals_.states.delete(`--${flag}`);
|
|
712
|
+
this.removeAttribute(flag);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
718
|
+
if (oldValue === newValue) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
this[name] = newValue;
|
|
722
|
+
const method = this[`on${name.charAt(0).toUpperCase()}${name.substr(1).toLowerCase()}Changed`];
|
|
723
|
+
method?.call(this, newValue, oldValue);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
class FieldError extends Templated(HTMLElement) {
|
|
729
|
+
render(slotted, template) {
|
|
730
|
+
this.classList.add('invalid-feedback');
|
|
731
|
+
}
|
|
732
|
+
static configure() {
|
|
733
|
+
customElements.define('ful-field-error', FieldError);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
class Errors extends Templated(HTMLElement) {
|
|
738
|
+
render(slotted, template) {
|
|
739
|
+
this.classList.add('alert', 'alert-danger', 'd-none');
|
|
740
|
+
}
|
|
741
|
+
static configure() {
|
|
742
|
+
customElements.define('ful-errors', Errors);
|
|
743
|
+
}
|
|
547
744
|
|
|
745
|
+
}
|
|
548
746
|
|
|
747
|
+
/* global Infinity, CSS */
|
|
549
748
|
|
|
550
|
-
class Form extends HTMLElement {
|
|
749
|
+
class Form extends Templated(Observable(HTMLElement)) {
|
|
551
750
|
constructor({ mutators, extractors, valueHoldersSelector, ignoredChildrenSelector }) {
|
|
552
751
|
super();
|
|
553
|
-
Observable.init(this);
|
|
554
752
|
this.mutators = mutators || {};
|
|
555
753
|
this.extractors = extractors || {};
|
|
556
754
|
this.valueHoldersSelector = valueHoldersSelector || '[name]';
|
|
557
755
|
this.ignoredChildrenSelector = ignoredChildrenSelector || '.d-none';
|
|
558
|
-
|
|
756
|
+
}
|
|
757
|
+
render(slotted, template) {
|
|
559
758
|
const form = document.createElement('form');
|
|
560
|
-
form.append(
|
|
561
|
-
this.appendChild(form);
|
|
562
|
-
|
|
759
|
+
form.append(slotted.default);
|
|
563
760
|
form.addEventListener('submit', async (e) => {
|
|
564
761
|
e.preventDefault();
|
|
565
762
|
this.spinner(true);
|
|
@@ -575,10 +772,11 @@ var ful = (function (exports) {
|
|
|
575
772
|
this.spinner(false);
|
|
576
773
|
}
|
|
577
774
|
});
|
|
775
|
+
return form;
|
|
578
776
|
}
|
|
579
777
|
spinner(spin) {
|
|
580
778
|
this.querySelectorAll('ful-spinner').forEach(el => {
|
|
581
|
-
el
|
|
779
|
+
el.hidden = !spin;
|
|
582
780
|
});
|
|
583
781
|
this.querySelectorAll('[type=submit],[type=reset]').forEach(el => {
|
|
584
782
|
el.disabled = spin;
|
|
@@ -726,338 +924,214 @@ var ful = (function (exports) {
|
|
|
726
924
|
}
|
|
727
925
|
});
|
|
728
926
|
}
|
|
729
|
-
static configure(configuration) {
|
|
730
|
-
FieldError.configure();
|
|
731
|
-
Errors.configure();
|
|
732
|
-
Spinner.configure();
|
|
733
|
-
Input.configure();
|
|
734
|
-
Select.configure();
|
|
735
|
-
Form.custom('ful-form', configuration || {});
|
|
736
|
-
}
|
|
737
927
|
}
|
|
738
928
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
929
|
+
const ful_input_ec = globalThis.ec || ftl.EvaluationContext.configure({
|
|
930
|
+
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
const ful_input_template_ = globalThis.ful_input_template || ftl.Template.fromHtml(`
|
|
934
|
+
<div data-tpl-if="floating" class="input-group has-validation">
|
|
935
|
+
<span data-tpl-if="slotted.ibefore" class="input-group-text">{{{{ slotted.ibefore }}}}</span>
|
|
936
|
+
<div data-tpl-if="slotted.before" data-tpl-remove="tag">{{{{ slotted.before }}}}</div>
|
|
937
|
+
<div class="form-floating">
|
|
938
|
+
{{{{ slotted.input }}}}
|
|
939
|
+
<label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
940
|
+
</div>
|
|
941
|
+
<div data-tpl-if="slotted.after" data-tpl-remove="tag">{{{{ slotted.after }}}}</div>
|
|
942
|
+
<span data-tpl-if="slotted.iafter" class="input-group-text">{{{{ slotted.iafter }}}}</span>
|
|
943
|
+
<ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
|
|
944
|
+
</div>
|
|
945
|
+
<div data-tpl-if="!floating" data-tpl-remove="tag">
|
|
946
|
+
<label data-tpl-for="id" class="form-label">{{{{ slotted.default }}}}</label>
|
|
947
|
+
<div class="input-group has-validation">
|
|
948
|
+
<span data-tpl-if="slotted.ibefore" class="input-group-text">{{{{ slotted.ibefore }}}}</span>
|
|
949
|
+
<div data-tpl-if="slotted.before" data-tpl-remove="tag">{{{{ slotted.before }}}}</div>
|
|
950
|
+
{{{{ slotted.input }}}}
|
|
951
|
+
<div data-tpl-if="slotted.after" data-tpl-remove="tag">{{{{ slotted.after }}}}</div>
|
|
952
|
+
<span data-tpl-if="slotted.iafter" class="input-group-text">{{{{ slotted.iafter }}}}</span>
|
|
953
|
+
<ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
|
|
954
|
+
</div>
|
|
955
|
+
</div>
|
|
956
|
+
`, ful_input_ec);
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
class Input extends Templated(HTMLElement, ful_input_template_) {
|
|
961
|
+
render(slotted, template) {
|
|
962
|
+
const floating = this.hasAttribute('floating');
|
|
963
|
+
const input = slotted.input = slotted.input || (() => {
|
|
964
|
+
const el = document.createElement("input");
|
|
965
|
+
el.classList.add("form-control");
|
|
966
|
+
return el;
|
|
967
|
+
})();
|
|
968
|
+
const id = input.getAttribute('id') || this.getAttribute('input-id') || Attributes.uid('ful-input');
|
|
969
|
+
Attributes.forward('input-', this, slotted.input);
|
|
970
|
+
Attributes.defaultValue(slotted.input, "id", id);
|
|
971
|
+
Attributes.defaultValue(slotted.input, "type", "text");
|
|
972
|
+
Attributes.defaultValue(slotted.input, "placeholder", " ");
|
|
973
|
+
const name = input.getAttribute('name');
|
|
974
|
+
return template.render({ id, name, floating, slotted });
|
|
755
975
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
this.remove(k);
|
|
759
|
-
return decoded;
|
|
976
|
+
static configure() {
|
|
977
|
+
customElements.define('ful-input', Input);
|
|
760
978
|
}
|
|
761
979
|
}
|
|
762
980
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
981
|
+
/**
|
|
982
|
+
* <script src="tom-select.complete.js"></script>
|
|
983
|
+
* <link href="tom-select.bootstrap5.css" rel="stylesheet" />
|
|
984
|
+
*/
|
|
985
|
+
const ful_select_ec = globalThis.ec || ftl.EvaluationContext.configure({
|
|
986
|
+
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
const ful_select_template_ = globalThis.ful_select_template || ftl.Template.fromHtml(`
|
|
990
|
+
<div data-tpl-if="floating" class="input-group has-validation">
|
|
991
|
+
<span data-tpl-if="slotted.ibefore" class="input-group-text">{{{{ slotted.ibefore }}}}</span>
|
|
992
|
+
<div data-tpl-if="slotted.before" data-tpl-remove="tag">{{{{ slotted.before }}}}</div>
|
|
993
|
+
<div class="form-floating">
|
|
994
|
+
{{{{ slotted.input }}}}
|
|
995
|
+
<label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
996
|
+
</div>
|
|
997
|
+
<div data-tpl-if="slotted.after" data-tpl-remove="tag">{{{{ slotted.after }}}}</div>
|
|
998
|
+
<span data-tpl-if="slotted.iafter" class="input-group-text">{{{{ slotted.iafter }}}}</span>
|
|
999
|
+
<ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
|
|
1000
|
+
</div>
|
|
1001
|
+
<div data-tpl-if="!floating" data-tpl-remove="tag">
|
|
1002
|
+
<label data-tpl-for="id" class="form-label">{{{{ slotted.default }}}}</label>
|
|
1003
|
+
<div class="input-group has-validation">
|
|
1004
|
+
<span data-tpl-if="slotted.ibefore" class="input-group-text">{{{{ slotted.ibefore }}}}</span>
|
|
1005
|
+
<div data-tpl-if="slotted.before" data-tpl-remove="tag">{{{{ slotted.before }}}}</div>
|
|
1006
|
+
{{{{ slotted.input }}}}
|
|
1007
|
+
<div data-tpl-if="slotted.after" data-tpl-remove="tag">{{{{ slotted.after }}}}</div>
|
|
1008
|
+
<span data-tpl-if="slotted.iafter" class="input-group-text">{{{{ slotted.iafter }}}}</span>
|
|
1009
|
+
<ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
|
|
1010
|
+
</div>
|
|
1011
|
+
</div>
|
|
1012
|
+
`, ful_select_ec);
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
class Select extends Templated(Observable(HTMLElement), ful_select_template_) {
|
|
1016
|
+
constructor(tsConfig) {
|
|
1017
|
+
super();
|
|
1018
|
+
this.tsConfig = tsConfig;
|
|
766
1019
|
}
|
|
767
|
-
|
|
1020
|
+
render(slotted, template) {
|
|
1021
|
+
const floating = this.hasAttribute('floating');
|
|
1022
|
+
const remote = this.hasAttribute('remote');
|
|
1023
|
+
const input = slotted.input = slotted.input || (() => {
|
|
1024
|
+
return document.createElement("select");
|
|
1025
|
+
})();
|
|
1026
|
+
const id = input.getAttribute('id') || this.getAttribute('input-id') || Attributes.uid('ful-select');
|
|
1027
|
+
Attributes.forward('input-', this, input);
|
|
1028
|
+
Attributes.defaultValue(input, "id", id);
|
|
1029
|
+
Attributes.defaultValue(input, "placeholder", " ");
|
|
1030
|
+
const name = input.getAttribute('name');
|
|
1031
|
+
input.setValue = this.setValue.bind(this);
|
|
1032
|
+
input.getValue = this.getValue.bind(this);
|
|
1033
|
+
|
|
1034
|
+
//tomselect needs the input to have a parent.
|
|
1035
|
+
//se we move the input to a fragment
|
|
1036
|
+
slotted.input = Fragments.from(input);
|
|
768
1037
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1038
|
+
this.loaded = !remote;
|
|
1039
|
+
this.ts = new TomSelect(input, Object.assign(remote ? {
|
|
1040
|
+
preload: 'focus',
|
|
1041
|
+
load: async (query, callback) => {
|
|
1042
|
+
if (this.loaded) {
|
|
1043
|
+
callback();
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const data = await this.fire('load', query, []);
|
|
1047
|
+
this.loaded = true;
|
|
1048
|
+
callback(data);
|
|
1049
|
+
}
|
|
1050
|
+
} : {}, this.tsConfig));
|
|
774
1051
|
|
|
775
|
-
|
|
776
|
-
constructor(storage, key, dataSupplier){
|
|
777
|
-
this.storage = storage;
|
|
778
|
-
this.key = key;
|
|
779
|
-
this.dataSupplier = dataSupplier;
|
|
780
|
-
this.cache = null;
|
|
781
|
-
|
|
1052
|
+
return template.render({ id, name, floating, slotted });
|
|
782
1053
|
}
|
|
783
|
-
async
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
this.cache = saved.value;
|
|
787
|
-
return;
|
|
1054
|
+
async setValue(v) {
|
|
1055
|
+
if (!this.loaded) {
|
|
1056
|
+
await this.ts.load();
|
|
788
1057
|
}
|
|
789
|
-
|
|
790
|
-
this.storage.save(this.key, {
|
|
791
|
-
revision: revision,
|
|
792
|
-
value: freshData
|
|
793
|
-
});
|
|
794
|
-
this.cache = freshData;
|
|
795
|
-
}
|
|
796
|
-
data(){
|
|
797
|
-
return this.cache;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
class AuthorizationCodeFlow {
|
|
802
|
-
static forKeycloak(clientId, realmBaseUrl, redirectUri){
|
|
803
|
-
const scope = "openid profile";
|
|
804
|
-
return new AuthorizationCodeFlow(clientId, scope, {
|
|
805
|
-
auth: new URL("protocol/openid-connect/auth", realmBaseUrl),
|
|
806
|
-
token: new URL("protocol/openid-connect/token", realmBaseUrl),
|
|
807
|
-
logout: new URL("protocol/openid-connect/logout", realmBaseUrl),
|
|
808
|
-
registration: new URL("protocol/openid-connect/registrations", realmBaseUrl),
|
|
809
|
-
redirect: redirectUri
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
constructor(clientId, scope, {auth, token, registration, logout, redirect}) {
|
|
813
|
-
this.storage = new SessionStorage(clientId);
|
|
814
|
-
this.clientId = clientId;
|
|
815
|
-
this.scope = scope;
|
|
816
|
-
this.uri = {auth, token, registration, logout, redirect};
|
|
817
|
-
}
|
|
818
|
-
async action(uri, additionalParams){
|
|
819
|
-
const pkceVerifier = Base64.encode(crypto.getRandomValues(new Uint8Array(32)).buffer);
|
|
820
|
-
const pkceChallenge = Base64.encode(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(pkceVerifier)));
|
|
821
|
-
const state = this.clientId + Base64.encode(crypto.getRandomValues(new Uint8Array(16)).buffer);
|
|
822
|
-
this.storage.save(AuthorizationCodeFlow.PKCE_AND_STATE_KEY, {
|
|
823
|
-
state: state,
|
|
824
|
-
verifier: pkceVerifier
|
|
825
|
-
});
|
|
826
|
-
const url = new URL(uri);
|
|
827
|
-
url.searchParams.set("client_id", this.clientId);
|
|
828
|
-
url.searchParams.set("redirect_uri", this.uri.redirect);
|
|
829
|
-
url.searchParams.set("response_type", 'code');
|
|
830
|
-
url.searchParams.set("scope", this.scope);
|
|
831
|
-
url.searchParams.set("state", state);
|
|
832
|
-
url.searchParams.set("code_challenge", pkceChallenge);
|
|
833
|
-
url.searchParams.set("code_challenge_method", 'S256');
|
|
834
|
-
Object.entries(additionalParams || {}).forEach(kv => {
|
|
835
|
-
url.searchParams.set(kv[0], kv[1]);
|
|
836
|
-
});
|
|
837
|
-
window.location = url;
|
|
838
|
-
}
|
|
839
|
-
async registration(additionalParams){
|
|
840
|
-
await this.action(this.uri.registration, additionalParams);
|
|
1058
|
+
this.ts.setValue(v);
|
|
841
1059
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
});
|
|
1060
|
+
getValue() {
|
|
1061
|
+
const v = this.ts.getValue();
|
|
1062
|
+
return v === '' ? null : v;
|
|
846
1063
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
}
|
|
853
|
-
const response = await fetch(this.uri.token, {
|
|
854
|
-
method: "POST",
|
|
855
|
-
headers: {
|
|
856
|
-
"Content-Type": 'application/x-www-form-urlencoded'
|
|
857
|
-
},
|
|
858
|
-
body: new URLSearchParams([
|
|
859
|
-
["client_id", this.clientId],
|
|
860
|
-
["code", code],
|
|
861
|
-
["grant_type", "authorization_code"],
|
|
862
|
-
["code_verifier", stateAndVerifier.verifier],
|
|
863
|
-
["state", stateAndVerifier.state],
|
|
864
|
-
["redirect_uri", this.uri.redirect]
|
|
865
|
-
])
|
|
1064
|
+
static custom(tagName, configuration) {
|
|
1065
|
+
customElements.define(tagName, class extends Select {
|
|
1066
|
+
constructor() {
|
|
1067
|
+
super(configuration);
|
|
1068
|
+
}
|
|
866
1069
|
});
|
|
867
|
-
if (!response.ok) {
|
|
868
|
-
const text = await response.text();
|
|
869
|
-
throw new Error("Error:" + response.status + ": " + text);
|
|
870
|
-
}
|
|
871
|
-
const token = await response.json();
|
|
872
|
-
return new AuthorizationCodeFlowSession(this.clientId, token, this.uri);
|
|
873
1070
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
const code = url.searchParams.get("code");
|
|
877
|
-
if (code && this.storage.load(AuthorizationCodeFlow.PKCE_AND_STATE_KEY)) {
|
|
878
|
-
//if callback from keycloak and we have our state still stored
|
|
879
|
-
const state = url.searchParams.get("state");
|
|
880
|
-
return await this._tokenExchange(code, state);
|
|
881
|
-
}
|
|
882
|
-
//if not authorized
|
|
883
|
-
await this.action(this.uri.auth, {});
|
|
884
|
-
return null;
|
|
1071
|
+
static configure() {
|
|
1072
|
+
return Select.custom('ful-select');
|
|
885
1073
|
}
|
|
1074
|
+
|
|
886
1075
|
}
|
|
887
|
-
AuthorizationCodeFlow.PKCE_AND_STATE_KEY = "state-and-verifier";
|
|
888
1076
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
]
|
|
1077
|
+
const ful_radiogroup_ec = globalThis.ec || ftl.EvaluationContext.configure({
|
|
1078
|
+
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
const ful_radiougroup_template_ = globalThis.ful_radiogroup_template || ftl.Template.fromHtml(`
|
|
1082
|
+
<fieldset>
|
|
1083
|
+
<legend class="form-label">
|
|
1084
|
+
{{{{ slotted.default }}}}
|
|
1085
|
+
</legend>
|
|
1086
|
+
<section>
|
|
1087
|
+
<label data-tpl-each="inputsAndLabels" data-tpl-var="ial">
|
|
1088
|
+
{{{{ ial[0] }}}}
|
|
1089
|
+
{{{{ ial[1] }}}}
|
|
1090
|
+
</label>
|
|
1091
|
+
</section>
|
|
1092
|
+
<ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
|
|
1093
|
+
</fieldset>
|
|
1094
|
+
`, ful_radiogroup_ec);
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
class RadioGroup extends Stateful(Templated(HTMLElement, ful_radiougroup_template_), ['readonly']) {
|
|
1098
|
+
render(slotted, template) {
|
|
1099
|
+
const name = this.getAttribute('input-name') || Attributes.uid('ful-radiogroup');
|
|
1100
|
+
const radioEls = Array.from(slotted.default.querySelectorAll('ful-radio'));
|
|
1101
|
+
const inputsAndLabels = radioEls.map(el => {
|
|
1102
|
+
const input = document.createElement('input');
|
|
1103
|
+
input.setAttribute('type', 'radio');
|
|
1104
|
+
Attributes.forward('input-', this, input);
|
|
1105
|
+
Attributes.forward('', el, input);
|
|
1106
|
+
Attributes.defaultValue(input, 'name', name);
|
|
1107
|
+
const label = Fragments.fromChildNodes(el);
|
|
1108
|
+
return [input, label];
|
|
921
1109
|
});
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
this.refreshCallback(this.token, this.accessToken, this.refreshToken);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
shouldBeRefreshed(gracePeriod) {
|
|
934
|
-
const now = new Date().getTime();
|
|
935
|
-
const refreshTokenExpiresAt = this.refreshToken.payload.exp * 1000;
|
|
936
|
-
const expired = now > refreshTokenExpiresAt;
|
|
937
|
-
const shouldRefresh = now - gracePeriod > refreshTokenExpiresAt;
|
|
938
|
-
return !expired && shouldRefresh;
|
|
939
|
-
}
|
|
940
|
-
async refreshIf(gracePeriod) {
|
|
941
|
-
if (!this.shouldBeRefreshed(gracePeriod)) {
|
|
942
|
-
return;
|
|
943
|
-
}
|
|
944
|
-
await this.refresh();
|
|
945
|
-
}
|
|
946
|
-
logout() {
|
|
947
|
-
const url = new URL(this.uri.logout);
|
|
948
|
-
url.searchParams.set("post_logout_redirect_uri", this.uri.redirect);
|
|
949
|
-
url.searchParams.set("id_token_hint", this.token.id_token);
|
|
950
|
-
window.location = url;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
bearerToken() {
|
|
954
|
-
return `Bearer ${this.token.access_token}`;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
interceptor(gracePeriodBefore, gracePeriodAfter){
|
|
958
|
-
return new AuthorizationCodeFlowInterceptor(this, gracePeriodBefore, gracePeriodAfter);
|
|
1110
|
+
radioEls.forEach(el => el.remove());
|
|
1111
|
+
|
|
1112
|
+
const fragment = template.render({
|
|
1113
|
+
name: name,
|
|
1114
|
+
slotted: slotted,
|
|
1115
|
+
inputsAndLabels: inputsAndLabels
|
|
1116
|
+
});
|
|
1117
|
+
return fragment;
|
|
959
1118
|
}
|
|
1119
|
+
static configure() {
|
|
1120
|
+
customElements.define('ful-radio-group', RadioGroup);
|
|
1121
|
+
}
|
|
960
1122
|
}
|
|
961
1123
|
|
|
962
|
-
class
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1124
|
+
class Spinner extends Templated(HTMLElement) {
|
|
1125
|
+
render(slotted, template) {
|
|
1126
|
+
return Fragments.fromHtml(`
|
|
1127
|
+
<div class="spinner-border spinner-border-sm" aria-hidden="true"></div>
|
|
1128
|
+
`);
|
|
967
1129
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const headers = new Headers(request.options.headers);
|
|
971
|
-
headers.set("Authorization", this.session.bearerToken());
|
|
972
|
-
request.options.headers = headers;
|
|
973
|
-
const response = await chain.proceed(request);
|
|
974
|
-
await this.session.refreshIf(this.gracePeriodAfter);
|
|
975
|
-
return response;
|
|
1130
|
+
static configure() {
|
|
1131
|
+
customElements.define('ful-spinner', Spinner);
|
|
976
1132
|
}
|
|
977
1133
|
}
|
|
978
1134
|
|
|
979
|
-
const timing = {
|
|
980
|
-
sleep(ms) {
|
|
981
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
982
|
-
},
|
|
983
|
-
DEBOUNCE_DEFAULT: 0,
|
|
984
|
-
DEBOUNCE_IMMEDIATE: 1,
|
|
985
|
-
debounce(timeoutMs, func, options) {
|
|
986
|
-
let tid = null;
|
|
987
|
-
let args = [];
|
|
988
|
-
let previousTimestamp = 0;
|
|
989
|
-
let opts = options || timing.DEBOUNCE_DEFAULT;
|
|
990
|
-
|
|
991
|
-
const later = () => {
|
|
992
|
-
const elapsed = new Date().getTime() - previousTimestamp;
|
|
993
|
-
if (timeoutMs > elapsed) {
|
|
994
|
-
tid = setTimeout(later, timeoutMs - elapsed);
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
tid = null;
|
|
998
|
-
if (opts !== timing.DEBOUNCE_IMMEDIATE) {
|
|
999
|
-
func(...args);
|
|
1000
|
-
}
|
|
1001
|
-
// This check is needed because `func` can recursively invoke `debounced`.
|
|
1002
|
-
if (tid === null) {
|
|
1003
|
-
args = [];
|
|
1004
|
-
}
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
return function () {
|
|
1008
|
-
args = arguments;
|
|
1009
|
-
previousTimestamp = new Date().getTime();
|
|
1010
|
-
if (tid === null) {
|
|
1011
|
-
tid = setTimeout(later, timeoutMs);
|
|
1012
|
-
if (opts === timing.DEBOUNCE_IMMEDIATE) {
|
|
1013
|
-
func(...args);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
};
|
|
1017
|
-
},
|
|
1018
|
-
THROTTLE_DEFAULT: 0,
|
|
1019
|
-
THROTTLE_NO_LEADING: 1,
|
|
1020
|
-
THROTTLE_NO_TRAILING: 2,
|
|
1021
|
-
throttle(timeoutMs, func, options) {
|
|
1022
|
-
let tid = null;
|
|
1023
|
-
let args = [];
|
|
1024
|
-
let previousTimestamp = 0;
|
|
1025
|
-
let opts = options || timing.THROTTLE_DEFAULT;
|
|
1026
|
-
|
|
1027
|
-
const later = () => {
|
|
1028
|
-
previousTimestamp = (opts & timing.THROTTLE_NO_LEADING) ? 0 : new Date().getTime();
|
|
1029
|
-
tid = null;
|
|
1030
|
-
func(...args);
|
|
1031
|
-
if (tid === null) {
|
|
1032
|
-
args = [];
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
return function () {
|
|
1037
|
-
const now = new Date().getTime();
|
|
1038
|
-
if (!previousTimestamp && (opts & timing.THROTTLE_NO_LEADING)) {
|
|
1039
|
-
previousTimestamp = now;
|
|
1040
|
-
}
|
|
1041
|
-
const remaining = timeoutMs - (now - previousTimestamp);
|
|
1042
|
-
args = arguments;
|
|
1043
|
-
if (remaining <= 0 || remaining > timeoutMs) {
|
|
1044
|
-
if (tid !== null) {
|
|
1045
|
-
clearTimeout(tid);
|
|
1046
|
-
tid = null;
|
|
1047
|
-
}
|
|
1048
|
-
previousTimestamp = now;
|
|
1049
|
-
func(...args);
|
|
1050
|
-
if (tid === null) {
|
|
1051
|
-
args = [];
|
|
1052
|
-
}
|
|
1053
|
-
} else if (tid === null && !(opts & timing.THROTTLE_NO_TRAILING)) {
|
|
1054
|
-
tid = setTimeout(later, remaining);
|
|
1055
|
-
}
|
|
1056
|
-
};
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
};
|
|
1060
|
-
|
|
1061
1135
|
class Wizard extends HTMLElement {
|
|
1062
1136
|
constructor() {
|
|
1063
1137
|
super();
|
|
@@ -1164,23 +1238,28 @@ var ful = (function (exports) {
|
|
|
1164
1238
|
}
|
|
1165
1239
|
|
|
1166
1240
|
exports.App = App;
|
|
1241
|
+
exports.Attributes = Attributes;
|
|
1167
1242
|
exports.AuthorizationCodeFlow = AuthorizationCodeFlow;
|
|
1168
1243
|
exports.AuthorizationCodeFlowInterceptor = AuthorizationCodeFlowInterceptor;
|
|
1169
1244
|
exports.AuthorizationCodeFlowSession = AuthorizationCodeFlowSession;
|
|
1170
1245
|
exports.Base64 = Base64;
|
|
1171
|
-
exports.CustomElements = CustomElements;
|
|
1172
1246
|
exports.Errors = Errors;
|
|
1173
1247
|
exports.Failure = Failure;
|
|
1174
1248
|
exports.FieldError = FieldError;
|
|
1175
1249
|
exports.Form = Form;
|
|
1250
|
+
exports.Fragments = Fragments;
|
|
1176
1251
|
exports.Hex = Hex;
|
|
1177
1252
|
exports.HttpClient = HttpClient;
|
|
1178
1253
|
exports.Input = Input;
|
|
1179
1254
|
exports.LocalStorage = LocalStorage;
|
|
1180
1255
|
exports.Observable = Observable;
|
|
1256
|
+
exports.RadioGroup = RadioGroup;
|
|
1181
1257
|
exports.Select = Select;
|
|
1182
1258
|
exports.SessionStorage = SessionStorage;
|
|
1259
|
+
exports.Slots = Slots;
|
|
1183
1260
|
exports.Spinner = Spinner;
|
|
1261
|
+
exports.Stateful = Stateful;
|
|
1262
|
+
exports.Templated = Templated;
|
|
1184
1263
|
exports.VersionedStorage = VersionedStorage;
|
|
1185
1264
|
exports.Wizard = Wizard;
|
|
1186
1265
|
exports.jsonPatch = jsonPatch;
|