@arsedizioni/ars-utils 22.5.15 → 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 +82 -23
- package/fesm2022/arsedizioni-ars-utils-clipper.common.mjs.map +1 -1
- package/fesm2022/arsedizioni-ars-utils-evolution.common.mjs +77 -22
- 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 +42 -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) {
|
|
@@ -930,8 +947,8 @@ class LoginService {
|
|
|
930
947
|
clear() { this.core.clear(); }
|
|
931
948
|
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
932
949
|
/**
|
|
933
|
-
* Initialises the service with the back-end URI and optional feature flags, and
|
|
934
|
-
*
|
|
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.
|
|
935
952
|
*
|
|
936
953
|
* Must be called once during application bootstrap, before any other method. What it used to do
|
|
937
954
|
* on a refresh was announce `LOGIN_COMPLETED` on the strength of a value in local storage; the
|
|
@@ -955,12 +972,13 @@ class LoginService {
|
|
|
955
972
|
this.handleBroadcastMessage(message);
|
|
956
973
|
});
|
|
957
974
|
}
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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();
|
|
964
982
|
}
|
|
965
983
|
/**
|
|
966
984
|
* Dispatches an incoming broadcast message to the appropriate handler.
|
|
@@ -1051,6 +1069,18 @@ class LoginService {
|
|
|
1051
1069
|
return r;
|
|
1052
1070
|
}));
|
|
1053
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
|
+
}
|
|
1054
1084
|
/**
|
|
1055
1085
|
* Reads the current session from the server, which resolves it from the auth cookie's claims.
|
|
1056
1086
|
* Used to restore the state after a page refresh instead of trusting what local storage holds.
|
|
@@ -1073,25 +1103,49 @@ class LoginService {
|
|
|
1073
1103
|
}));
|
|
1074
1104
|
}
|
|
1075
1105
|
/**
|
|
1076
|
-
* Asks `/session/
|
|
1077
|
-
*
|
|
1106
|
+
* Asks `/session/status` whether this browser holds a live session, and hydrates the context
|
|
1107
|
+
* through {@link me} when it does.
|
|
1078
1108
|
*
|
|
1079
|
-
*
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
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.
|
|
1113
|
+
*
|
|
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.
|
|
1083
1117
|
* @returns void
|
|
1084
1118
|
*/
|
|
1085
1119
|
restoreSession() {
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
+
} }))
|
|
1088
1126
|
.subscribe({
|
|
1089
1127
|
next: r => {
|
|
1090
|
-
if (r?.success) {
|
|
1091
|
-
|
|
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();
|
|
1092
1146
|
}
|
|
1093
1147
|
},
|
|
1094
|
-
//
|
|
1148
|
+
// Transport failure: keep the stored hint, the next bootstrap will ask again.
|
|
1095
1149
|
error: () => { }
|
|
1096
1150
|
});
|
|
1097
1151
|
}
|
|
@@ -1384,8 +1438,9 @@ class EvolutionService {
|
|
|
1384
1438
|
get user() { return this.core.user; }
|
|
1385
1439
|
/**
|
|
1386
1440
|
* `true` when this browser has a session the API has accepted. It starts from the stored context
|
|
1387
|
-
* and is
|
|
1388
|
-
*
|
|
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.
|
|
1389
1444
|
*/
|
|
1390
1445
|
get loggedIn() { return this.core.loggedIn; }
|
|
1391
1446
|
/** `true` while an authentication is in flight. */
|