@arsedizioni/ars-utils 22.5.14 → 22.5.16
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/fesm2022/arsedizioni-ars-utils-clipper.common.mjs +101 -26
- package/fesm2022/arsedizioni-ars-utils-clipper.common.mjs.map +1 -1
- package/fesm2022/arsedizioni-ars-utils-clipper.ui.mjs +5 -5
- package/fesm2022/arsedizioni-ars-utils-clipper.ui.mjs.map +1 -1
- package/fesm2022/arsedizioni-ars-utils-evolution.common.mjs +85 -25
- 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 +54 -10
- package/types/arsedizioni-ars-utils-evolution.common.d.ts +39 -11
|
@@ -516,6 +516,19 @@ function isAuthEndpoint(url) {
|
|
|
516
516
|
|| url.includes('/passkeys/request-options')
|
|
517
517
|
|| url.includes('/passkeys/login');
|
|
518
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Returns whether the URL is the silent link probe, whose failures must never reach the user.
|
|
521
|
+
*
|
|
522
|
+
* `/session/status` runs unconditionally at every bootstrap, including for users who never linked
|
|
523
|
+
* Evolution at all. When the API is unreachable, reporting that failure would show "Evolution non è
|
|
524
|
+
* disponibile" to somebody who never asked Evolution for anything: the error is still rethrown for
|
|
525
|
+
* the caller to observe, but nothing is broadcast.
|
|
526
|
+
* @param url - The request URL.
|
|
527
|
+
* @returns True when the URL is the status probe.
|
|
528
|
+
*/
|
|
529
|
+
function isStatusProbe(url) {
|
|
530
|
+
return url.includes('/session/status');
|
|
531
|
+
}
|
|
519
532
|
/**
|
|
520
533
|
* Resolves the user-facing message for a failed request.
|
|
521
534
|
* @param error - The raw HTTP error object.
|
|
@@ -641,7 +654,11 @@ function evolutionAuthInterceptor(serviceUri, flags = EvolutionServiceFlags.None
|
|
|
641
654
|
// at all. Everything that gets here is an Evolution request anyway: the others returned
|
|
642
655
|
// before the pipe was built.
|
|
643
656
|
const errorStatus = parseInt(error?.status ?? '0');
|
|
644
|
-
if (
|
|
657
|
+
if (isStatusProbe(authenticatedRequest.url)) {
|
|
658
|
+
// The silent probe stays silent: its caller reads the outcome from the rethrown error,
|
|
659
|
+
// nobody else needs to hear about it.
|
|
660
|
+
}
|
|
661
|
+
else if (errorStatus === 401 && !isAuthEndpoint(authenticatedRequest.url)) {
|
|
645
662
|
// Expired or invalidated session on an authenticated request. Notified once: several
|
|
646
663
|
// in-flight requests failing together is how a single expiry becomes a wall of dialogs.
|
|
647
664
|
if (!sessionExpiredNotified) {
|
|
@@ -741,6 +758,11 @@ class CoreService {
|
|
|
741
758
|
}
|
|
742
759
|
return this._loginInfo;
|
|
743
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* Gets the signed-in account.
|
|
763
|
+
* @returns The current {@link EvolutionUserInfo}, or `undefined` when there is no session.
|
|
764
|
+
*/
|
|
765
|
+
get user() { return this.loginInfo?.context; }
|
|
744
766
|
/**
|
|
745
767
|
* Sets the logged-in state.
|
|
746
768
|
* @param value - The new logged-in value.
|
|
@@ -895,7 +917,7 @@ class LoginService {
|
|
|
895
917
|
* Gets the signed-in account with its licence context.
|
|
896
918
|
* @returns The current {@link EvolutionUserInfo}, or undefined when there is no session.
|
|
897
919
|
*/
|
|
898
|
-
get user() { return this.core.
|
|
920
|
+
get user() { return this.core.user; }
|
|
899
921
|
/** `true` when this browser has a session the API has accepted. */
|
|
900
922
|
get loggedIn() { return this.core.loggedIn; }
|
|
901
923
|
/** `true` while an authentication is in flight. */
|
|
@@ -925,8 +947,8 @@ class LoginService {
|
|
|
925
947
|
clear() { this.core.clear(); }
|
|
926
948
|
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
927
949
|
/**
|
|
928
|
-
* Initialises the service with the back-end URI and optional feature flags, and
|
|
929
|
-
*
|
|
950
|
+
* Initialises the service with the back-end URI and optional feature flags, and asks the API — the
|
|
951
|
+
* only party that can read the HttpOnly cookie — whether this browser still holds a session.
|
|
930
952
|
*
|
|
931
953
|
* Must be called once during application bootstrap, before any other method. What it used to do
|
|
932
954
|
* on a refresh was announce `LOGIN_COMPLETED` on the strength of a value in local storage; the
|
|
@@ -950,12 +972,13 @@ class LoginService {
|
|
|
950
972
|
this.handleBroadcastMessage(message);
|
|
951
973
|
});
|
|
952
974
|
}
|
|
953
|
-
//
|
|
954
|
-
//
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
975
|
+
// The probe runs UNCONDITIONALLY, where it used to run only when a stored context existed. The
|
|
976
|
+
// stored context is just a hint -- "this browser signed in at some point" -- and the cookie can
|
|
977
|
+
// outlive it: local storage cleared while the month-long cookie stayed put made this library
|
|
978
|
+
// declare "no link" with a perfectly valid session in the browser. The API is the only one that
|
|
979
|
+
// knows, and it is asked every time.
|
|
980
|
+
this.core.loggingIn.set(true);
|
|
981
|
+
this.restoreSession();
|
|
959
982
|
}
|
|
960
983
|
/**
|
|
961
984
|
* Dispatches an incoming broadcast message to the appropriate handler.
|
|
@@ -1046,6 +1069,18 @@ class LoginService {
|
|
|
1046
1069
|
return r;
|
|
1047
1070
|
}));
|
|
1048
1071
|
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Asks the API whether this browser holds a live session, without ever failing with a 401: the
|
|
1074
|
+
* route is anonymous and answers `connected: false` to a caller with no session. This is the one
|
|
1075
|
+
* honest way to know the link state -- the auth cookie is HttpOnly and belongs to the API's host,
|
|
1076
|
+
* so it cannot be read from here, and its presence would prove nothing anyway, since the session
|
|
1077
|
+
* also lives server-side and may have been revoked or evicted.
|
|
1078
|
+
* @returns An observable that emits the `ApiResult<EvolutionSessionStatus>`.
|
|
1079
|
+
*/
|
|
1080
|
+
status() {
|
|
1081
|
+
return this.httpClient
|
|
1082
|
+
.get(this.core.serviceUri + '/session/status');
|
|
1083
|
+
}
|
|
1049
1084
|
/**
|
|
1050
1085
|
* Reads the current session from the server, which resolves it from the auth cookie's claims.
|
|
1051
1086
|
* Used to restore the state after a page refresh instead of trusting what local storage holds.
|
|
@@ -1068,25 +1103,49 @@ class LoginService {
|
|
|
1068
1103
|
}));
|
|
1069
1104
|
}
|
|
1070
1105
|
/**
|
|
1071
|
-
* Asks `/session/
|
|
1072
|
-
*
|
|
1106
|
+
* Asks `/session/status` whether this browser holds a live session, and hydrates the context
|
|
1107
|
+
* through {@link me} when it does.
|
|
1108
|
+
*
|
|
1109
|
+
* The probe is silent by construction: `/session/status` never answers 401, so a visitor who
|
|
1110
|
+
* never signed in -- or a cookie whose session has died -- comes back as `connected: false` with
|
|
1111
|
+
* no error broadcast and no dialog. Only then is the stale stored context cleared, so the
|
|
1112
|
+
* `loggedIn` signal tells the truth at every bootstrap.
|
|
1073
1113
|
*
|
|
1074
|
-
* Nothing is cleared on failure, and that is deliberate:
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
* A genuine 401 is a different matter and the interceptor has already reported it.
|
|
1114
|
+
* Nothing is cleared on a transport failure, and that is deliberate: an unreachable API is not
|
|
1115
|
+
* proof of a dead session -- the proxy may have answered 502, the device may be offline -- and
|
|
1116
|
+
* clearing on any of those would sign the user out with a perfectly valid cookie.
|
|
1078
1117
|
* @returns void
|
|
1079
1118
|
*/
|
|
1080
1119
|
restoreSession() {
|
|
1081
|
-
|
|
1082
|
-
|
|
1120
|
+
// Whether the probe handed over to `/session/me`, which then owns lowering `loggingIn`.
|
|
1121
|
+
let restoring = false;
|
|
1122
|
+
this.status()
|
|
1123
|
+
.pipe(finalize(() => { if (!restoring) {
|
|
1124
|
+
this.core.loggingIn.set(false);
|
|
1125
|
+
} }))
|
|
1083
1126
|
.subscribe({
|
|
1084
1127
|
next: r => {
|
|
1085
|
-
if (r?.success) {
|
|
1086
|
-
|
|
1128
|
+
if (r?.success && r.value?.connected) {
|
|
1129
|
+
restoring = true;
|
|
1130
|
+
this.me()
|
|
1131
|
+
.pipe(finalize(() => this.core.loggingIn.set(false)))
|
|
1132
|
+
.subscribe({
|
|
1133
|
+
next: m => {
|
|
1134
|
+
if (m?.success) {
|
|
1135
|
+
this.broadcastService.sendMessage(EvolutionMessages.LOGIN_COMPLETED);
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
// Consumed so the rethrown error is not reported a second time as an unhandled one.
|
|
1139
|
+
error: () => { }
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
else if (this.core.loggedIn()) {
|
|
1143
|
+
// The stored context outlived its session: drop it, or the next bootstrap would keep
|
|
1144
|
+
// claiming a session that is gone.
|
|
1145
|
+
this.core.clear();
|
|
1087
1146
|
}
|
|
1088
1147
|
},
|
|
1089
|
-
//
|
|
1148
|
+
// Transport failure: keep the stored hint, the next bootstrap will ask again.
|
|
1090
1149
|
error: () => { }
|
|
1091
1150
|
});
|
|
1092
1151
|
}
|
|
@@ -1322,7 +1381,7 @@ class AccountService {
|
|
|
1322
1381
|
* @returns An observable that emits `ApiResult<EvolutionUserInfo>`.
|
|
1323
1382
|
*/
|
|
1324
1383
|
refreshLicence() {
|
|
1325
|
-
return this.httpClient.get(this.core.serviceUri + '/account/licenses/refresh/' + (this.core.
|
|
1384
|
+
return this.httpClient.get(this.core.serviceUri + '/account/licenses/refresh/' + (this.core.user?.userId ?? -1));
|
|
1326
1385
|
}
|
|
1327
1386
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AccountService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
|
|
1328
1387
|
static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.3", ngImport: i0, type: AccountService }); }
|
|
@@ -1376,11 +1435,12 @@ class EvolutionService {
|
|
|
1376
1435
|
* Gets the signed-in account with its licence context.
|
|
1377
1436
|
* @returns The current {@link EvolutionUserInfo}, or undefined when there is no session.
|
|
1378
1437
|
*/
|
|
1379
|
-
get user() { return this.core.
|
|
1438
|
+
get user() { return this.core.user; }
|
|
1380
1439
|
/**
|
|
1381
1440
|
* `true` when this browser has a session the API has accepted. It starts from the stored context
|
|
1382
|
-
* and is
|
|
1383
|
-
*
|
|
1441
|
+
* and is settled -- raised or cleared -- by the silent `/session/status` probe {@link initialize}
|
|
1442
|
+
* makes, which is the one honest way to read a state that lives in an HttpOnly cookie plus a
|
|
1443
|
+
* server-side lease. This signal IS the link state: bind it, do not poll it.
|
|
1384
1444
|
*/
|
|
1385
1445
|
get loggedIn() { return this.core.loggedIn; }
|
|
1386
1446
|
/** `true` while an authentication is in flight. */
|