@arsedizioni/ars-utils 22.5.12 → 22.5.14
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/clipper.common/README.md +9 -3
- package/fesm2022/arsedizioni-ars-utils-clipper.common.mjs +927 -712
- package/fesm2022/arsedizioni-ars-utils-clipper.common.mjs.map +1 -1
- package/fesm2022/arsedizioni-ars-utils-clipper.ui.mjs +8 -8
- package/fesm2022/arsedizioni-ars-utils-clipper.ui.mjs.map +1 -1
- package/fesm2022/arsedizioni-ars-utils-evolution.common.mjs +429 -211
- package/fesm2022/arsedizioni-ars-utils-evolution.common.mjs.map +1 -1
- package/package.json +1 -1
- package/types/arsedizioni-ars-utils-clipper.common.d.ts +201 -55
- package/types/arsedizioni-ars-utils-clipper.ui.d.ts +6 -6
- package/types/arsedizioni-ars-utils-evolution.common.d.ts +336 -57
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HttpResponse, HttpClient } from '@angular/common/http';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { inject, signal, Service, DestroyRef } from '@angular/core';
|
|
4
4
|
import { BroadcastService, SystemUtils } from '@arsedizioni/ars-utils/core';
|
|
5
|
-
import { catchError, throwError, EMPTY, of } from 'rxjs';
|
|
5
|
+
import { tap, catchError, throwError, EMPTY, of } from 'rxjs';
|
|
6
6
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
7
|
import { catchError as catchError$1, map, finalize } from 'rxjs/operators';
|
|
8
8
|
|
|
@@ -20,6 +20,15 @@ const EvolutionMessages = {
|
|
|
20
20
|
LOGOUT: '§evo-logout'
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The `sessionStorage` key naming which account a tab is working as; its value travels on every
|
|
25
|
+
* request as `X-Evo-Account`.
|
|
26
|
+
*
|
|
27
|
+
* It lives here, and not on `CoreService`, so the auth interceptor can read it without importing a
|
|
28
|
+
* service: the interceptor is a factory precisely so that registering it does not pull the Evolution
|
|
29
|
+
* service graph into the eager bundle.
|
|
30
|
+
*/
|
|
31
|
+
const EVOLUTION_ACTIVE_ACCOUNT_KEY = 'evolution_account';
|
|
23
32
|
var EvolutionServiceFlags;
|
|
24
33
|
(function (EvolutionServiceFlags) {
|
|
25
34
|
EvolutionServiceFlags[EvolutionServiceFlags["None"] = 0] = "None";
|
|
@@ -483,14 +492,81 @@ var ERPPlace;
|
|
|
483
492
|
class EvolutionComplianceContextInfo {
|
|
484
493
|
}
|
|
485
494
|
|
|
486
|
-
/** Minimum milliseconds between consecutive error broadcasts (debounce guard). */
|
|
495
|
+
/** Minimum milliseconds between consecutive generic error broadcasts (debounce guard). */
|
|
487
496
|
const ERROR_DEBOUNCE_MS = 5000;
|
|
497
|
+
/**
|
|
498
|
+
* Returns whether the URL is an authentication endpoint, where a 401 is a normal outcome -- wrong
|
|
499
|
+
* credentials, a rejected code, a refused passkey -- rather than an expired or invalid session.
|
|
500
|
+
*
|
|
501
|
+
* They are called by a visitor who has no session by definition, so answering "la sessione non è
|
|
502
|
+
* più valida" would send them back to the page they are already standing on and hide the real
|
|
503
|
+
* reason they were refused.
|
|
504
|
+
*
|
|
505
|
+
* The list covers the whole Evolution sign-in surface, including the ways in that this library does
|
|
506
|
+
* not itself offer: a host application registers one interceptor for every call it makes to the
|
|
507
|
+
* API, its own passkey and provider flows included.
|
|
508
|
+
* @param url - The request URL.
|
|
509
|
+
* @returns True when the URL is a login / confirm / otp / oauth / passkey endpoint.
|
|
510
|
+
*/
|
|
511
|
+
function isAuthEndpoint(url) {
|
|
512
|
+
return url.includes('/session/login')
|
|
513
|
+
|| url.includes('/session/confirm')
|
|
514
|
+
|| url.includes('/session/otp')
|
|
515
|
+
|| url.includes('/session/oauth')
|
|
516
|
+
|| url.includes('/passkeys/request-options')
|
|
517
|
+
|| url.includes('/passkeys/login');
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Resolves the user-facing message for a failed request.
|
|
521
|
+
* @param error - The raw HTTP error object.
|
|
522
|
+
* @param errorStatus - The numeric HTTP status code.
|
|
523
|
+
* @returns A localised message, ready to be rendered as HTML.
|
|
524
|
+
*/
|
|
525
|
+
function resolveErrorMessage(error, errorStatus) {
|
|
526
|
+
const message = error?.error?.message ?? error?.message;
|
|
527
|
+
switch (errorStatus) {
|
|
528
|
+
case 0:
|
|
529
|
+
return "In questo momento Evolution non è disponibile. Riprova tra qualche minuto.";
|
|
530
|
+
case 401:
|
|
531
|
+
// A 401 that reaches here comes from a sign-in attempt; anything else was classified as a
|
|
532
|
+
// dead session before this function was called.
|
|
533
|
+
return message ?? "Credenziali non valide o accesso non abilitato.";
|
|
534
|
+
case 403:
|
|
535
|
+
return "Non hai i permessi necessari per eseguire l'operazione richiesta.";
|
|
536
|
+
case 429:
|
|
537
|
+
return message ?? "Troppe richieste consecutive. Aspetta qualche secondo.";
|
|
538
|
+
default:
|
|
539
|
+
return (message ?? "Impossibile eseguire l'operazione richiesta.")
|
|
540
|
+
.replaceAll('\r\n', '</p><p>');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
488
543
|
/**
|
|
489
544
|
* Builds the Evolution auth HTTP interceptor.
|
|
490
545
|
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
546
|
+
* Attaches the credentials, the service-worker bypass and the active-account header to every
|
|
547
|
+
* request aimed at the Evolution API, and turns HTTP failures into broadcast messages -- no UI
|
|
548
|
+
* dependency here.
|
|
549
|
+
*
|
|
550
|
+
* Takes the Evolution base URI and the service flags directly as arguments, captured in the
|
|
551
|
+
* closure, so it never injects `EvolutionService`: registering it does not pull the whole Evolution
|
|
552
|
+
* service into the eager bundle. The debounce and the expiry latch live in that same closure, so
|
|
553
|
+
* two applications registering two interceptors never share them.
|
|
554
|
+
*
|
|
555
|
+
* ## What the statuses mean
|
|
556
|
+
*
|
|
557
|
+
* Aligned with the Evolution API on 2026-08-25. The meaning of the status codes changed with it:
|
|
558
|
+
* failures used to arrive as `200 OK` with `success: false`, and a dead session had to be guessed
|
|
559
|
+
* from a 405, a 410 or any 5xx. Now the status is the truth -- a 401 on an already-authenticated
|
|
560
|
+
* request means the session is gone, a 403 means the permission is missing, a 5xx means the server
|
|
561
|
+
* broke, and those are three different things that used to be one. `invalidateSession` is
|
|
562
|
+
* therefore now, and only, a 401 outside the authentication endpoints.
|
|
563
|
+
*
|
|
564
|
+
* `EvolutionServiceFlags.NotifySystemErrors` still governs one narrow case, and only that one: a
|
|
565
|
+
* 5xx, i.e. a failure of the server rather than of the request. Without the flag it is swallowed,
|
|
566
|
+
* as it always was in this library.
|
|
567
|
+
*
|
|
568
|
+
* Errors are re-thrown rather than swallowed: call sites rely on their `error` callback firing, and
|
|
569
|
+
* on `finalize` clearing a busy dialog.
|
|
494
570
|
*
|
|
495
571
|
* Register it with `withInterceptors`:
|
|
496
572
|
* @example
|
|
@@ -504,48 +580,31 @@ const ERROR_DEBOUNCE_MS = 5000;
|
|
|
504
580
|
* @returns An `HttpInterceptorFn` ready to register.
|
|
505
581
|
*/
|
|
506
582
|
function evolutionAuthInterceptor(serviceUri, flags = EvolutionServiceFlags.None) {
|
|
507
|
-
|
|
583
|
+
/** Timestamp of the last generic error shown, used to debounce a burst of failures into one. */
|
|
508
584
|
let lastErrorTime = -1;
|
|
509
585
|
/**
|
|
510
|
-
*
|
|
511
|
-
*
|
|
586
|
+
* Whether the session-expiry notification has already been sent. Prevents a storm of dialogs when
|
|
587
|
+
* several in-flight requests fail at once; re-armed on the next successful response.
|
|
588
|
+
*/
|
|
589
|
+
let sessionExpiredNotified = false;
|
|
590
|
+
/**
|
|
591
|
+
* Broadcasts a generic, user-facing error for an Evolution HTTP failure, debounced.
|
|
592
|
+
* @param error - The raw error value thrown by the HTTP layer.
|
|
593
|
+
* @param errorStatus - The numeric HTTP status code.
|
|
512
594
|
* @param broadcastService - The broadcast service for the current request.
|
|
513
595
|
* @returns void
|
|
514
596
|
*/
|
|
515
|
-
function
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
// Only reject errors that explicitly belong to a different service; a missing
|
|
519
|
-
// url (network failure on an Evolution request) is allowed through.
|
|
520
|
-
if (error.url && !error.url.startsWith(serviceUri))
|
|
521
|
-
return;
|
|
522
|
-
const errorStatus = error.status;
|
|
523
|
-
const shouldNotify = errorStatus === 0 ||
|
|
524
|
-
(errorStatus > 0 && errorStatus < 500) ||
|
|
525
|
-
(flags & EvolutionServiceFlags.NotifySystemErrors) > 0;
|
|
526
|
-
if (!shouldNotify)
|
|
597
|
+
function notifyError(error, errorStatus, broadcastService) {
|
|
598
|
+
// A broken server is the one case the flag still governs.
|
|
599
|
+
if (errorStatus >= 500 && (flags & EvolutionServiceFlags.NotifySystemErrors) === 0)
|
|
527
600
|
return;
|
|
528
601
|
const now = Date.now();
|
|
529
602
|
if (now - lastErrorTime <= ERROR_DEBOUNCE_MS)
|
|
530
603
|
return;
|
|
531
604
|
lastErrorTime = now;
|
|
532
|
-
let message;
|
|
533
|
-
switch (errorStatus) {
|
|
534
|
-
case 0:
|
|
535
|
-
message = "In questo momento Evolution non è disponibile. Riprova tra qualche minuto.";
|
|
536
|
-
break;
|
|
537
|
-
case 403:
|
|
538
|
-
message = "Non hai i permessi necessari per eseguire l'operazione richiesta.";
|
|
539
|
-
break;
|
|
540
|
-
default:
|
|
541
|
-
message = (error.error?.['message'] ??
|
|
542
|
-
error.message ??
|
|
543
|
-
"Impossibile eseguire l'operazione richiesta.").replaceAll('\r\n', '</p><p>');
|
|
544
|
-
break;
|
|
545
|
-
}
|
|
546
605
|
broadcastService.sendMessage(EvolutionMessages.ERROR, {
|
|
547
|
-
invalidateSession:
|
|
548
|
-
message,
|
|
606
|
+
invalidateSession: false,
|
|
607
|
+
message: resolveErrorMessage(error, errorStatus),
|
|
549
608
|
title: "Errore in Evolution",
|
|
550
609
|
errorStatus,
|
|
551
610
|
service: serviceUri
|
|
@@ -558,14 +617,47 @@ function evolutionAuthInterceptor(serviceUri, flags = EvolutionServiceFlags.None
|
|
|
558
617
|
}
|
|
559
618
|
const broadcastService = inject(BroadcastService);
|
|
560
619
|
const authenticatedRequest = request.clone({
|
|
620
|
+
// `withCredentials` is what carries the session: an HttpOnly cookie the application cannot
|
|
621
|
+
// read, cannot forge and cannot forget to send.
|
|
561
622
|
withCredentials: true,
|
|
562
623
|
setHeaders: {
|
|
563
624
|
'ngsw-bypass': 'ngsw-bypass',
|
|
564
|
-
|
|
625
|
+
// Which of the accounts inside the one session cookie this tab is working as. It replaces
|
|
626
|
+
// the per-tab random `X-Client-Id`, whose only job was to give each tab a cookie of its
|
|
627
|
+
// own -- one per tab ever opened, all of them sent on every request, until the header
|
|
628
|
+
// outgrew what the server accepts. Empty on a tab that has not signed in yet: the API then
|
|
629
|
+
// serves the active account.
|
|
630
|
+
'X-Evo-Account': sessionStorage.getItem(EVOLUTION_ACTIVE_ACCOUNT_KEY) ?? ''
|
|
565
631
|
}
|
|
566
632
|
});
|
|
567
|
-
return next(authenticatedRequest).pipe(
|
|
568
|
-
|
|
633
|
+
return next(authenticatedRequest).pipe(tap(event => {
|
|
634
|
+
// A response means the session is valid again: re-arm the expiry notifier.
|
|
635
|
+
if (event instanceof HttpResponse) {
|
|
636
|
+
sessionExpiredNotified = false;
|
|
637
|
+
}
|
|
638
|
+
}), catchError((error) => {
|
|
639
|
+
// Which request this was is read from the request, not from `error.url`: a failure with no
|
|
640
|
+
// response -- the browser refusing the connection while the API restarts -- carries no URL
|
|
641
|
+
// at all. Everything that gets here is an Evolution request anyway: the others returned
|
|
642
|
+
// before the pipe was built.
|
|
643
|
+
const errorStatus = parseInt(error?.status ?? '0');
|
|
644
|
+
if (errorStatus === 401 && !isAuthEndpoint(authenticatedRequest.url)) {
|
|
645
|
+
// Expired or invalidated session on an authenticated request. Notified once: several
|
|
646
|
+
// in-flight requests failing together is how a single expiry becomes a wall of dialogs.
|
|
647
|
+
if (!sessionExpiredNotified) {
|
|
648
|
+
sessionExpiredNotified = true;
|
|
649
|
+
broadcastService.sendMessage(EvolutionMessages.ERROR, {
|
|
650
|
+
invalidateSession: true,
|
|
651
|
+
message: "La sessione di lavoro non è più valida.",
|
|
652
|
+
title: "Errore in Evolution",
|
|
653
|
+
errorStatus,
|
|
654
|
+
service: serviceUri
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
notifyError(error, errorStatus, broadcastService);
|
|
660
|
+
}
|
|
569
661
|
return throwError(() => error);
|
|
570
662
|
}));
|
|
571
663
|
};
|
|
@@ -587,9 +679,16 @@ class CoreService {
|
|
|
587
679
|
constructor() {
|
|
588
680
|
this.broadcastService = inject(BroadcastService);
|
|
589
681
|
this._flags = EvolutionServiceFlags.None;
|
|
590
|
-
|
|
682
|
+
/**
|
|
683
|
+
* Seeded from the stored context, which means "this browser signed in at some point" and nothing
|
|
684
|
+
* more: the auth cookie is HttpOnly and may have expired while the stored context lived on.
|
|
685
|
+
* `LoginService.initialize` turns the hint into an answer by asking `/session/me`, and the hint
|
|
686
|
+
* is what keeps a visitor who never signed in from calling a route that would answer 401 and
|
|
687
|
+
* raise a dead-session error over the sign-in page.
|
|
688
|
+
*/
|
|
689
|
+
this._loggedIn = signal(localStorage.getItem('evolution_context') !== null, /* @ts-ignore */
|
|
591
690
|
...(ngDevMode ? [{ debugName: "_loggedIn" }] : /* istanbul ignore next */ []));
|
|
592
|
-
/** `true` when
|
|
691
|
+
/** `true` when this browser has a session the API has accepted. */
|
|
593
692
|
this.loggedIn = this._loggedIn.asReadonly();
|
|
594
693
|
/** `true` while a login request is in flight. */
|
|
595
694
|
this.loggingIn = signal(false, /* @ts-ignore */
|
|
@@ -650,19 +749,26 @@ class CoreService {
|
|
|
650
749
|
setLoggedIn(value) {
|
|
651
750
|
this._loggedIn.set(value);
|
|
652
751
|
}
|
|
752
|
+
/** The `sessionStorage` key naming which account this tab is working as. */
|
|
753
|
+
static { this.activeAccountKey = EVOLUTION_ACTIVE_ACCOUNT_KEY; }
|
|
653
754
|
/**
|
|
654
|
-
* Records
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
755
|
+
* Records which account this tab is working as, so the interceptor can name it on every request.
|
|
756
|
+
*
|
|
757
|
+
* `sessionStorage` and not `localStorage`: the choice belongs to the tab. One cookie holds every
|
|
758
|
+
* account this browser has open, and two tabs may sit on two different licences while reading
|
|
759
|
+
* that same cookie -- which is what replaced the per-tab `X-Client-Id`, a random UUID that gave
|
|
760
|
+
* every tab ever opened a session cookie of its own until the header outgrew what the server
|
|
761
|
+
* accepts.
|
|
762
|
+
* @param userId - The account to work as, or `undefined` to stop naming one.
|
|
658
763
|
* @returns void
|
|
659
764
|
*/
|
|
660
|
-
|
|
661
|
-
if (
|
|
662
|
-
|
|
765
|
+
setActiveAccount(userId) {
|
|
766
|
+
if (userId && userId > 0) {
|
|
767
|
+
sessionStorage.setItem(CoreService.activeAccountKey, String(userId));
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
sessionStorage.removeItem(CoreService.activeAccountKey);
|
|
663
771
|
}
|
|
664
|
-
this._loginInfo.oauth = oauth;
|
|
665
|
-
this._loginInfo.remember = remember;
|
|
666
772
|
}
|
|
667
773
|
/**
|
|
668
774
|
* Persists the current login info to `localStorage`.
|
|
@@ -694,17 +800,33 @@ class CoreService {
|
|
|
694
800
|
this.broadcastService.sendMessage(EvolutionMessages.LOGOUT_COMPLETED);
|
|
695
801
|
}
|
|
696
802
|
/**
|
|
697
|
-
*
|
|
698
|
-
*
|
|
803
|
+
* Clears every trace of the session on this device -- the stored context, the account this tab
|
|
804
|
+
* was working as and the leftovers of the token era -- and then calls {@link reset}. It asks the
|
|
805
|
+
* API nothing: use `LoginService.logout` for that.
|
|
806
|
+
*
|
|
807
|
+
* It takes no argument any more. `clearOAuthToken` decided whether to drop
|
|
808
|
+
* `evolution_oauth_token`, a provider access token this library no longer obtains, stores or
|
|
809
|
+
* sends: the whole exchange happens on the backend.
|
|
699
810
|
* @returns void
|
|
700
811
|
*/
|
|
701
|
-
clear(
|
|
812
|
+
clear() {
|
|
813
|
+
// The stored context goes with the session it describes. It used to be removed by `logout()`
|
|
814
|
+
// alone, so every other way of losing a session -- an expiry, a rejected restore -- left it
|
|
815
|
+
// behind for the next bootstrap to trust.
|
|
816
|
+
localStorage.removeItem('evolution_context');
|
|
817
|
+
// The account this tab was working as. Left behind, the next request would name an account no
|
|
818
|
+
// longer in the cookie: harmless, because the API falls back to the active one, but it would
|
|
819
|
+
// also survive a sign-in as somebody else until the next `completeLogin` overwrote it.
|
|
820
|
+
sessionStorage.removeItem(CoreService.activeAccountKey);
|
|
821
|
+
// Leftovers of the token era, when the session travelled in session storage instead of an
|
|
822
|
+
// HttpOnly cookie, and of the per-tab cookie era. Nothing writes them any more; they are
|
|
823
|
+
// removed so a browser upgrading from the previous version does not keep them for good.
|
|
702
824
|
sessionStorage.removeItem('evolution_auth');
|
|
703
825
|
sessionStorage.removeItem('evolution_refresh');
|
|
704
826
|
sessionStorage.removeItem('evolution_oauth');
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
827
|
+
sessionStorage.removeItem('evolution_auth_oauth');
|
|
828
|
+
sessionStorage.removeItem('evolution_oauth_token');
|
|
829
|
+
sessionStorage.removeItem('evolution_client_id');
|
|
708
830
|
this.reset();
|
|
709
831
|
}
|
|
710
832
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CoreService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
|
|
@@ -715,11 +837,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
715
837
|
}] });
|
|
716
838
|
|
|
717
839
|
/**
|
|
718
|
-
* Authentication
|
|
719
|
-
* session
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
840
|
+
* Authentication for the Evolution back end: password login, e-mailed code confirmation, account
|
|
841
|
+
* switch, logout and session restore. All session state lives in the private {@link CoreService};
|
|
842
|
+
* this service orchestrates the HTTP flows and re-publishes the parts of core that consumers need.
|
|
843
|
+
*
|
|
844
|
+
* Aligned with the Evolution API on 2026-08-25. Everything goes through `/session/*` and an
|
|
845
|
+
* HttpOnly cookie now: there is no bearer token, no `remember` credential the API can replay, and
|
|
846
|
+
* no provider token acquired by the caller -- the external-provider flow is a full-page redirect
|
|
847
|
+
* handled server-side, and all that reaches this service is the `/session/me` call on the way back.
|
|
848
|
+
*
|
|
849
|
+
* ## What this service no longer offers, and why
|
|
850
|
+
*
|
|
851
|
+
* - **`autoLogin`.** It was `login(undefined, undefined, true)`: a credential-less sign-in that
|
|
852
|
+
* worked only because the API could decrypt a stored password from an `evolution_remember`
|
|
853
|
+
* cookie. `POST /session/login` takes a user and a password and nothing else.
|
|
854
|
+
* - **`autoLogout`.** It wrapped {@link logout} in toasts and a callback, and ran the callback
|
|
855
|
+
* from `complete` -- that is, before the request it was waiting for had said anything. A host
|
|
856
|
+
* that wants a callback subscribes: `logout().subscribe(() => …)`.
|
|
857
|
+
* - **re-login on `LOGIN_CHANGED`.** The handler used to answer that message with a silent
|
|
858
|
+
* `autoLogin`, which is the same replay by another name. The message is still broadcast by hosts
|
|
859
|
+
* that link and unlink Evolution, and it is theirs to act on; this library no longer signs
|
|
860
|
+
* anybody in without being asked.
|
|
861
|
+
* - **the OAuth `Authorization` header, and the per-tab `X-Client-Id`.** The first is server-side
|
|
862
|
+
* now; the second was a random UUID that gave every tab a session cookie of its own. A tab says
|
|
863
|
+
* which of the accounts in the one cookie it is working as with `X-Evo-Account`, written by
|
|
864
|
+
* {@link CoreService.setActiveAccount}.
|
|
865
|
+
*
|
|
866
|
+
* This is a deliberately reduced subset of what the Evolution application itself does: passkeys and
|
|
867
|
+
* the provider redirect are not here. A library whose job is to let another application talk to
|
|
868
|
+
* Evolution signs in with a password; an application that offers the other ways in owns them.
|
|
723
869
|
*/
|
|
724
870
|
class LoginService {
|
|
725
871
|
constructor() {
|
|
@@ -745,9 +891,14 @@ class LoginService {
|
|
|
745
891
|
* @returns The current {@link EvolutionLoginInfo}, or undefined.
|
|
746
892
|
*/
|
|
747
893
|
get loginInfo() { return this.core.loginInfo; }
|
|
748
|
-
/**
|
|
894
|
+
/**
|
|
895
|
+
* Gets the signed-in account with its licence context.
|
|
896
|
+
* @returns The current {@link EvolutionUserInfo}, or undefined when there is no session.
|
|
897
|
+
*/
|
|
898
|
+
get user() { return this.core.loginInfo?.context; }
|
|
899
|
+
/** `true` when this browser has a session the API has accepted. */
|
|
749
900
|
get loggedIn() { return this.core.loggedIn; }
|
|
750
|
-
/** `true` while
|
|
901
|
+
/** `true` while an authentication is in flight. */
|
|
751
902
|
get loggingIn() { return this.core.loggingIn; }
|
|
752
903
|
// ── Re-published core operations ────────────────────────────────────────────
|
|
753
904
|
/**
|
|
@@ -767,26 +918,26 @@ class LoginService {
|
|
|
767
918
|
*/
|
|
768
919
|
reset() { this.core.reset(); }
|
|
769
920
|
/**
|
|
770
|
-
*
|
|
771
|
-
*
|
|
921
|
+
* Clears every trace of the session on this device without asking the API anything
|
|
922
|
+
* (delegates to core). Use {@link logout} for a real sign-out.
|
|
772
923
|
* @returns void
|
|
773
924
|
*/
|
|
774
|
-
clear(
|
|
925
|
+
clear() { this.core.clear(); }
|
|
775
926
|
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
776
927
|
/**
|
|
777
|
-
* Initialises the service with the back-end URI and optional feature flags
|
|
778
|
-
*
|
|
928
|
+
* Initialises the service with the back-end URI and optional feature flags, and revalidates a
|
|
929
|
+
* session that survived a page refresh instead of assuming it is still there.
|
|
930
|
+
*
|
|
931
|
+
* Must be called once during application bootstrap, before any other method. What it used to do
|
|
932
|
+
* on a refresh was announce `LOGIN_COMPLETED` on the strength of a value in local storage; the
|
|
933
|
+
* auth cookie is HttpOnly and may have expired while that value lived on, so the claim was never
|
|
934
|
+
* verified and the consumer met the inevitable 401 as "your session just expired" rather than as
|
|
935
|
+
* a bootstrap that never got off the ground.
|
|
779
936
|
* @param serviceUri - Base URL of the Evolution service.
|
|
780
937
|
* @param flags - Bitmask of `EvolutionServiceFlags` (default: `None`).
|
|
781
938
|
* @returns void
|
|
782
939
|
*/
|
|
783
940
|
initialize(serviceUri, flags = EvolutionServiceFlags.None) {
|
|
784
|
-
// Create a unique per-tab client ID if not already set
|
|
785
|
-
if (!sessionStorage.getItem('evolution_client_id')) {
|
|
786
|
-
sessionStorage.setItem('evolution_client_id', (flags & EvolutionServiceFlags.Embedded) > 0
|
|
787
|
-
? 'embedded'
|
|
788
|
-
: SystemUtils.generateUUID());
|
|
789
|
-
}
|
|
790
941
|
this.core.setServiceUri(serviceUri);
|
|
791
942
|
this.core.setFlags(flags);
|
|
792
943
|
// Subscribe to broadcast messages (only once)
|
|
@@ -799,10 +950,11 @@ class LoginService {
|
|
|
799
950
|
this.handleBroadcastMessage(message);
|
|
800
951
|
});
|
|
801
952
|
}
|
|
802
|
-
//
|
|
953
|
+
// A stored context means this browser signed in at some point, never that the session is still
|
|
954
|
+
// alive: the API is the only one that knows, and it is asked here.
|
|
803
955
|
if (this.core.loggedIn()) {
|
|
804
|
-
this.core.loggingIn.set(
|
|
805
|
-
this.
|
|
956
|
+
this.core.loggingIn.set(true);
|
|
957
|
+
this.restoreSession();
|
|
806
958
|
}
|
|
807
959
|
}
|
|
808
960
|
/**
|
|
@@ -811,47 +963,26 @@ class LoginService {
|
|
|
811
963
|
* @returns void
|
|
812
964
|
*/
|
|
813
965
|
handleBroadcastMessage(message) {
|
|
814
|
-
if (message.id
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
this.broadcastService.sendMessage(EvolutionMessages.ERROR, { message: "Le credenziali di accesso sono cambiate o non sono più valide. Esegui un nuovo accesso." });
|
|
821
|
-
}
|
|
822
|
-
this.broadcastService.sendMessage(EvolutionMessages.LOGIN_FAILED);
|
|
823
|
-
}
|
|
824
|
-
else {
|
|
825
|
-
if ((this.core.flags & EvolutionServiceFlags.DisplayConnectionStateMessages) > 0) {
|
|
826
|
-
this.broadcastService.sendMessage(EvolutionMessages.SUCCESS_TOAST, { message: 'Connesso a Evolution', icon: 'power', duration: 1500 });
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
},
|
|
830
|
-
error: () => { console.error('Evolution non disponibile.'); }
|
|
831
|
-
});
|
|
966
|
+
if (message.id !== EvolutionMessages.LOGOUT)
|
|
967
|
+
return;
|
|
968
|
+
if (!this.core.loggedIn()) {
|
|
969
|
+
// No session to close server-side, but the device must still be left clean.
|
|
970
|
+
this.core.clear();
|
|
971
|
+
return;
|
|
832
972
|
}
|
|
833
|
-
|
|
834
|
-
if (
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
this.broadcastService.sendMessage(EvolutionMessages.ERROR, { message: "<p>" + r.message + "</p><br><br><hr><p class='small'><i>Per eliminare la configurazione di Evolution accedere a:<br><b>menu > personalizza > collegamenti</b></i></p>" });
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
else {
|
|
842
|
-
if ((this.core.flags & EvolutionServiceFlags.DisplayConnectionStateMessages) > 0) {
|
|
843
|
-
this.broadcastService.sendMessage(EvolutionMessages.SUCCESS_TOAST, { message: 'Disconnesso da Evolution', icon: 'power_off', duration: 1500 });
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
});
|
|
973
|
+
this.logout().subscribe(r => {
|
|
974
|
+
if (!r.success) {
|
|
975
|
+
if (r.message) {
|
|
976
|
+
this.broadcastService.sendMessage(EvolutionMessages.ERROR, { message: "<p>" + r.message + "</p><br><br><hr><p class='small'><i>Per eliminare la configurazione di Evolution accedere a:<br><b>menu > personalizza > collegamenti</b></i></p>" });
|
|
977
|
+
}
|
|
847
978
|
}
|
|
848
|
-
else {
|
|
849
|
-
this.
|
|
979
|
+
else if ((this.core.flags & EvolutionServiceFlags.DisplayConnectionStateMessages) > 0) {
|
|
980
|
+
this.broadcastService.sendMessage(EvolutionMessages.SUCCESS_TOAST, { message: 'Disconnesso da Evolution', icon: 'power_off', duration: 1500 });
|
|
850
981
|
}
|
|
851
|
-
}
|
|
982
|
+
});
|
|
852
983
|
}
|
|
853
984
|
/**
|
|
854
|
-
* Sends a one-shot ping to the back end to verify connectivity.
|
|
985
|
+
* Sends a one-shot ping to the back end to verify connectivity. Errors are swallowed.
|
|
855
986
|
* @returns void
|
|
856
987
|
*/
|
|
857
988
|
ping() {
|
|
@@ -860,78 +991,30 @@ class LoginService {
|
|
|
860
991
|
.pipe(catchError$1(() => EMPTY))
|
|
861
992
|
.subscribe();
|
|
862
993
|
}
|
|
994
|
+
// ── Session ─────────────────────────────────────────────────────────────────
|
|
863
995
|
/**
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
else {
|
|
876
|
-
if (!r.value?.requiresMfa) {
|
|
877
|
-
this.broadcastService.sendMessage(EvolutionMessages.SUCCESS_TOAST, { message: 'Connesso ad Evolution', icon: 'power', duration: 1500 });
|
|
878
|
-
}
|
|
879
|
-
onSuccess?.();
|
|
880
|
-
}
|
|
881
|
-
},
|
|
882
|
-
error: () => { this.broadcastService.sendMessage(EvolutionMessages.ERROR, { message: "Evolution non disponibile." }); }
|
|
883
|
-
});
|
|
884
|
-
return true;
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Performs an automatic logout, notifying the user when the session ends.
|
|
888
|
-
* @param onSuccess - Optional callback invoked after the logout completes.
|
|
889
|
-
* @returns void
|
|
890
|
-
*/
|
|
891
|
-
autoLogout(onSuccess) {
|
|
892
|
-
this.logout().subscribe({
|
|
893
|
-
next: r => {
|
|
894
|
-
if (!r.success) {
|
|
895
|
-
if (r.message) {
|
|
896
|
-
this.broadcastService.sendMessage(EvolutionMessages.ERROR, { message: r.message });
|
|
897
|
-
}
|
|
898
|
-
this.broadcastService.sendMessage(EvolutionMessages.LOGIN_CHANGED);
|
|
899
|
-
}
|
|
900
|
-
else {
|
|
901
|
-
this.broadcastService.sendMessage(EvolutionMessages.SUCCESS_TOAST, { message: 'Disconnesso da Evolution', icon: 'power_off', duration: 1500 });
|
|
902
|
-
}
|
|
903
|
-
},
|
|
904
|
-
error: () => { },
|
|
905
|
-
complete: () => {
|
|
906
|
-
onSuccess?.();
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Authenticates the user against the Evolution back end (credential or OAuth2).
|
|
912
|
-
* @param email - User e-mail (credential login only; omit for OAuth2).
|
|
913
|
-
* @param password - User password (credential login only; omit for OAuth2).
|
|
914
|
-
* @param remember - Whether to persist the session across browser restarts.
|
|
915
|
-
* @param oauth - OAuth2 provider type, when using federated login.
|
|
916
|
-
* @param oauthAccessToken - Bearer token from the OAuth2 provider.
|
|
996
|
+
* Authenticates the user with an e-mail address and a password, the only credentials
|
|
997
|
+
* `POST /session/login` accepts.
|
|
998
|
+
*
|
|
999
|
+
* On success the session cookie is issued and the context stored, unless the API answers
|
|
1000
|
+
* `requiresMfa`: the device is not a trusted one, a six-digit code has been e-mailed and nothing
|
|
1001
|
+
* about the account is disclosed until {@link confirmIdentity} redeems it.
|
|
1002
|
+
*
|
|
1003
|
+
* A failure is no longer a `200 OK` carrying `success: false`. Wrong credentials come back as a
|
|
1004
|
+
* real 4xx with the message in the envelope, so the `next` branch of the caller is never reached.
|
|
1005
|
+
* @param email - The user's e-mail address.
|
|
1006
|
+
* @param password - The user's password.
|
|
917
1007
|
* @returns An observable that emits the `ApiResult<EvolutionLoginResult>`.
|
|
918
1008
|
*/
|
|
919
|
-
login(email, password
|
|
1009
|
+
login(email, password) {
|
|
1010
|
+
// Kept for the confirmation step, which has to name the account the code was issued to.
|
|
1011
|
+
this.pendingLoginEmail = email;
|
|
920
1012
|
return this.httpClient
|
|
921
|
-
.post(this.core.serviceUri + '/
|
|
922
|
-
user: oauth ? null : email,
|
|
923
|
-
password: oauth ? null : password,
|
|
924
|
-
remember,
|
|
925
|
-
oauth
|
|
926
|
-
}, {
|
|
927
|
-
headers: oauth && oauthAccessToken
|
|
928
|
-
? new HttpHeaders().set('Authorization', oauthAccessToken)
|
|
929
|
-
: new HttpHeaders()
|
|
930
|
-
})
|
|
1013
|
+
.post(this.core.serviceUri + '/session/login', { user: email ?? '', password: password ?? '' })
|
|
931
1014
|
.pipe(catchError$1(err => throwError(() => err)), map((r) => {
|
|
932
1015
|
if (r.success) {
|
|
933
|
-
|
|
934
|
-
|
|
1016
|
+
if (r.value?.requiresMfa) {
|
|
1017
|
+
// Notify the login is pending an e-mailed code.
|
|
935
1018
|
this.broadcastService.sendMessage(EvolutionMessages.LOGIN_PENDING, {});
|
|
936
1019
|
}
|
|
937
1020
|
else {
|
|
@@ -942,24 +1025,20 @@ class LoginService {
|
|
|
942
1025
|
}));
|
|
943
1026
|
}
|
|
944
1027
|
/**
|
|
945
|
-
*
|
|
946
|
-
*
|
|
947
|
-
*
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
/**
|
|
956
|
-
* Confirms a multi-factor authentication challenge and completes the login.
|
|
957
|
-
* @param code - The one-time MFA code entered by the user.
|
|
1028
|
+
* Confirms a pending e-mailed code challenge, which establishes the session and marks this device
|
|
1029
|
+
* as trusted: the next sign-in from it will not ask for a code again.
|
|
1030
|
+
*
|
|
1031
|
+
* The address travels with the code, in the body. The API stores each code under the account it
|
|
1032
|
+
* was issued to, so naming the account is part of redeeming it; and a code in the body is a code
|
|
1033
|
+
* that stays out of the URL, where `POST /login/confirm/{code}` left it for the access logs and
|
|
1034
|
+
* the browser history to keep.
|
|
1035
|
+
* @param code - The one-time confirmation code provided to the user.
|
|
1036
|
+
* @param email - The address the code was sent to; defaults to the one the pending login started with.
|
|
958
1037
|
* @returns An observable that emits the `ApiResult<EvolutionLoginResult>`.
|
|
959
1038
|
*/
|
|
960
|
-
confirmIdentity(code) {
|
|
1039
|
+
confirmIdentity(code, email) {
|
|
961
1040
|
return this.httpClient
|
|
962
|
-
.post(this.core.serviceUri + '/
|
|
1041
|
+
.post(this.core.serviceUri + '/session/confirm', { email: email ?? this.pendingLoginEmail, code: code })
|
|
963
1042
|
.pipe(catchError$1(err => throwError(() => err)), map((r) => {
|
|
964
1043
|
if (r.success) {
|
|
965
1044
|
this.completeLogin(r.value);
|
|
@@ -968,33 +1047,119 @@ class LoginService {
|
|
|
968
1047
|
}));
|
|
969
1048
|
}
|
|
970
1049
|
/**
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
1050
|
+
* Reads the current session from the server, which resolves it from the auth cookie's claims.
|
|
1051
|
+
* Used to restore the state after a page refresh instead of trusting what local storage holds.
|
|
1052
|
+
*
|
|
1053
|
+
* The route answers with the same `EvolutionLoginResult` a login answers with, built by the same
|
|
1054
|
+
* `ResolveSignInContextAsync`, so a restored session and a fresh one are identical by
|
|
1055
|
+
* construction.
|
|
1056
|
+
* @returns An observable that emits the `ApiResult<EvolutionLoginResult>`.
|
|
974
1057
|
*/
|
|
975
|
-
|
|
1058
|
+
me() {
|
|
976
1059
|
return this.httpClient
|
|
977
|
-
.
|
|
978
|
-
.pipe(
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1060
|
+
.get(this.core.serviceUri + '/session/me')
|
|
1061
|
+
.pipe(map((r) => {
|
|
1062
|
+
if (r?.success) {
|
|
1063
|
+
// No LOGIN_COMPLETED from here: {@link restoreSession} announces it, so a consumer
|
|
1064
|
+
// hears it once whether the session was opened or recovered.
|
|
1065
|
+
this.completeLogin(r.value, false);
|
|
1066
|
+
}
|
|
1067
|
+
return r;
|
|
1068
|
+
}));
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Asks `/session/me` whether the restored session is still alive, and announces the login when it
|
|
1072
|
+
* is.
|
|
1073
|
+
*
|
|
1074
|
+
* Nothing is cleared on failure, and that is deliberate: a failure here is not proof of a dead
|
|
1075
|
+
* session -- the API may be unreachable, the proxy may have answered 502, the device may be
|
|
1076
|
+
* offline -- and clearing on any of those would sign the user out with a perfectly valid cookie.
|
|
1077
|
+
* A genuine 401 is a different matter and the interceptor has already reported it.
|
|
1078
|
+
* @returns void
|
|
1079
|
+
*/
|
|
1080
|
+
restoreSession() {
|
|
1081
|
+
this.me()
|
|
1082
|
+
.pipe(finalize(() => this.core.loggingIn.set(false)))
|
|
1083
|
+
.subscribe({
|
|
1084
|
+
next: r => {
|
|
1085
|
+
if (r?.success) {
|
|
1086
|
+
this.broadcastService.sendMessage(EvolutionMessages.LOGIN_COMPLETED);
|
|
1087
|
+
}
|
|
1088
|
+
},
|
|
1089
|
+
// Consumed so the rethrown error is not reported a second time as an unhandled one.
|
|
1090
|
+
error: () => { }
|
|
1091
|
+
});
|
|
982
1092
|
}
|
|
983
1093
|
/**
|
|
984
|
-
* Switches
|
|
1094
|
+
* Switches this tab to another account of the same person -- same address, same stored password
|
|
1095
|
+
* -- without re-entering credentials.
|
|
1096
|
+
*
|
|
1097
|
+
* The account being left stays open: a switch used to close its session, because each account had
|
|
1098
|
+
* a cookie of its own, and they now share one cookie holding several. What changes here is only
|
|
1099
|
+
* which account THIS tab names on its requests, which {@link completeLogin} records.
|
|
985
1100
|
* @param id - The user ID to switch to.
|
|
986
1101
|
* @returns An observable that emits the `ApiResult<EvolutionLoginResult>`.
|
|
987
1102
|
*/
|
|
988
1103
|
loginSwitch(id) {
|
|
989
1104
|
return this.httpClient
|
|
990
|
-
.post(this.core.serviceUri + '/
|
|
1105
|
+
.post(this.core.serviceUri + '/session/switch', { userId: id })
|
|
991
1106
|
.pipe(catchError$1(err => throwError(() => err)), map((r) => {
|
|
992
1107
|
if (r.success) {
|
|
993
|
-
this.completeLogin(r.value);
|
|
1108
|
+
this.completeLogin(r.value, false);
|
|
994
1109
|
}
|
|
995
1110
|
return r;
|
|
996
1111
|
}));
|
|
997
1112
|
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Signs out: closes this browser's session on the API and clears the local state whatever the
|
|
1115
|
+
* answer, since there is nothing useful to do with a logout that failed.
|
|
1116
|
+
*
|
|
1117
|
+
* `forget` no longer means "drop the stored credentials" -- there are none to drop. It means
|
|
1118
|
+
* "stop trusting this device", that is clear the MFA cookie, so that the next sign-in from here
|
|
1119
|
+
* is challenged with an e-mailed code again. It also closes one session and not all of them: an
|
|
1120
|
+
* account may hold one per front end, and signing out of this browser must not sign the person
|
|
1121
|
+
* out of their phone.
|
|
1122
|
+
* @param forget - When `true`, the device stops being a trusted one. Defaults to `false`.
|
|
1123
|
+
* @returns An observable that emits `ApiResult<unknown>`.
|
|
1124
|
+
*/
|
|
1125
|
+
logout(forget = false) {
|
|
1126
|
+
return this.httpClient
|
|
1127
|
+
.post(this.core.serviceUri + '/session/logout?forget=' + forget, {})
|
|
1128
|
+
.pipe(finalize(() => this.core.clear()),
|
|
1129
|
+
// A logout that failed is still a logout as far as this device is concerned: the local
|
|
1130
|
+
// state has already been cleared by `finalize`, and there is nothing useful a caller could
|
|
1131
|
+
// do with the failure. `message` is empty rather than absent because `ApiResponse` declares
|
|
1132
|
+
// it as a string.
|
|
1133
|
+
catchError$1(() => of({ success: false, value: undefined, message: '' })));
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Finalises a login, a code confirmation, an account switch or a session restore: stores the
|
|
1137
|
+
* context, raises the logged-in signal, records which account this tab is working as and
|
|
1138
|
+
* optionally announces `LOGIN_COMPLETED`.
|
|
1139
|
+
*
|
|
1140
|
+
* `loggedIn` is raised unconditionally. It used to be `!result.context?.isTemporary`, and
|
|
1141
|
+
* `isTemporary` was always false -- it is `[NotMapped]` on the API and never filled -- so the
|
|
1142
|
+
* condition never did anything. An expired password is reported by `requiresPasswordChange` and
|
|
1143
|
+
* does not mean there is no session: the cookie has been issued, and it is what the password
|
|
1144
|
+
* change itself travels on.
|
|
1145
|
+
*
|
|
1146
|
+
* `loggingIn` is deliberately left alone. Whoever raised it lowers it -- {@link restoreSession}
|
|
1147
|
+
* for a refresh, the host application for its own sign-in flow -- because lowering it here would
|
|
1148
|
+
* drop the host's splash the instant the response arrived, before anything was on screen.
|
|
1149
|
+
* @param result - The login result payload from the server.
|
|
1150
|
+
* @param notify - Whether to broadcast `LOGIN_COMPLETED`. Defaults to `true`.
|
|
1151
|
+
* @returns void
|
|
1152
|
+
*/
|
|
1153
|
+
completeLogin(result, notify = true) {
|
|
1154
|
+
this.core.updateContext(result);
|
|
1155
|
+
this.core.setLoggedIn(true);
|
|
1156
|
+
// Which account THIS TAB is working as. Per tab on purpose: two tabs may sit on two different
|
|
1157
|
+
// licences, and they share the one cookie that holds both.
|
|
1158
|
+
this.core.setActiveAccount(result?.context?.userId);
|
|
1159
|
+
if (notify) {
|
|
1160
|
+
this.broadcastService.sendMessage(EvolutionMessages.LOGIN_COMPLETED);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
998
1163
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: LoginService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
|
|
999
1164
|
static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.3", ngImport: i0, type: LoginService }); }
|
|
1000
1165
|
}
|
|
@@ -1093,7 +1258,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
1093
1258
|
type: Service
|
|
1094
1259
|
}] });
|
|
1095
1260
|
|
|
1096
|
-
/**
|
|
1261
|
+
/**
|
|
1262
|
+
* The `/account/*` endpoints: the links a user account carries, its settings, its password and the
|
|
1263
|
+
* licence behind it.
|
|
1264
|
+
*
|
|
1265
|
+
* Everything here acts on the account the session names, which is why none of these methods takes
|
|
1266
|
+
* one. The licence refresh is the exception in appearance only: the id in the route is ignored by
|
|
1267
|
+
* the API, which reads the account from the session -- it used to be taken at face value, with no
|
|
1268
|
+
* authorization at all, so any integer returned somebody else's company and expiry.
|
|
1269
|
+
*/
|
|
1097
1270
|
class AccountService {
|
|
1098
1271
|
constructor() {
|
|
1099
1272
|
this.httpClient = inject(HttpClient);
|
|
@@ -1115,6 +1288,42 @@ class AccountService {
|
|
|
1115
1288
|
deleteLink(item) {
|
|
1116
1289
|
return this.httpClient.post(this.core.serviceUri + '/account/links/delete', item);
|
|
1117
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Persists updated settings for the signed-in account (dashboard, flags).
|
|
1293
|
+
* @param params - The settings to save.
|
|
1294
|
+
* @returns An observable that emits `ApiResult<boolean>`.
|
|
1295
|
+
*/
|
|
1296
|
+
updateSettings(params) {
|
|
1297
|
+
return this.httpClient.post(this.core.serviceUri + '/account/settings/save', params);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Changes a password: the signed-in account's own, with `oldPassword`, or somebody else's when an
|
|
1301
|
+
* administrator names it.
|
|
1302
|
+
* @param params - The password change to apply.
|
|
1303
|
+
* @returns An observable that emits `ApiResult<boolean>`.
|
|
1304
|
+
*/
|
|
1305
|
+
resetPassword(params) {
|
|
1306
|
+
return this.httpClient.post(this.core.serviceUri + '/account/password/reset', params);
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Starts a password recovery, which mails a reset link to the given address.
|
|
1310
|
+
* @param params - The address to recover, with the reCAPTCHA token that guards the route.
|
|
1311
|
+
* @returns An observable that emits `ApiResult<boolean>`.
|
|
1312
|
+
*/
|
|
1313
|
+
recoverPassword(params) {
|
|
1314
|
+
return this.httpClient.post(this.core.serviceUri + '/account/password/recover', params);
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Re-reads the licence of the signed-in account: company, expiry, quotas and allowed modules.
|
|
1318
|
+
*
|
|
1319
|
+
* Does NOT update the stored context: what the caller does with a fresh licence -- refresh a
|
|
1320
|
+
* settings page, recompute a quota, store it -- is the caller's business, and a service that
|
|
1321
|
+
* quietly rewrote the session context under it would be doing more than it was asked.
|
|
1322
|
+
* @returns An observable that emits `ApiResult<EvolutionUserInfo>`.
|
|
1323
|
+
*/
|
|
1324
|
+
refreshLicence() {
|
|
1325
|
+
return this.httpClient.get(this.core.serviceUri + '/account/licenses/refresh/' + (this.core.loginInfo?.context?.userId ?? -1));
|
|
1326
|
+
}
|
|
1118
1327
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AccountService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
|
|
1119
1328
|
static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.3", ngImport: i0, type: AccountService }); }
|
|
1120
1329
|
}
|
|
@@ -1163,9 +1372,18 @@ class EvolutionService {
|
|
|
1163
1372
|
* @returns The current {@link EvolutionLoginInfo}, or undefined.
|
|
1164
1373
|
*/
|
|
1165
1374
|
get loginInfo() { return this.core.loginInfo; }
|
|
1166
|
-
/**
|
|
1375
|
+
/**
|
|
1376
|
+
* Gets the signed-in account with its licence context.
|
|
1377
|
+
* @returns The current {@link EvolutionUserInfo}, or undefined when there is no session.
|
|
1378
|
+
*/
|
|
1379
|
+
get user() { return this.core.loginInfo?.context; }
|
|
1380
|
+
/**
|
|
1381
|
+
* `true` when this browser has a session the API has accepted. It starts from the stored context
|
|
1382
|
+
* and is confirmed -- or left to the interceptor's 401 handling -- by the `/session/me` call
|
|
1383
|
+
* {@link initialize} makes.
|
|
1384
|
+
*/
|
|
1167
1385
|
get loggedIn() { return this.core.loggedIn; }
|
|
1168
|
-
/** `true` while
|
|
1386
|
+
/** `true` while an authentication is in flight. */
|
|
1169
1387
|
get loggingIn() { return this.core.loggingIn; }
|
|
1170
1388
|
// ── Lifecycle (the only flattened sub-service methods) ──────────────────────
|
|
1171
1389
|
/**
|
|
@@ -1204,5 +1422,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
1204
1422
|
* Generated bundle index. Do not edit.
|
|
1205
1423
|
*/
|
|
1206
1424
|
|
|
1207
|
-
export { AccountService, ComplianceService, ERPComplianceActivityState, ERPComplianceLawOrigin, ERPComplianceLawState, ERPComplianceLawsSelectionType, ERPComplianceNotificationLimit, ERPComplianceProfileFlags, ERPComplianceProfileRole, ERPComplianceRegisterNotificationType, ERPComplianceRegisterSiteImportOptions, ERPComplianceScope, ERPExportFormat, ERPExportPart, ERPExportSource, ERPExportType, ERPModule, ERPPlace, ERPPlacePermission, ERPRecurrenceFrequencyType, EvolutionComplianceActivityStates, EvolutionComplianceContextInfo, EvolutionComplianceLawChangeStates, EvolutionComplianceLawOrigins, EvolutionComplianceLawStates, EvolutionComplianceNotificationLimits, EvolutionComplianceNotifications, EvolutionComplianceObligationAuthorities, EvolutionComplianceObligationTypes, EvolutionComplianceProfileFlags, EvolutionComplianceProfileRoles, EvolutionComplianceScopes, EvolutionMessages, EvolutionRecurrenceFrequencyTypes, EvolutionService, EvolutionServiceFlags, LoginService, evolutionAuthInterceptor };
|
|
1425
|
+
export { AccountService, ComplianceService, ERPComplianceActivityState, ERPComplianceLawOrigin, ERPComplianceLawState, ERPComplianceLawsSelectionType, ERPComplianceNotificationLimit, ERPComplianceProfileFlags, ERPComplianceProfileRole, ERPComplianceRegisterNotificationType, ERPComplianceRegisterSiteImportOptions, ERPComplianceScope, ERPExportFormat, ERPExportPart, ERPExportSource, ERPExportType, ERPModule, ERPPlace, ERPPlacePermission, ERPRecurrenceFrequencyType, EVOLUTION_ACTIVE_ACCOUNT_KEY, EvolutionComplianceActivityStates, EvolutionComplianceContextInfo, EvolutionComplianceLawChangeStates, EvolutionComplianceLawOrigins, EvolutionComplianceLawStates, EvolutionComplianceNotificationLimits, EvolutionComplianceNotifications, EvolutionComplianceObligationAuthorities, EvolutionComplianceObligationTypes, EvolutionComplianceProfileFlags, EvolutionComplianceProfileRoles, EvolutionComplianceScopes, EvolutionMessages, EvolutionRecurrenceFrequencyTypes, EvolutionService, EvolutionServiceFlags, LoginService, evolutionAuthInterceptor };
|
|
1208
1426
|
//# sourceMappingURL=arsedizioni-ars-utils-evolution.common.mjs.map
|