wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
 
import { NetworkResponse } from "./NetworkResponse.js";
import { ServerAuthorizationTokenResponse } from "../response/ServerAuthorizationTokenResponse.js";
import * as Constants from "../utils/Constants.js";
import { CacheManager } from "../cache/CacheManager.js";
import { ServerError } from "../error/ServerError.js";
import {
    getRequestThumbprint,
    RequestThumbprint,
} from "./RequestThumbprint.js";
import { ThrottlingEntity } from "../cache/entities/ThrottlingEntity.js";
import { BaseAuthRequest } from "../request/BaseAuthRequest.js";
 
/** @internal */
export class ThrottlingUtils {
    /**
     * Prepares a RequestThumbprint to be stored as a key.
     * @param thumbprint
     */
    static generateThrottlingStorageKey(thumbprint: RequestThumbprint): string {
        return `${Constants.THROTTLING_PREFIX}.${JSON.stringify(thumbprint)}`;
    }
 
    /**
     * Performs necessary throttling checks before a network request.
     * @param cacheManager
     * @param thumbprint
     */
    static preProcess(
        cacheManager: CacheManager,
        thumbprint: RequestThumbprint,
        correlationId: string
    ): void {
        const key = ThrottlingUtils.generateThrottlingStorageKey(thumbprint);
        const value = cacheManager.getThrottlingCache(key, correlationId);
 
        if (value) {
            if (value.throttleTime < Date.now()) {
                cacheManager.removeItem(key, correlationId);
                return;
            }
            throw new ServerError(
                value.errorCodes?.join(" ") || "",
                value.errorMessage,
                value.subError
            );
        }
    }
 
    /**
     * Performs necessary throttling checks after a network request.
     * @param cacheManager
     * @param thumbprint
     * @param response
     */
    static postProcess(
        cacheManager: CacheManager,
        thumbprint: RequestThumbprint,
        response: NetworkResponse<ServerAuthorizationTokenResponse>,
        correlationId: string
    ): void {
        if (
            ThrottlingUtils.checkResponseStatus(response) ||
            ThrottlingUtils.checkResponseForRetryAfter(response)
        ) {
            const thumbprintValue: ThrottlingEntity = {
                throttleTime: ThrottlingUtils.calculateThrottleTime(
                    parseInt(
                        response.headers[Constants.HeaderNames.RETRY_AFTER]
                    )
                ),
                error: response.body.error,
                errorCodes: response.body.error_codes,
                errorMessage: response.body.error_description,
                subError: response.body.suberror,
            };
            cacheManager.setThrottlingCache(
                ThrottlingUtils.generateThrottlingStorageKey(thumbprint),
                thumbprintValue,
                correlationId
            );
        }
    }
 
    /**
     * Checks a NetworkResponse object's status codes against 429 or 5xx
     * @param response
     */
    static checkResponseStatus(
        response: NetworkResponse<ServerAuthorizationTokenResponse>
    ): boolean {
        return (
            response.status === 429 ||
            (response.status >= 500 && response.status < 600)
        );
    }
 
    /**
     * Checks a NetworkResponse object's RetryAfter header
     * @param response
     */
    static checkResponseForRetryAfter(
        response: NetworkResponse<ServerAuthorizationTokenResponse>
    ): boolean {
        if (response.headers) {
            return (
                response.headers.hasOwnProperty(
                    Constants.HeaderNames.RETRY_AFTER
                ) &&
                (response.status < 200 || response.status >= 300)
            );
        }
        return false;
    }
 
    /**
     * Calculates the Unix-time value for a throttle to expire given throttleTime in seconds.
     * @param throttleTime
     */
    static calculateThrottleTime(throttleTime: number): number {
        const time = throttleTime <= 0 ? 0 : throttleTime;
 
        const currentSeconds = Date.now() / 1000;
        return Math.floor(
            Math.min(
                currentSeconds +
                    (time || Constants.DEFAULT_THROTTLE_TIME_SECONDS),
                currentSeconds + Constants.DEFAULT_MAX_THROTTLE_TIME_SECONDS
            ) * 1000
        );
    }
 
    static removeThrottle(
        cacheManager: CacheManager,
        clientId: string,
        request: BaseAuthRequest,
        homeAccountIdentifier?: string
    ): void {
        const thumbprint = getRequestThumbprint(
            clientId,
            request,
            homeAccountIdentifier
        );
        const key = this.generateThrottlingStorageKey(thumbprint);
        cacheManager.removeItem(key, request.correlationId);
    }
}