PageSourceSearch

https://www.marktplaats.nl/i/chatbot-mara/js/tokenService.js

js marktplaats.nl collected 2026-09-24 08:26:53 UTC 5,221 bytes, 187 lines download raw bytes

1/**
2 * TokenService
3 *
4 * Responsible for fetching and caching Cognigy Webchat authentication tokens.
5 *
6 * This service:
7 *  - Fetches tokens from a backend identity endpoint.
8 *  - Caches tokens until they expire.
9 *  - Applies a configurable safety margin before expiry.
10 *  - Treats `null` tokens as invalid for authentication purposes.
11 *  - Gracefully handles network or server errors without throwing.
12 *
13 * Design Principles:
14 *  - Minimal complexity.
15 *  - Deterministic behavior.
16 *  - Safe for use in Salesforce LWC environments.
17 *  - No retry or concurrency control (intentional by design).
18 *
19 * Expected Server Response:
20 * {
21 *   token: string | null,
22 *   expiresAt: number (epoch milliseconds)
23 * }
24 *
25 * If `expiresAt` is missing or invalid, a fallback TTL is used.
26 */
27export class TokenService {
28
29    /**
30     * Creates a new TokenService instance.
31     *
32     * @param {string} endpoint
33     *   The HTTP endpoint used to retrieve the webchat token.
34     *
35     * @param {Object} [options]
36     * @param {number} [options.fallbackTtlMs=120000]
37     *   Fallback time-to-live in milliseconds when the server does not
38     *   provide a valid `expiresAt` value.
39     *
40     * @param {number} [options.safetyMarginMs=5000]
41     *   Time in milliseconds subtracted from the server-provided expiry
42     *   to avoid using tokens too close to expiration.
43     */
44    constructor(endpoint, {fallbackTtlMs = 120_000, safetyMarginMs = 5_000} = {}) {
45        this.endpoint = endpoint;
46        this._fallbackTtlMs = fallbackTtlMs;
47        this._safetyMarginMs = safetyMarginMs;
48
49        /**
50         * @private
51         * @type {string|null|undefined}
52         * undefined → never fetched
53         * null      → fetched but invalid/no token
54         * string    → valid token value
55         */
56        this._token = undefined;
57
58        /**
59         * @private
60         * @type {number}
61         * Epoch timestamp (ms) at which the cached token expires.
62         */
63        this._expiresAt = 0;
64    }
65
66    /**
67     * Fetches a valid webchat token.
68     *
69     * If a cached token exists and is still valid, it is returned.
70     * Otherwise, a new token is requested from the backend.
71     *
72     * @param {boolean} [forceRefresh=false]
73     *   If true, bypasses cache validation and forces a new fetch.
74     *
75     * @returns {Promise<string|null>}
76     *   A valid token string, or `null` if:
77     *     - The backend returned no token.
78     *     - The request failed.
79     *     - The token is considered invalid.
80     *
81     * This method never throws.
82     */
83    async fetchWebchatToken(forceRefresh = false) {
84        if (!forceRefresh && this._isTokenValid()) {
85            return this._token;
86        }
87
88        try {
89            const res = await fetch(this.endpoint, {
90                method: 'POST',
91                headers: {'Content-Type': 'application/json'},
92                credentials: 'include'
93            });
94
95            return await this._handleTokenResponse(res);
96
97        } catch (e) {
98            console.warn('[TokenService] fetch failed', e);
99            this._cache(null, Date.now() + 10_000);
100            return null;
101        }
102    }
103
104    /**
105     * Handles the HTTP response from the token endpoint.
106     *
107     * @private
108     * @param {Response} res
109     * @returns {Promise<string|null>}
110     */
111    async _handleTokenResponse(res) {
112        if (!res.ok) {
113            console.warn(`[TokenService] ${res.status} from token endpoint`);
114            this._cache(null, Date.now() + 10_000);
115            return null;
116        }
117
118        const data = await res.json();
119
120        const token = data?.token ?? null;
121        const expiresAt = Number(data?.expiresAt);
122
123        const expiresAtMs = this._resolveExpiry(expiresAt);
124
125        this._cache(token, expiresAtMs);
126
127        return this._token;
128    }
129
130    /**
131     * Determines whether the currently cached token is valid.
132     *
133     * A token is valid if:
134     *   - It is a non-empty string.
135     *   - It has not yet expired.
136     *
137     * @private
138     * @returns {boolean}
139     */
140    _isTokenValid() {
141        const now = Date.now();
142
143        return (
144            typeof this._token === 'string' &&
145            this._token.length > 0 &&
146            now < this._expiresAt
147        );
148    }
149
150    /**
151     * Resolves the final expiration timestamp.
152     *
153     * Uses server-provided expiry when valid,
154     * otherwise falls back to configured TTL.
155     *
156     * @private
157     * @param {number} expiresAtFromServer
158     * @returns {number} Epoch timestamp in milliseconds.
159     */
160    _resolveExpiry(expiresAtFromServer) {
161        const now = Date.now();
162
163        if (
164            Number.isFinite(expiresAtFromServer) &&
165            expiresAtFromServer > now
166        ) {
167            return Math.max(
168                now + 1000,
169                expiresAtFromServer - this._safetyMarginMs
170            );
171        }
172
173        return now + this._fallbackTtlMs;
174    }
175
176    /**
177     * Updates the internal cache.
178     *
179     * @private
180     * @param {string|null} token
181     * @param {number} expiresAtMs
182     */
183    _cache(token, expiresAtMs) {
184        this._token = token ?? null;
185        this._expiresAt = expiresAtMs;
186    }
187}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.