@tstdl/base 0.93.150 → 0.93.151
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.
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type Observable } from 'rxjs';
|
|
2
1
|
import type { AfterResolve } from '../../injector/index.js';
|
|
3
2
|
import { afterResolve } from '../../injector/index.js';
|
|
4
3
|
import type { Record } from '../../types/index.js';
|
|
@@ -36,7 +35,7 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
36
35
|
* Observable for authentication errors.
|
|
37
36
|
* Emits when a refresh fails.
|
|
38
37
|
*/
|
|
39
|
-
readonly error$: Observable<Error>;
|
|
38
|
+
readonly error$: import("rxjs").Observable<Error>;
|
|
40
39
|
/** Current token */
|
|
41
40
|
readonly token: import("../../signals/api.js").WritableSignal<TokenPayload<AdditionalTokenPayload> | undefined>;
|
|
42
41
|
/** Current raw token */
|
|
@@ -58,23 +57,23 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
58
57
|
/** Whether the user is impersonated */
|
|
59
58
|
readonly impersonated: import("../../signals/api.js").Signal<boolean>;
|
|
60
59
|
/** Current token */
|
|
61
|
-
readonly token$: Observable<TokenPayload<AdditionalTokenPayload> | undefined>;
|
|
60
|
+
readonly token$: import("rxjs").Observable<TokenPayload<AdditionalTokenPayload> | undefined>;
|
|
62
61
|
/** Emits when token is available (not undefined) */
|
|
63
|
-
readonly definedToken$: Observable<Exclude<TokenPayload<AdditionalTokenPayload>, void | undefined>>;
|
|
62
|
+
readonly definedToken$: import("rxjs").Observable<Exclude<TokenPayload<AdditionalTokenPayload>, void | undefined>>;
|
|
64
63
|
/** Emits when a valid token is available (not undefined and not expired) */
|
|
65
|
-
readonly validToken$: Observable<Exclude<TokenPayload<AdditionalTokenPayload>, void | undefined>>;
|
|
64
|
+
readonly validToken$: import("rxjs").Observable<Exclude<TokenPayload<AdditionalTokenPayload>, void | undefined>>;
|
|
66
65
|
/** Current subject */
|
|
67
|
-
readonly subjectId$: Observable<string | undefined>;
|
|
66
|
+
readonly subjectId$: import("rxjs").Observable<string | undefined>;
|
|
68
67
|
/** Emits when subject is available */
|
|
69
|
-
readonly definedSubjectId$: Observable<string>;
|
|
68
|
+
readonly definedSubjectId$: import("rxjs").Observable<string>;
|
|
70
69
|
/** Current session id */
|
|
71
|
-
readonly sessionId$: Observable<string | undefined>;
|
|
70
|
+
readonly sessionId$: import("rxjs").Observable<string | undefined>;
|
|
72
71
|
/** Emits when session id is available */
|
|
73
|
-
readonly definedSessionId$: Observable<string>;
|
|
72
|
+
readonly definedSessionId$: import("rxjs").Observable<string>;
|
|
74
73
|
/** Whether the user is logged in */
|
|
75
|
-
readonly isLoggedIn$: Observable<boolean>;
|
|
74
|
+
readonly isLoggedIn$: import("rxjs").Observable<boolean>;
|
|
76
75
|
/** Emits when the user logs out */
|
|
77
|
-
readonly loggedOut$: Observable<void>;
|
|
76
|
+
readonly loggedOut$: import("rxjs").Observable<void>;
|
|
78
77
|
private get authenticationData();
|
|
79
78
|
private set authenticationData(value);
|
|
80
79
|
private get impersonatorAuthenticationData();
|
|
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import { Subject, filter, firstValueFrom, from,
|
|
10
|
+
import { Subject, filter, firstValueFrom, from, race, skip, takeUntil, timer } from 'rxjs';
|
|
11
11
|
import { CancellationSignal } from '../../cancellation/token.js';
|
|
12
12
|
import { BadRequestError } from '../../errors/bad-request.error.js';
|
|
13
13
|
import { ForbiddenError } from '../../errors/forbidden.error.js';
|
|
@@ -398,76 +398,60 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
398
398
|
if (this.isLoggedIn()) {
|
|
399
399
|
await this.syncClock();
|
|
400
400
|
}
|
|
401
|
+
// Helper to sleep until the delay passes OR a vital state change occurs
|
|
402
|
+
const waitForNextAction = async (delayMs, referenceExp) => await firstValueFrom(race([
|
|
403
|
+
timer(Math.max(10, delayMs)),
|
|
404
|
+
from(this.disposeSignal),
|
|
405
|
+
this.token$.pipe(filter((t) => t?.exp !== referenceExp)),
|
|
406
|
+
this.forceRefreshRequested$.pipe(filter(Boolean),
|
|
407
|
+
// Skip the current value if already true to prevent infinite tight loops
|
|
408
|
+
skip(this.forceRefreshRequested() ? 1 : 0)),
|
|
409
|
+
]), { defaultValue: undefined });
|
|
401
410
|
while (this.disposeSignal.isUnset) {
|
|
402
|
-
const
|
|
411
|
+
const token = this.token();
|
|
412
|
+
// 1. Wait for login/token if none is present
|
|
413
|
+
if (isUndefined(token)) {
|
|
414
|
+
await firstValueFrom(race([this.definedToken$, from(this.disposeSignal)]), { defaultValue: undefined });
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
403
417
|
try {
|
|
404
|
-
const token = iterationToken;
|
|
405
|
-
if (isUndefined(token)) {
|
|
406
|
-
// Wait for login or dispose.
|
|
407
|
-
// We ignore forceRefreshToken here because we can't refresh without a token.
|
|
408
|
-
await firstValueFrom(race([this.definedToken$, from(this.disposeSignal)]), { defaultValue: undefined });
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
418
|
const now = this.estimatedServerTimestampSeconds();
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
|
|
419
|
+
const buffer = calculateRefreshBufferSeconds(token);
|
|
420
|
+
const needsRefresh = this.forceRefreshRequested() || (now >= token.exp - buffer);
|
|
421
|
+
// 2. Handle token refresh
|
|
415
422
|
if (needsRefresh) {
|
|
416
423
|
const lockResult = await this.lock.tryUse(undefined, async () => {
|
|
417
424
|
const currentToken = this.token();
|
|
425
|
+
if (isUndefined(currentToken)) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
// Passive Sync: Verify if refresh is still needed once lock is acquired
|
|
418
429
|
const currentNow = this.estimatedServerTimestampSeconds();
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
const stillNeedsRefresh = isDefined(currentToken) && (this.forceRefreshRequested() || (currentNow >= (currentToken.exp - currentRefreshBufferSeconds)));
|
|
430
|
+
const currentBuffer = calculateRefreshBufferSeconds(currentToken);
|
|
431
|
+
const stillNeedsRefresh = this.forceRefreshRequested() || (currentNow >= currentToken.exp - currentBuffer);
|
|
422
432
|
if (stillNeedsRefresh) {
|
|
423
433
|
await this.refresh();
|
|
424
434
|
}
|
|
425
|
-
|
|
426
|
-
this.forceRefreshRequested.set(false);
|
|
427
|
-
}
|
|
435
|
+
this.forceRefreshRequested.set(false);
|
|
428
436
|
});
|
|
437
|
+
// If lock is held by another instance/tab, wait briefly for it to finish (passive sync)
|
|
429
438
|
if (!lockResult.success) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.token$.pipe(filter((t) => t?.jti != token.jti), map(() => 'token')),
|
|
434
|
-
from(this.disposeSignal),
|
|
435
|
-
]), { defaultValue: undefined });
|
|
436
|
-
if (changeReason == 'token') {
|
|
439
|
+
await waitForNextAction(5000, token.exp);
|
|
440
|
+
// If another tab successfully refreshed, the expiration will have changed
|
|
441
|
+
if (this.token()?.exp !== token.exp) {
|
|
437
442
|
this.forceRefreshRequested.set(false);
|
|
438
443
|
}
|
|
439
|
-
continue;
|
|
440
444
|
}
|
|
445
|
+
continue; // Re-evaluate the loop with the newly refreshed (or synced) token
|
|
441
446
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const currentRefreshBufferSeconds = calculateRefreshBufferSeconds(currentToken);
|
|
447
|
-
const delay = clamp((currentToken.exp - this.estimatedServerTimestampSeconds() - currentRefreshBufferSeconds) * millisecondsPerSecond, minRefreshDelay, maxRefreshDelay);
|
|
448
|
-
const wakeUpSignals = [
|
|
449
|
-
from(this.disposeSignal),
|
|
450
|
-
this.token$.pipe(filter((t) => t?.jti != currentToken.jti)),
|
|
451
|
-
];
|
|
452
|
-
if (!forceRefresh) {
|
|
453
|
-
wakeUpSignals.push(this.forceRefreshRequested$.pipe(filter((requested) => requested)));
|
|
454
|
-
}
|
|
455
|
-
if (delay > 0) {
|
|
456
|
-
await firstValueFrom(race([timer(delay), ...wakeUpSignals]), { defaultValue: undefined });
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
await firstValueFrom(race([timer(2500), ...wakeUpSignals]), { defaultValue: undefined });
|
|
460
|
-
}
|
|
447
|
+
// 3. Calculate delay and sleep until the next scheduled refresh window
|
|
448
|
+
const timeUntilRefreshMs = (token.exp - this.estimatedServerTimestampSeconds() - buffer) * millisecondsPerSecond;
|
|
449
|
+
const delay = clamp(timeUntilRefreshMs, minRefreshDelay, maxRefreshDelay);
|
|
450
|
+
await waitForNextAction(delay, token.exp);
|
|
461
451
|
}
|
|
462
452
|
catch (error) {
|
|
463
453
|
this.logger.error(error);
|
|
464
|
-
|
|
465
|
-
await firstValueFrom(race([
|
|
466
|
-
timer(2500),
|
|
467
|
-
from(this.disposeSignal),
|
|
468
|
-
this.token$.pipe(filter((t) => t?.jti != currentToken?.jti)),
|
|
469
|
-
this.forceRefreshRequested$.pipe(filter((requested) => requested), skip(this.forceRefreshRequested() ? 1 : 0)),
|
|
470
|
-
]), { defaultValue: undefined });
|
|
454
|
+
await waitForNextAction(2500, token.exp);
|
|
471
455
|
}
|
|
472
456
|
}
|
|
473
457
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NotSupportedError } from '../../errors/not-supported.error.js';
|
|
2
2
|
import { filterUndefinedObjectProperties, fromEntries, hasOwnProperty, objectEntries } from '../../utils/object/object.js';
|
|
3
3
|
import { isDefined, isNotNull, isNumber, isString } from '../../utils/type-guards.js';
|
|
4
|
-
import { ArraySchema, BooleanSchema, DateSchema, DefaultSchema, EnumerationSchema, LiteralSchema,
|
|
4
|
+
import { AnySchema, ArraySchema, BooleanSchema, DateSchema, DefaultSchema, EnumerationSchema, LiteralSchema, NullableSchema, NumberSchema, ObjectSchema, OptionalSchema, StringSchema, TransformSchema, Uint8ArraySchema, UnionSchema, UnknownSchema } from '../schemas/index.js';
|
|
5
5
|
import { schemaTestableToSchema } from '../testable.js';
|
|
6
6
|
export function convertToOpenApiSchema(testable) {
|
|
7
7
|
const schema = schemaTestableToSchema(testable);
|
|
@@ -17,7 +17,7 @@ export function convertToOpenApiSchema(testable) {
|
|
|
17
17
|
function convertToOpenApiSchemaBase(schema) {
|
|
18
18
|
if (schema instanceof ObjectSchema) {
|
|
19
19
|
const entries = objectEntries(schema.properties);
|
|
20
|
-
const convertedEntries = entries.map(([property, propertySchema]) => [property, convertToOpenApiSchema(
|
|
20
|
+
const convertedEntries = entries.map(([property, propertySchema]) => [property, convertToOpenApiSchema(propertySchema)]);
|
|
21
21
|
const required = entries
|
|
22
22
|
.filter(([, propertySchema]) => !(propertySchema instanceof OptionalSchema) && !((propertySchema instanceof NullableSchema) && (propertySchema.schema instanceof OptionalSchema)))
|
|
23
23
|
.map(([property]) => property);
|
|
@@ -29,13 +29,13 @@ function convertToOpenApiSchemaBase(schema) {
|
|
|
29
29
|
maxProperties: isNumber(schema.maximumPropertiesCount) ? schema.maximumPropertiesCount : undefined,
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
if (schema instanceof DefaultSchema) {
|
|
32
|
+
if (schema instanceof DefaultSchema) {
|
|
33
33
|
return {
|
|
34
34
|
...convertToOpenApiSchema(schema.schema),
|
|
35
35
|
default: schema.defaultValue,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
if (schema instanceof TransformSchema) {
|
|
38
|
+
if (schema instanceof TransformSchema) {
|
|
39
39
|
return convertToOpenApiSchema(schema.schema);
|
|
40
40
|
}
|
|
41
41
|
if (schema instanceof StringSchema) {
|
|
@@ -114,19 +114,16 @@ function convertToOpenApiSchemaBase(schema) {
|
|
|
114
114
|
nullable: true,
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
+
if (schema instanceof OptionalSchema) {
|
|
118
|
+
return convertToOpenApiSchema(schema.schema);
|
|
119
|
+
}
|
|
117
120
|
if (schema instanceof UnionSchema) {
|
|
118
121
|
return {
|
|
119
122
|
anyOf: schema.schemas.map((innerSchema) => convertToOpenApiSchema(innerSchema)),
|
|
120
123
|
};
|
|
121
124
|
}
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
function stripOptional(schema) {
|
|
125
|
-
if ((schema instanceof OptionalSchema)) {
|
|
126
|
-
return schema.schema;
|
|
127
|
-
}
|
|
128
|
-
if ((schema instanceof NullableSchema) && (schema.schema instanceof OptionalSchema)) {
|
|
129
|
-
return nullable(schema.schema.schema);
|
|
125
|
+
if ((schema instanceof AnySchema) || (schema instanceof UnknownSchema)) {
|
|
126
|
+
return {};
|
|
130
127
|
}
|
|
131
|
-
|
|
128
|
+
throw new NotSupportedError(`Schema "${schema.name}" not supported.`);
|
|
132
129
|
}
|