gameglue 4.0.1 → 4.0.2
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/LICENSE +21 -21
- package/README.md +275 -275
- package/babel.config.cjs +5 -5
- package/coverage/auth.js.html +525 -525
- package/coverage/base.css +224 -224
- package/coverage/block-navigation.js +87 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +175 -175
- package/coverage/index.js.html +309 -309
- package/coverage/lcov-report/auth.js.html +525 -525
- package/coverage/lcov-report/base.css +224 -224
- package/coverage/lcov-report/block-navigation.js +87 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +175 -175
- package/coverage/lcov-report/index.js.html +309 -309
- package/coverage/lcov-report/listener.js.html +528 -528
- package/coverage/lcov-report/prettify.css +1 -1
- package/coverage/lcov-report/prettify.js +2 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -210
- package/coverage/lcov-report/user.js.html +117 -117
- package/coverage/lcov-report/utils.js.html +117 -117
- package/coverage/lcov.info +391 -391
- package/coverage/listener.js.html +528 -528
- package/coverage/prettify.css +1 -1
- package/coverage/prettify.js +2 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -210
- package/coverage/user.js.html +117 -117
- package/coverage/utils.js.html +117 -117
- package/dist/gg.cjs.js +1 -1
- package/dist/gg.cjs.js.map +1 -1
- package/dist/gg.esm.js +1 -1
- package/dist/gg.esm.js.map +1 -1
- package/dist/gg.umd.js +1 -1
- package/dist/gg.umd.js.map +1 -1
- package/examples/certs/cert.pem +19 -19
- package/examples/certs/key.pem +28 -28
- package/examples/flight-dashboard.html +431 -431
- package/examples/server.js +99 -99
- package/examples/telemetry-validator.html +1410 -1410
- package/jest.config.cjs +33 -33
- package/package.json +56 -56
- package/rollup.config.js +57 -57
- package/src/auth.js +255 -255
- package/src/auth.spec.js +481 -481
- package/src/index.js +168 -168
- package/src/listener.js +196 -196
- package/src/listener.spec.js +598 -598
- package/src/presence_listener.js +112 -112
- package/src/test/fixtures.js +106 -106
- package/src/test/setup.js +51 -51
- package/src/utils.js +63 -63
- package/src/utils.spec.js +78 -78
- package/types/index.d.ts +338 -338
- package/webpack.config.js +15 -15
package/src/auth.js
CHANGED
|
@@ -1,255 +1,255 @@
|
|
|
1
|
-
import { OidcClient } from 'oidc-client-ts';
|
|
2
|
-
import { storage, isCorsError, logCorsHelp } from './utils';
|
|
3
|
-
import jwt_decode from 'jwt-decode';
|
|
4
|
-
|
|
5
|
-
const DEFAULT_AUTH_URL = 'https://auth.gameglue.gg/realms/GameGlue';
|
|
6
|
-
|
|
7
|
-
// Track if callback is being processed (prevents double-processing)
|
|
8
|
-
let _callbackPromise = null;
|
|
9
|
-
|
|
10
|
-
export class GameGlueAuth {
|
|
11
|
-
constructor(cfg) {
|
|
12
|
-
const authority = cfg.authUrl || DEFAULT_AUTH_URL;
|
|
13
|
-
this._oidcSettings = {
|
|
14
|
-
authority,
|
|
15
|
-
client_id: cfg.clientId,
|
|
16
|
-
redirect_uri: removeTrailingSlashes(cfg.redirect_uri || window.location.href),
|
|
17
|
-
post_logout_redirect_uri: removeTrailingSlashes(window.location.href),
|
|
18
|
-
response_type: "code",
|
|
19
|
-
scope: `openid ${(cfg.scopes || []).join(' ')}`,
|
|
20
|
-
response_mode: "fragment",
|
|
21
|
-
filterProtocolClaims: true
|
|
22
|
-
};
|
|
23
|
-
this._oidcClient = new OidcClient(this._oidcSettings);
|
|
24
|
-
this._refreshCallback = () => {};
|
|
25
|
-
this._refreshTimeout = null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Check if user is authenticated.
|
|
30
|
-
* If OAuth callback params are in URL, processes them first.
|
|
31
|
-
* Safe to call multiple times - idempotent.
|
|
32
|
-
* @returns {Promise<boolean>}
|
|
33
|
-
*/
|
|
34
|
-
async isAuthenticated() {
|
|
35
|
-
// If callback params present, process them first
|
|
36
|
-
if (this._hasCallbackParams()) {
|
|
37
|
-
await this._processCallback();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Check for valid tokens
|
|
41
|
-
return this._hasValidTokens();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Redirect to OAuth login page.
|
|
46
|
-
* Does not return - navigates away.
|
|
47
|
-
*/
|
|
48
|
-
login() {
|
|
49
|
-
this._oidcClient.createSigninRequest({ state: { bar: 15 } }).then((req) => {
|
|
50
|
-
window.location = req.url;
|
|
51
|
-
}).catch((err) => {
|
|
52
|
-
if (isCorsError(err)) {
|
|
53
|
-
logCorsHelp('Login Request', this._oidcSettings.authority);
|
|
54
|
-
}
|
|
55
|
-
console.error('Failed to create signin request:', err);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Log out the user.
|
|
61
|
-
* Clears local tokens and optionally redirects to Keycloak logout.
|
|
62
|
-
* @param {Object} options - { redirect?: boolean }
|
|
63
|
-
*/
|
|
64
|
-
logout(options = {}) {
|
|
65
|
-
// Clear local tokens
|
|
66
|
-
storage.remove('gg-auth-token');
|
|
67
|
-
storage.remove('gg-refresh-token');
|
|
68
|
-
clearTimeout(this._refreshTimeout);
|
|
69
|
-
|
|
70
|
-
// Optionally redirect to Keycloak logout
|
|
71
|
-
if (options.redirect !== false) {
|
|
72
|
-
const logoutUrl = `${this._oidcSettings.authority}/protocol/openid-connect/logout?post_logout_redirect_uri=${encodeURIComponent(this._oidcSettings.post_logout_redirect_uri)}`;
|
|
73
|
-
window.location.href = logoutUrl;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get the current user's ID.
|
|
79
|
-
* @throws {Error} if not authenticated
|
|
80
|
-
* @returns {string}
|
|
81
|
-
*/
|
|
82
|
-
getUser() {
|
|
83
|
-
const token = this._getAccessToken();
|
|
84
|
-
if (!token) {
|
|
85
|
-
throw new Error('Not authenticated');
|
|
86
|
-
}
|
|
87
|
-
const decoded = jwt_decode(token);
|
|
88
|
-
return decoded.sub;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Get the access token for API calls.
|
|
93
|
-
* @returns {string|null}
|
|
94
|
-
*/
|
|
95
|
-
getAccessToken() {
|
|
96
|
-
return this._getAccessToken();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Register callback for token refresh events.
|
|
101
|
-
* @param {Function} callback
|
|
102
|
-
*/
|
|
103
|
-
onTokenRefreshed(callback) {
|
|
104
|
-
this._refreshCallback = callback;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ============ Internal Methods ============
|
|
108
|
-
|
|
109
|
-
_hasCallbackParams() {
|
|
110
|
-
return location.hash.includes("state=") &&
|
|
111
|
-
(location.hash.includes("code=") || location.hash.includes("error="));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
_clearCallbackUrl() {
|
|
115
|
-
window.history.replaceState("", document.title, window.location.pathname + window.location.search);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async _processCallback() {
|
|
119
|
-
// If already processing, wait for that to complete
|
|
120
|
-
if (_callbackPromise) {
|
|
121
|
-
await _callbackPromise;
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Start processing
|
|
126
|
-
_callbackPromise = this._doProcessCallback();
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
await _callbackPromise;
|
|
130
|
-
} finally {
|
|
131
|
-
_callbackPromise = null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async _doProcessCallback() {
|
|
136
|
-
try {
|
|
137
|
-
const response = await this._oidcClient.processSigninResponse(window.location.href);
|
|
138
|
-
|
|
139
|
-
if (response.error) {
|
|
140
|
-
this._clearCallbackUrl();
|
|
141
|
-
throw new Error(response.error);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!response.access_token) {
|
|
145
|
-
this._clearCallbackUrl();
|
|
146
|
-
throw new Error('No access token received');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
this._setAccessToken(response.access_token);
|
|
150
|
-
this._setRefreshToken(response.refresh_token);
|
|
151
|
-
this._clearCallbackUrl();
|
|
152
|
-
} catch (err) {
|
|
153
|
-
// If we failed but tokens exist (another call succeeded), that's fine
|
|
154
|
-
if (this._hasValidTokens()) {
|
|
155
|
-
this._clearCallbackUrl();
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
this._clearCallbackUrl();
|
|
159
|
-
throw err;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
_hasValidTokens() {
|
|
164
|
-
const token = this._getAccessToken();
|
|
165
|
-
if (!token) {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
const decoded = jwt_decode(token);
|
|
171
|
-
const expirationDate = new Date(decoded.exp * 1000);
|
|
172
|
-
return expirationDate > new Date();
|
|
173
|
-
} catch {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
_getAccessToken() {
|
|
179
|
-
const token = storage.get('gg-auth-token');
|
|
180
|
-
if (token) {
|
|
181
|
-
this._setTokenRefreshTimeout(token);
|
|
182
|
-
}
|
|
183
|
-
return token || undefined;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
_setAccessToken(token) {
|
|
187
|
-
this._setTokenRefreshTimeout(token);
|
|
188
|
-
return storage.set('gg-auth-token', token);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
_setRefreshToken(token) {
|
|
192
|
-
return storage.set('gg-refresh-token', token);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
_getRefreshToken() {
|
|
196
|
-
return storage.get('gg-refresh-token');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
_setTokenRefreshTimeout(token) {
|
|
200
|
-
if (!token) return;
|
|
201
|
-
|
|
202
|
-
clearTimeout(this._refreshTimeout);
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
const timeUntilExp = (jwt_decode(token).exp * 1000) - Date.now() - 5000;
|
|
206
|
-
if (timeUntilExp > 0) {
|
|
207
|
-
this._refreshTimeout = setTimeout(() => {
|
|
208
|
-
this._attemptRefresh();
|
|
209
|
-
}, timeUntilExp);
|
|
210
|
-
}
|
|
211
|
-
} catch {
|
|
212
|
-
// Invalid token, ignore
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async _attemptRefresh() {
|
|
217
|
-
const url = `${this._oidcSettings.authority}/protocol/openid-connect/token`;
|
|
218
|
-
const client_id = this._oidcSettings.client_id;
|
|
219
|
-
const refresh_token = this._getRefreshToken();
|
|
220
|
-
const grant_type = 'refresh_token';
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
const response = await fetch(url, {
|
|
224
|
-
method: 'POST',
|
|
225
|
-
headers: {
|
|
226
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
227
|
-
},
|
|
228
|
-
body: new URLSearchParams({
|
|
229
|
-
client_id,
|
|
230
|
-
grant_type,
|
|
231
|
-
refresh_token
|
|
232
|
-
})
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
if (response.status === 200) {
|
|
236
|
-
const resObj = await response.json();
|
|
237
|
-
this._setAccessToken(resObj.access_token);
|
|
238
|
-
this._setRefreshToken(resObj.refresh_token);
|
|
239
|
-
this._refreshCallback(resObj.access_token);
|
|
240
|
-
}
|
|
241
|
-
} catch (e) {
|
|
242
|
-
if (isCorsError(e)) {
|
|
243
|
-
logCorsHelp('Token Refresh', url);
|
|
244
|
-
}
|
|
245
|
-
console.error('Token refresh failed:', e);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function removeTrailingSlashes(url) {
|
|
251
|
-
if (url.endsWith('/')) {
|
|
252
|
-
return url.replace(/\/+$/, '');
|
|
253
|
-
}
|
|
254
|
-
return url;
|
|
255
|
-
}
|
|
1
|
+
import { OidcClient } from 'oidc-client-ts';
|
|
2
|
+
import { storage, isCorsError, logCorsHelp } from './utils';
|
|
3
|
+
import jwt_decode from 'jwt-decode';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_AUTH_URL = 'https://auth.gameglue.gg/realms/GameGlue';
|
|
6
|
+
|
|
7
|
+
// Track if callback is being processed (prevents double-processing)
|
|
8
|
+
let _callbackPromise = null;
|
|
9
|
+
|
|
10
|
+
export class GameGlueAuth {
|
|
11
|
+
constructor(cfg) {
|
|
12
|
+
const authority = cfg.authUrl || DEFAULT_AUTH_URL;
|
|
13
|
+
this._oidcSettings = {
|
|
14
|
+
authority,
|
|
15
|
+
client_id: cfg.clientId,
|
|
16
|
+
redirect_uri: removeTrailingSlashes(cfg.redirect_uri || window.location.href),
|
|
17
|
+
post_logout_redirect_uri: removeTrailingSlashes(window.location.href),
|
|
18
|
+
response_type: "code",
|
|
19
|
+
scope: `openid ${(cfg.scopes || []).join(' ')}`,
|
|
20
|
+
response_mode: "fragment",
|
|
21
|
+
filterProtocolClaims: true
|
|
22
|
+
};
|
|
23
|
+
this._oidcClient = new OidcClient(this._oidcSettings);
|
|
24
|
+
this._refreshCallback = () => {};
|
|
25
|
+
this._refreshTimeout = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if user is authenticated.
|
|
30
|
+
* If OAuth callback params are in URL, processes them first.
|
|
31
|
+
* Safe to call multiple times - idempotent.
|
|
32
|
+
* @returns {Promise<boolean>}
|
|
33
|
+
*/
|
|
34
|
+
async isAuthenticated() {
|
|
35
|
+
// If callback params present, process them first
|
|
36
|
+
if (this._hasCallbackParams()) {
|
|
37
|
+
await this._processCallback();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check for valid tokens
|
|
41
|
+
return this._hasValidTokens();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Redirect to OAuth login page.
|
|
46
|
+
* Does not return - navigates away.
|
|
47
|
+
*/
|
|
48
|
+
login() {
|
|
49
|
+
this._oidcClient.createSigninRequest({ state: { bar: 15 } }).then((req) => {
|
|
50
|
+
window.location = req.url;
|
|
51
|
+
}).catch((err) => {
|
|
52
|
+
if (isCorsError(err)) {
|
|
53
|
+
logCorsHelp('Login Request', this._oidcSettings.authority);
|
|
54
|
+
}
|
|
55
|
+
console.error('Failed to create signin request:', err);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Log out the user.
|
|
61
|
+
* Clears local tokens and optionally redirects to Keycloak logout.
|
|
62
|
+
* @param {Object} options - { redirect?: boolean }
|
|
63
|
+
*/
|
|
64
|
+
logout(options = {}) {
|
|
65
|
+
// Clear local tokens
|
|
66
|
+
storage.remove('gg-auth-token');
|
|
67
|
+
storage.remove('gg-refresh-token');
|
|
68
|
+
clearTimeout(this._refreshTimeout);
|
|
69
|
+
|
|
70
|
+
// Optionally redirect to Keycloak logout
|
|
71
|
+
if (options.redirect !== false) {
|
|
72
|
+
const logoutUrl = `${this._oidcSettings.authority}/protocol/openid-connect/logout?post_logout_redirect_uri=${encodeURIComponent(this._oidcSettings.post_logout_redirect_uri)}`;
|
|
73
|
+
window.location.href = logoutUrl;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the current user's ID.
|
|
79
|
+
* @throws {Error} if not authenticated
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
getUser() {
|
|
83
|
+
const token = this._getAccessToken();
|
|
84
|
+
if (!token) {
|
|
85
|
+
throw new Error('Not authenticated');
|
|
86
|
+
}
|
|
87
|
+
const decoded = jwt_decode(token);
|
|
88
|
+
return decoded.sub;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the access token for API calls.
|
|
93
|
+
* @returns {string|null}
|
|
94
|
+
*/
|
|
95
|
+
getAccessToken() {
|
|
96
|
+
return this._getAccessToken();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Register callback for token refresh events.
|
|
101
|
+
* @param {Function} callback
|
|
102
|
+
*/
|
|
103
|
+
onTokenRefreshed(callback) {
|
|
104
|
+
this._refreshCallback = callback;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============ Internal Methods ============
|
|
108
|
+
|
|
109
|
+
_hasCallbackParams() {
|
|
110
|
+
return location.hash.includes("state=") &&
|
|
111
|
+
(location.hash.includes("code=") || location.hash.includes("error="));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_clearCallbackUrl() {
|
|
115
|
+
window.history.replaceState("", document.title, window.location.pathname + window.location.search);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async _processCallback() {
|
|
119
|
+
// If already processing, wait for that to complete
|
|
120
|
+
if (_callbackPromise) {
|
|
121
|
+
await _callbackPromise;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Start processing
|
|
126
|
+
_callbackPromise = this._doProcessCallback();
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
await _callbackPromise;
|
|
130
|
+
} finally {
|
|
131
|
+
_callbackPromise = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async _doProcessCallback() {
|
|
136
|
+
try {
|
|
137
|
+
const response = await this._oidcClient.processSigninResponse(window.location.href);
|
|
138
|
+
|
|
139
|
+
if (response.error) {
|
|
140
|
+
this._clearCallbackUrl();
|
|
141
|
+
throw new Error(response.error);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!response.access_token) {
|
|
145
|
+
this._clearCallbackUrl();
|
|
146
|
+
throw new Error('No access token received');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this._setAccessToken(response.access_token);
|
|
150
|
+
this._setRefreshToken(response.refresh_token);
|
|
151
|
+
this._clearCallbackUrl();
|
|
152
|
+
} catch (err) {
|
|
153
|
+
// If we failed but tokens exist (another call succeeded), that's fine
|
|
154
|
+
if (this._hasValidTokens()) {
|
|
155
|
+
this._clearCallbackUrl();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this._clearCallbackUrl();
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_hasValidTokens() {
|
|
164
|
+
const token = this._getAccessToken();
|
|
165
|
+
if (!token) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const decoded = jwt_decode(token);
|
|
171
|
+
const expirationDate = new Date(decoded.exp * 1000);
|
|
172
|
+
return expirationDate > new Date();
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_getAccessToken() {
|
|
179
|
+
const token = storage.get('gg-auth-token');
|
|
180
|
+
if (token) {
|
|
181
|
+
this._setTokenRefreshTimeout(token);
|
|
182
|
+
}
|
|
183
|
+
return token || undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_setAccessToken(token) {
|
|
187
|
+
this._setTokenRefreshTimeout(token);
|
|
188
|
+
return storage.set('gg-auth-token', token);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_setRefreshToken(token) {
|
|
192
|
+
return storage.set('gg-refresh-token', token);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_getRefreshToken() {
|
|
196
|
+
return storage.get('gg-refresh-token');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
_setTokenRefreshTimeout(token) {
|
|
200
|
+
if (!token) return;
|
|
201
|
+
|
|
202
|
+
clearTimeout(this._refreshTimeout);
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const timeUntilExp = (jwt_decode(token).exp * 1000) - Date.now() - 5000;
|
|
206
|
+
if (timeUntilExp > 0) {
|
|
207
|
+
this._refreshTimeout = setTimeout(() => {
|
|
208
|
+
this._attemptRefresh();
|
|
209
|
+
}, timeUntilExp);
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// Invalid token, ignore
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async _attemptRefresh() {
|
|
217
|
+
const url = `${this._oidcSettings.authority}/protocol/openid-connect/token`;
|
|
218
|
+
const client_id = this._oidcSettings.client_id;
|
|
219
|
+
const refresh_token = this._getRefreshToken();
|
|
220
|
+
const grant_type = 'refresh_token';
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const response = await fetch(url, {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
headers: {
|
|
226
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
227
|
+
},
|
|
228
|
+
body: new URLSearchParams({
|
|
229
|
+
client_id,
|
|
230
|
+
grant_type,
|
|
231
|
+
refresh_token
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (response.status === 200) {
|
|
236
|
+
const resObj = await response.json();
|
|
237
|
+
this._setAccessToken(resObj.access_token);
|
|
238
|
+
this._setRefreshToken(resObj.refresh_token);
|
|
239
|
+
this._refreshCallback(resObj.access_token);
|
|
240
|
+
}
|
|
241
|
+
} catch (e) {
|
|
242
|
+
if (isCorsError(e)) {
|
|
243
|
+
logCorsHelp('Token Refresh', url);
|
|
244
|
+
}
|
|
245
|
+
console.error('Token refresh failed:', e);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function removeTrailingSlashes(url) {
|
|
251
|
+
if (url.endsWith('/')) {
|
|
252
|
+
return url.replace(/\/+$/, '');
|
|
253
|
+
}
|
|
254
|
+
return url;
|
|
255
|
+
}
|