@idp.global/sdk 19.3.0 → 19.5.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/changelog.md +66 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_browser/classes.idpclient.js +1 -1
- package/dist_ts_browser/classes.idprequests.js +1 -1
- package/dist_ts_browser/classes.relyingpartybearerclient.d.ts +313 -0
- package/dist_ts_browser/classes.relyingpartybearerclient.js +646 -0
- package/dist_ts_browser/index.d.ts +1 -0
- package/dist_ts_browser/index.js +2 -1
- package/dist_ts_browser/plugins.js +1 -1
- package/dist_ts_browser/secret-delivery.js +1 -1
- package/dist_ts_relyingparty/classes.authorityobserver.d.ts +23 -0
- package/dist_ts_relyingparty/classes.authorityobserver.js +68 -13
- package/dist_ts_relyingparty/classes.errors.d.ts +5 -1
- package/dist_ts_relyingparty/classes.errors.js +3 -1
- package/dist_ts_relyingparty/classes.relyingpartyroutes.d.ts +12 -0
- package/dist_ts_relyingparty/classes.relyingpartyroutes.js +33 -6
- package/dist_ts_relyingparty/classes.relyingpartysessions.d.ts +161 -11
- package/dist_ts_relyingparty/classes.relyingpartysessions.js +381 -55
- package/dist_ts_relyingparty/classes.rpmodels.d.ts +6 -4
- package/dist_ts_relyingparty/classes.rpmodels.js +8 -6
- package/dist_ts_relyingparty/contract.bearer.d.ts +119 -0
- package/dist_ts_relyingparty/contract.bearer.js +13 -0
- package/dist_ts_relyingparty/helpers.bearer.d.ts +3 -4
- package/dist_ts_relyingparty/helpers.bearer.js +4 -6
- package/dist_ts_relyingparty/interfaces.d.ts +75 -26
- package/package.json +2 -1
- package/readme.md +363 -21
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_browser/classes.relyingpartybearerclient.ts +901 -0
- package/ts_browser/index.ts +1 -0
- package/ts_relyingparty/classes.authorityobserver.ts +80 -12
- package/ts_relyingparty/classes.errors.ts +7 -1
- package/ts_relyingparty/classes.relyingpartyroutes.ts +38 -7
- package/ts_relyingparty/classes.relyingpartysessions.ts +463 -65
- package/ts_relyingparty/classes.rpmodels.ts +7 -4
- package/ts_relyingparty/contract.bearer.ts +145 -0
- package/ts_relyingparty/helpers.bearer.ts +10 -14
- package/ts_relyingparty/interfaces.ts +93 -33
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type IOidcProvider } from './classes.idpoidcprovider.js';
|
|
2
2
|
import type { IRelyingPartyModels } from './classes.rpmodels.js';
|
|
3
|
-
import type { IAuthorityTokenSource, IProviderToken, IRelyingPartyBearerDelivery, IRelyingPartyBearerHandoff, IRelyingPartyBearerLoginRequest, IRelyingPartyBearerLoginStart, IRelyingPartyBearerOptions, IRelyingPartyBearerReturnTargets, IRelyingPartyBearerTokens, IRelyingPartyHooks, IRelyingPartyLiveSession, IRelyingPartyLoginOptions, IRelyingPartyLoginStart, IRelyingPartySession, IRelyingPartySessionOpening, IResolvedRelyingPartySession, TRelyingPartyPrincipals, TRelyingPartySessionEndReason } from './interfaces.js';
|
|
3
|
+
import type { IAuthorityTokenSource, IProviderToken, IRelyingPartyBearerDelivery, IRelyingPartyBearerHandoff, IRelyingPartyBearerHandoffOpening, IRelyingPartyBearerLoginRequest, IRelyingPartyBearerLoginStart, IRelyingPartyBearerOptions, IRelyingPartyBearerReturnTargets, IRelyingPartyBearerTokens, IRelyingPartyHooks, IRelyingPartyLiveSession, IRelyingPartyLoginOptions, IRelyingPartyLoginStart, IRelyingPartySession, IRelyingPartySessionOpening, IRelyingPartySessionsErasure, IRelyingPartySessionsPage, IRelyingPartySessionsPageOptions, IResolvedRelyingPartySession, TRelyingPartyPrincipals, TRelyingPartySessionEndReason } from './interfaces.js';
|
|
4
4
|
export declare const RELYING_PARTY_DEFAULT_SESSION_MAX_AGE_SECONDS: number;
|
|
5
5
|
export declare const RELYING_PARTY_DEFAULT_LOGIN_PATH_PREFIX = "/auth/";
|
|
6
6
|
/** The label the CSRF HMAC starts with unless the application names its own. */
|
|
@@ -9,6 +9,10 @@ export declare const RELYING_PARTY_DEFAULT_CSRF_LABEL: string;
|
|
|
9
9
|
export declare const LOGIN_ATTEMPT_MAX_AGE_SECONDS: number;
|
|
10
10
|
/** The longest intent a login carries (`IRelyingPartyLoginOptions.intent`), in characters. */
|
|
11
11
|
export declare const RELYING_PARTY_MAX_INTENT_LENGTH = 4096;
|
|
12
|
+
/** How many sessions one page of `sessionsOfPrincipal` holds unless its `limit` says otherwise. */
|
|
13
|
+
export declare const RELYING_PARTY_DEFAULT_SESSIONS_PAGE_LIMIT = 100;
|
|
14
|
+
/** The largest `limit` of `sessionsOfPrincipal`; the store reads no larger page. */
|
|
15
|
+
export declare const RELYING_PARTY_MAX_SESSIONS_PAGE_LIMIT = 1000;
|
|
12
16
|
/** How long a bearer hand-off code can be claimed, at most; never past its session. */
|
|
13
17
|
export declare const BEARER_HANDOFF_MAX_AGE_SECONDS = 60;
|
|
14
18
|
/** How long a bearer access token lives unless `bearer.accessTokenMaxAgeSeconds` says otherwise. */
|
|
@@ -40,6 +44,12 @@ export interface IRelyingPartySessionsOptions<TPrincipal extends object | undefi
|
|
|
40
44
|
sessionEncryptionKey: Buffer;
|
|
41
45
|
/** 32 bytes; the CSRF HMAC key */
|
|
42
46
|
csrfKey: Buffer;
|
|
47
|
+
/**
|
|
48
|
+
* `true` (the default): cookie sessions with a session-bound CSRF token,
|
|
49
|
+
* beside bearer hand-off mode when `bearer` is given. Bearer hand-off alone,
|
|
50
|
+
* without any cookie, is `IRelyingPartyBearerOnlySessionsOptions`.
|
|
51
|
+
*/
|
|
52
|
+
cookieSessions?: true;
|
|
43
53
|
/** the label the CSRF HMAC starts with; an application with live sessions keeps its own */
|
|
44
54
|
csrfLabel?: string;
|
|
45
55
|
sessionMaxAgeSeconds?: number;
|
|
@@ -63,16 +73,43 @@ export interface IRelyingPartySessionsOptions<TPrincipal extends object | undefi
|
|
|
63
73
|
/** the clock every comparison reads; tests step it instead of sleeping */
|
|
64
74
|
now?: () => number;
|
|
65
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Bearer-only mode (`cookieSessions: false`): the application's own clients
|
|
78
|
+
* sign in through bearer hand-off and nothing else. The relying party never
|
|
79
|
+
* reads or sets a cookie — not even the clearing intent cookie of a callback
|
|
80
|
+
* that is no bearer login — so it has no CSRF token and takes no CSRF key:
|
|
81
|
+
* `csrfKey`, `csrfLabel` and `cookieNames` are refused, not ignored. The
|
|
82
|
+
* cookie steps (`openSession`, `startLogin`, `completeLogin`, `abandonLogin`,
|
|
83
|
+
* `finalizeLogin`, `clearSessionCookie`, `clearIntentCookie`) throw
|
|
84
|
+
* `RelyingPartyError('login_method_unavailable')` and `resolveCookieSession`
|
|
85
|
+
* answers `null`.
|
|
86
|
+
*/
|
|
87
|
+
export interface IRelyingPartyBearerOnlySessionsOptions<TPrincipal extends object | undefined = undefined> extends Omit<IRelyingPartySessionsOptions<TPrincipal>, 'provider' | 'csrfKey' | 'csrfLabel' | 'cookieNames' | 'cookieSessions' | 'bearer'> {
|
|
88
|
+
cookieSessions: false;
|
|
89
|
+
/** every bearer session comes from a live provider login, directly or through a hand-off opened from one */
|
|
90
|
+
provider: IOidcProvider;
|
|
91
|
+
bearer: IRelyingPartyBearerOptions;
|
|
92
|
+
csrfKey?: never;
|
|
93
|
+
csrfLabel?: never;
|
|
94
|
+
cookieNames?: never;
|
|
95
|
+
}
|
|
66
96
|
/**
|
|
67
97
|
* The relying-party session layer: provider logins through the authorization
|
|
68
98
|
* code flow with PKCE, HttpOnly cookie sessions with a session-bound CSRF
|
|
69
99
|
* token, provider tokens sealed at rest and rotated under a cross-replica
|
|
70
100
|
* lock, and session ends that hand the tokens back.
|
|
71
101
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
102
|
+
* With `cookieSessions: false` it runs bearer hand-off mode alone and never
|
|
103
|
+
* touches a cookie (`IRelyingPartyBearerOnlySessionsOptions`).
|
|
104
|
+
*
|
|
105
|
+
* Nothing here runs on a timer: expired login attempts, bearer state and
|
|
106
|
+
* ended sessions are swept one bounded batch at a time when a login starts or
|
|
107
|
+
* the application opens a session, and the only timer — the revocation
|
|
108
|
+
* budget — is unreferenced and cleared on every path. Every session comes
|
|
109
|
+
* from a login start, `openSession` or `openBearerHandoff`, and each of those
|
|
110
|
+
* sweeps a batch of at least one first, so removal keeps pace with creation
|
|
111
|
+
* and the ended sessions kept stay bounded. Nothing here does I/O while it is
|
|
112
|
+
* constructed, so it is built before the database connects.
|
|
76
113
|
*
|
|
77
114
|
* `TPrincipal` is what a `principals.resolve` answers; with `isActive`
|
|
78
115
|
* principals it stays `undefined`.
|
|
@@ -85,9 +122,11 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
85
122
|
readonly intentCookieName: string;
|
|
86
123
|
readonly loginPathPrefix: string;
|
|
87
124
|
readonly sessionMaxAgeSeconds: number;
|
|
88
|
-
constructor(optionsArg: IRelyingPartySessionsOptions<TPrincipal>);
|
|
125
|
+
constructor(optionsArg: IRelyingPartySessionsOptions<TPrincipal> | IRelyingPartyBearerOnlySessionsOptions<TPrincipal>);
|
|
89
126
|
/** Whether provider logins are offered at all. */
|
|
90
127
|
get providerLoginEnabled(): boolean;
|
|
128
|
+
/** Whether cookie sessions are offered; `false` only in bearer-only mode (`cookieSessions: false`). */
|
|
129
|
+
get cookieSessionsEnabled(): boolean;
|
|
91
130
|
/** Whether bearer hand-off mode is offered. */
|
|
92
131
|
get bearerLoginEnabled(): boolean;
|
|
93
132
|
/** The return targets bearer clients may name, per kind, as configured; `undefined` without bearer mode. */
|
|
@@ -109,7 +148,11 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
109
148
|
principalId: string;
|
|
110
149
|
maxAgeSeconds?: number;
|
|
111
150
|
}): Promise<IRelyingPartySessionOpening>;
|
|
112
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* The live session behind the session cookie, or `null`. A read touches it
|
|
153
|
+
* at most once a minute. Always `null` in bearer-only mode, which reads no
|
|
154
|
+
* cookie.
|
|
155
|
+
*/
|
|
113
156
|
resolveCookieSession(cookieHeaderArg: string | null | undefined): Promise<IResolvedRelyingPartySession<TPrincipal> | null>;
|
|
114
157
|
/**
|
|
115
158
|
* Re-checks a session by its id with the same strength as a cookie request:
|
|
@@ -142,6 +185,34 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
142
185
|
endSessionsOfPrincipal(principalIdArg: string, optionsArg?: {
|
|
143
186
|
keepSessionId?: string;
|
|
144
187
|
}): Promise<number>;
|
|
188
|
+
/**
|
|
189
|
+
* Erases every session of a principal, live or ended, and every row that
|
|
190
|
+
* belongs to one, without waiting for a sweep: what an application runs
|
|
191
|
+
* when it deletes an account, after its own erasers and before its user
|
|
192
|
+
* record.
|
|
193
|
+
*
|
|
194
|
+
* First each session not revoked yet, an expired one too, ends as
|
|
195
|
+
* `endSession` ends it: its tokens stop working at once, `onSessionEnded`
|
|
196
|
+
* names the principal, and its provider tokens go back to idp.global,
|
|
197
|
+
* which a deleted row could no longer do. Then each session's rows go: in
|
|
198
|
+
* bearer mode its hand-offs, claimed or not, and its token generations;
|
|
199
|
+
* the login attempt a provider session came out of; and the session row
|
|
200
|
+
* last, because it is the only link from the principal to the others, so
|
|
201
|
+
* an erase that fails half way leaves a retry everything it needs. A
|
|
202
|
+
* session opened or a token issued while the erase runs is not covered;
|
|
203
|
+
* it works only while the principal is active.
|
|
204
|
+
*/
|
|
205
|
+
eraseSessionsOfPrincipal(principalIdArg: string): Promise<IRelyingPartySessionsErasure>;
|
|
206
|
+
/**
|
|
207
|
+
* One page of a principal's live sessions, newest first: the ones neither
|
|
208
|
+
* ended nor expired, and none at all while the principal is not active —
|
|
209
|
+
* exactly the sessions `isSessionLive` confirms. Pass the page's
|
|
210
|
+
* `nextCursor` as `cursor` to read the next one; the last page has none.
|
|
211
|
+
* The cursor is opaque, and one this method did not answer is refused with
|
|
212
|
+
* `invalid_sessions_cursor`. A session opened while the pages are read is
|
|
213
|
+
* listed only when it sorts after the cursor; none is listed twice.
|
|
214
|
+
*/
|
|
215
|
+
sessionsOfPrincipal(principalIdArg: string, optionsArg?: IRelyingPartySessionsPageOptions): Promise<IRelyingPartySessionsPage>;
|
|
145
216
|
clearSessionCookie(): string;
|
|
146
217
|
clearIntentCookie(): string;
|
|
147
218
|
/**
|
|
@@ -195,6 +266,35 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
195
266
|
* at most and only its hash is stored. A code nobody claims ends its session.
|
|
196
267
|
*/
|
|
197
268
|
completeBearerLogin(callbackUrlArg: URL): Promise<IRelyingPartyBearerHandoff>;
|
|
269
|
+
/**
|
|
270
|
+
* Opens a bearer session for a device the application pairs to a person
|
|
271
|
+
* who is signed in here, and answers its single-use hand-off code, which
|
|
272
|
+
* the device claims at `bearer/claim` with the verifier of `codeChallenge`,
|
|
273
|
+
* exactly like the code of a bearer login.
|
|
274
|
+
*
|
|
275
|
+
* `openerSessionId` names the session that confirmed the pairing, taken
|
|
276
|
+
* from the application's own record, never from the device. It must be a
|
|
277
|
+
* live provider login: not ended, not expired, its principal active, and
|
|
278
|
+
* with a provider login of its own. A session the application opened
|
|
279
|
+
* itself or one opened here does not qualify, so a paired device never
|
|
280
|
+
* pairs another one. Anything else throws
|
|
281
|
+
* `RelyingPartyError('invalid_handoff_opener')`.
|
|
282
|
+
*
|
|
283
|
+
* The new session acts as the opener's principal and has no provider login
|
|
284
|
+
* of its own: it ends by its expiry, `endSession`, `endSessionsOfPrincipal`,
|
|
285
|
+
* `bearer/logout` or the reuse of one of its credentials, never by an
|
|
286
|
+
* idp.global logout, and `AuthorityObserver` refuses it
|
|
287
|
+
* (`authority_login_required`). `maxAgeSeconds` is a whole number from 1 to
|
|
288
|
+
* `sessionMaxAgeSeconds`, the default, as for `openSession`. The code lives
|
|
289
|
+
* `BEARER_HANDOFF_MAX_AGE_SECONDS` at most and never past the session, and
|
|
290
|
+
* only its hash is stored; a code nobody claims ends its session. Nothing
|
|
291
|
+
* is stored for a refused opening.
|
|
292
|
+
*/
|
|
293
|
+
openBearerHandoff(optionsArg: {
|
|
294
|
+
openerSessionId: string;
|
|
295
|
+
codeChallenge: string;
|
|
296
|
+
maxAgeSeconds?: number;
|
|
297
|
+
}): Promise<IRelyingPartyBearerHandoffOpening>;
|
|
198
298
|
/** `abandonLogin` for a bearer login: the provider came back without a code. */
|
|
199
299
|
abandonBearerLogin(callbackUrlArg: URL): Promise<void>;
|
|
200
300
|
/**
|
|
@@ -210,9 +310,11 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
210
310
|
* access and refresh token answer, and the old access token stops working.
|
|
211
311
|
* Rotation is strict — a client serializes its refreshes — because a
|
|
212
312
|
* refresh token that comes back after it was spent ends the whole session.
|
|
213
|
-
* The provider login is checked on the way: a login
|
|
214
|
-
* the session here (`invalid_bearer_grant`), an
|
|
215
|
-
* with `AuthorityTokenError('authority_unavailable')`
|
|
313
|
+
* The provider login of a bearer login is checked on the way: a login
|
|
314
|
+
* idp.global ended ends the session here (`invalid_bearer_grant`), an
|
|
315
|
+
* unreachable provider fails with `AuthorityTokenError('authority_unavailable')`
|
|
316
|
+
* and spends nothing. A session `openBearerHandoff` opened has no provider
|
|
317
|
+
* login (no `idpSubject` is stored) and rotates without the provider.
|
|
216
318
|
*/
|
|
217
319
|
refreshBearerTokens(refreshTokenArg: unknown): Promise<IRelyingPartyBearerTokens>;
|
|
218
320
|
/**
|
|
@@ -235,8 +337,25 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
235
337
|
* is unknown — is rotated first. The session has to belong to
|
|
236
338
|
* `expectedPrincipalIdArg`, so a credential is never evaluated with another
|
|
237
339
|
* person's tokens.
|
|
340
|
+
*
|
|
341
|
+
* With `rejectedAccessToken` (`IAuthorityTokenSource`) the call rotates the
|
|
342
|
+
* session's token whatever is stored, and only a rotation that it starts
|
|
343
|
+
* marks its answer `rotatedAfterRejection`: one that started before it, in
|
|
344
|
+
* this process or another replica's, says nothing about the rejection, so
|
|
345
|
+
* the call awaits it and throws `authority_unavailable`. A refused rotation
|
|
346
|
+
* ends the session (`refresh_token_refused`) and throws
|
|
347
|
+
* `authority_login_required`. A session without a refresh token has nothing
|
|
348
|
+
* to rotate with: it throws `authority_unavailable`, or ends as
|
|
349
|
+
* `access_token_unusable` when its token is dead.
|
|
350
|
+
*/
|
|
351
|
+
liveProviderToken(sessionIdArg: string, expectedPrincipalIdArg: string, optionsArg?: {
|
|
352
|
+
rejectedAccessToken?: string;
|
|
353
|
+
}): Promise<IProviderToken>;
|
|
354
|
+
/**
|
|
355
|
+
* One rotation of the session in this process at a time. A forced call that
|
|
356
|
+
* finds one running waits for it, so a refusal still ends it, and is
|
|
357
|
+
* unavailable otherwise: that rotation started before the call.
|
|
238
358
|
*/
|
|
239
|
-
liveProviderToken(sessionIdArg: string, expectedPrincipalIdArg: string): Promise<IProviderToken>;
|
|
240
359
|
private refreshProviderToken;
|
|
241
360
|
private runProviderRefresh;
|
|
242
361
|
/**
|
|
@@ -246,6 +365,7 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
246
365
|
*/
|
|
247
366
|
private tokenAfterForeignRefresh;
|
|
248
367
|
private requireProvider;
|
|
368
|
+
private requireCookieSessions;
|
|
249
369
|
private requireBearer;
|
|
250
370
|
private checkLoginOptions;
|
|
251
371
|
/** An unsaved attempt with its state, nonce and sealed verifier; the caller binds it and names where it returns. */
|
|
@@ -270,6 +390,12 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
270
390
|
private createProviderSession;
|
|
271
391
|
/** Where a bearer attempt's outcome goes, while its return target is still configured; `null` otherwise. */
|
|
272
392
|
private bearerDeliveryOfAttempt;
|
|
393
|
+
/**
|
|
394
|
+
* Stores a new hand-off of a session, bound to the client's challenge, and
|
|
395
|
+
* answers its code, which is stored only as a hash. The code lives
|
|
396
|
+
* `BEARER_HANDOFF_MAX_AGE_SECONDS` at most and never past the session.
|
|
397
|
+
*/
|
|
398
|
+
private storeBearerHandoff;
|
|
273
399
|
/** Stores the next token generation of a live session and answers its tokens; none of them is stored in the clear. */
|
|
274
400
|
private issueBearerTokens;
|
|
275
401
|
/** A spent hand-off code or refresh token came back: whoever holds it, the session ends. */
|
|
@@ -282,6 +408,19 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
282
408
|
private endUnclaimedHandoff;
|
|
283
409
|
/** Revokes a session that was never handed out and erases its sealed tokens. */
|
|
284
410
|
private discardUnopenedSession;
|
|
411
|
+
/**
|
|
412
|
+
* Revokes a session not revoked yet and erases its sealed tokens; answers
|
|
413
|
+
* the document as it was before, `null` when it was over already. Its
|
|
414
|
+
* expiry comes forward to the revocation, never later, so the sweep finds
|
|
415
|
+
* an ended session by its expiry alone and `revokedAt` keeps when and that
|
|
416
|
+
* it was ended. Two conditional writes keep the earlier of the two times:
|
|
417
|
+
* the first ends a session within its expiry, the second one past it. A
|
|
418
|
+
* crash between them leaves an expired row unrevoked, which reads as over
|
|
419
|
+
* and is swept; a row revoked before 19.4.0 kept its later expiry, which is
|
|
420
|
+
* harmless because every reader checks `revokedAt` or reads live rows only,
|
|
421
|
+
* and it ages out.
|
|
422
|
+
*/
|
|
423
|
+
private revokeSessionDocument;
|
|
285
424
|
/**
|
|
286
425
|
* Tells the application a session ended. Its hook never stands between the
|
|
287
426
|
* local revocation and the provider revocation: a throw or a rejection is
|
|
@@ -326,6 +465,17 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
|
|
|
326
465
|
* sign-in.
|
|
327
466
|
*/
|
|
328
467
|
private sweepBearerState;
|
|
468
|
+
/**
|
|
469
|
+
* Removes sessions an hour past their expiry, oldest first, one bounded
|
|
470
|
+
* page per login start and per session the application opens. An ended
|
|
471
|
+
* session's expiry is its revocation at the latest, so the expiry index
|
|
472
|
+
* finds expired and revoked sessions alike. Each of them ended already: the
|
|
473
|
+
* row goes without an end, a hook or a provider call. A row is deleted only
|
|
474
|
+
* while its expiry is still past the cutoff, so a row whose expiry moved
|
|
475
|
+
* after the page was read stays. A sweep that cannot run never blocks a
|
|
476
|
+
* sign-in.
|
|
477
|
+
*/
|
|
478
|
+
private sweepEndedSessions;
|
|
329
479
|
/**
|
|
330
480
|
* Reports one diagnostic. Every cleanup step reports through here, so a
|
|
331
481
|
* throwing sink must not end it: the event and the sink's failure go to
|