@axa-fr/react-oidc 6.0.0-beta9 → 6.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.
Files changed (54) hide show
  1. package/README.md +13 -12
  2. package/dist/FetchToken.d.ts.map +1 -1
  3. package/dist/FetchToken.js +10 -6
  4. package/dist/FetchToken.js.map +1 -1
  5. package/dist/OidcProvider.d.ts +1 -0
  6. package/dist/OidcProvider.d.ts.map +1 -1
  7. package/dist/OidcProvider.js +11 -4
  8. package/dist/OidcProvider.js.map +1 -1
  9. package/dist/OidcSecure.js +2 -2
  10. package/dist/OidcSecure.js.map +1 -1
  11. package/dist/OidcServiceWorker.js +62 -32
  12. package/dist/OidcTrustedDomains.js +7 -2
  13. package/dist/ReactOidc.d.ts.map +1 -1
  14. package/dist/ReactOidc.js +4 -3
  15. package/dist/ReactOidc.js.map +1 -1
  16. package/dist/core/default-component/SilentLogin.component.js +1 -1
  17. package/dist/core/default-component/SilentLogin.component.js.map +1 -1
  18. package/dist/core/routes/OidcRoutes.d.ts.map +1 -1
  19. package/dist/core/routes/OidcRoutes.js +1 -4
  20. package/dist/core/routes/OidcRoutes.js.map +1 -1
  21. package/dist/vanilla/initSession.d.ts +2 -1
  22. package/dist/vanilla/initSession.d.ts.map +1 -1
  23. package/dist/vanilla/initSession.js +7 -7
  24. package/dist/vanilla/initSession.js.map +1 -1
  25. package/dist/vanilla/initWorker.d.ts +2 -3
  26. package/dist/vanilla/initWorker.d.ts.map +1 -1
  27. package/dist/vanilla/initWorker.js +6 -21
  28. package/dist/vanilla/initWorker.js.map +1 -1
  29. package/dist/vanilla/oidc.d.ts +10 -5
  30. package/dist/vanilla/oidc.d.ts.map +1 -1
  31. package/dist/vanilla/oidc.js +463 -492
  32. package/dist/vanilla/oidc.js.map +1 -1
  33. package/dist/vanilla/parseTokens.d.ts +5 -0
  34. package/dist/vanilla/parseTokens.d.ts.map +1 -0
  35. package/dist/vanilla/parseTokens.js +107 -0
  36. package/dist/vanilla/parseTokens.js.map +1 -0
  37. package/package.json +3 -3
  38. package/src/oidc/FetchToken.tsx +7 -4
  39. package/src/oidc/OidcProvider.tsx +9 -0
  40. package/src/oidc/OidcSecure.tsx +2 -2
  41. package/src/oidc/ReactOidc.tsx +4 -3
  42. package/src/oidc/core/default-component/SilentLogin.component.tsx +1 -1
  43. package/src/oidc/core/routes/OidcRoutes.tsx +0 -4
  44. package/src/oidc/vanilla/OidcServiceWorker.js +62 -32
  45. package/src/oidc/vanilla/OidcTrustedDomains.js +7 -2
  46. package/src/oidc/vanilla/initSession.ts +6 -7
  47. package/src/oidc/vanilla/initWorker.ts +6 -15
  48. package/src/oidc/vanilla/oidc.ts +220 -277
  49. package/src/oidc/vanilla/parseTokens.ts +107 -0
  50. package/dist/core/default-component/ServiceWorkerInstall.component.d.ts +0 -4
  51. package/dist/core/default-component/ServiceWorkerInstall.component.d.ts.map +0 -1
  52. package/dist/core/default-component/ServiceWorkerInstall.component.js +0 -131
  53. package/dist/core/default-component/ServiceWorkerInstall.component.js.map +0 -1
  54. package/src/oidc/core/default-component/ServiceWorkerInstall.component.tsx +0 -60
@@ -19,6 +19,7 @@ import timer from './timer';
19
19
  import {CheckSessionIFrame} from "./checkSessionIFrame"
20
20
  import {getParseQueryStringFromLocation} from "./route-utils";
21
21
  import {AuthorizationServiceConfigurationJson} from "@openid/appauth/src/authorization_service_configuration";
22
+ import {computeTimeLeft, isTokensValid, parseOriginalTokens, setTokens} from "./parseTokens";
22
23
 
23
24
  const performTokenRequestAsync= async (url, details, extras) => {
24
25
 
@@ -47,22 +48,9 @@ const performTokenRequestAsync= async (url, details, extras) => {
47
48
  return {success:false, status: response.status}
48
49
  }
49
50
  const tokens = await response.json();
50
-
51
- if(!tokens.issued_at) {
52
- const currentTimeUnixSecond = new Date().getTime() /1000;
53
- tokens.issued_at = currentTimeUnixSecond;
54
- }
55
-
56
- return { success : true,
57
- data : {
58
- accessToken: tokens.access_token,
59
- expiresIn: tokens.expires_in,
60
- idToken: tokens.id_token,
61
- refreshToken: tokens.refresh_token,
62
- scope: tokens.scope,
63
- tokenType: tokens.token_type,
64
- issuedAt: tokens.issued_at
65
- }
51
+ return {
52
+ success : true,
53
+ data: parseOriginalTokens(tokens)
66
54
  };
67
55
  }
68
56
 
@@ -82,7 +70,6 @@ const internalFetch = async (url, headers, numberRetry=0) => {
82
70
  throw e;
83
71
  }
84
72
  } else {
85
-
86
73
  console.error(e.message);
87
74
  throw e; // rethrow other unexpected errors
88
75
  }
@@ -108,36 +95,6 @@ export class OidcAuthorizationServiceConfiguration extends AuthorizationServiceC
108
95
 
109
96
  }
110
97
 
111
- const idTokenPayload = (token) => {
112
- const base64Url = token.split('.')[1];
113
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
114
- const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
115
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
116
- }).join(''));
117
-
118
- return JSON.parse(jsonPayload);
119
- }
120
-
121
- const countLetter = (str, find)=> {
122
- return (str.split(find)).length - 1;
123
- }
124
-
125
- const extractAccessTokenPayload = tokens => {
126
- if(tokens.accessTokenPayload)
127
- {
128
- return tokens.accessTokenPayload;
129
- }
130
- const accessToken = tokens.accessToken;
131
- try{
132
- if (!accessToken || countLetter(accessToken,'.') != 2) {
133
- return null;
134
- }
135
- return JSON.parse(atob(accessToken.split('.')[1]));
136
- } catch (e) {
137
- console.warn(e);
138
- }
139
- return null;
140
- };
141
98
 
142
99
  export interface StringMap {
143
100
  [key: string]: string;
@@ -193,42 +150,45 @@ const loginCallbackWithAutoTokensRenewAsync = async (oidc) => {
193
150
  }
194
151
 
195
152
  const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
196
- const refreshTimeBeforeTokensExpirationInSecond = oidc.configuration.refresh_time_before_tokens_expiration_in_second ?? 60;
153
+ const refreshTimeBeforeTokensExpirationInSecond = oidc.configuration.refresh_time_before_tokens_expiration_in_second;
197
154
  return timer.setTimeout(async () => {
198
- const currentTimeUnixSecond = new Date().getTime() /1000;
199
- const timeInfo = { timeLeft: Math.round(((expiresAt - refreshTimeBeforeTokensExpirationInSecond) - currentTimeUnixSecond))};
155
+ const timeLeft = computeTimeLeft(refreshTimeBeforeTokensExpirationInSecond, expiresAt);
156
+ const timeInfo = { timeLeft };
200
157
  oidc.publishEvent(Oidc.eventNames.token_timer, timeInfo);
201
- if(currentTimeUnixSecond > (expiresAt - refreshTimeBeforeTokensExpirationInSecond)) {
202
- const tokens = await oidc.refreshTokensAsync(refreshToken);
203
- oidc.tokens= await setTokensAsync(oidc.serviceWorker, tokens);
158
+ const {tokens, status} = await oidc.synchroniseTokensAsync(refreshToken);
159
+ oidc.tokens= tokens;
204
160
  if(!oidc.serviceWorker){
205
161
  await oidc.session.setTokens(oidc.tokens);
206
162
  }
207
163
  if(!oidc.tokens){
208
- if(oidc.checkSessionIFrame){
209
- oidc.checkSessionIFrame.stop();
210
- oidc.checkSessionIFrame = null;
211
- }
164
+ await oidc.destroyAsync(status);
212
165
  return;
213
166
  }
214
- oidc.publishEvent(Oidc.eventNames.token_renewed, {});
167
+
215
168
  if(oidc.timeoutId) {
216
169
  oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, oidc.tokens.expiresAt);
217
170
  }
218
- } else{
219
- const tokens = await oidc.syncTokensAsync();
220
- if(tokens && oidc.timeoutId) {
221
- oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, expiresAt);
222
- }
223
- }
171
+
224
172
  }, 1000);
225
173
  }
226
174
 
227
175
  const getLoginSessionKey = (configurationName:string, redirectUri:string) => {
228
176
  return `oidc_login.${configurationName}:${redirectUri}`;
229
177
  }
178
+
179
+ const setLoginParams = (configurationName:string, redirectUri:string, data) =>{
180
+ const sessionKey = getLoginSessionKey(configurationName, redirectUri);
181
+ getLoginParamsCache = data
182
+ sessionStorage[sessionKey] = JSON.stringify(data);
183
+ }
184
+
185
+ let getLoginParamsCache = null;
230
186
  const getLoginParams = (configurationName, redirectUri) => {
231
- return JSON.parse(sessionStorage[getLoginSessionKey(configurationName, redirectUri)]);
187
+ const dataString = sessionStorage[getLoginSessionKey(configurationName, redirectUri)];
188
+ if(!getLoginParamsCache){
189
+ getLoginParamsCache = JSON.parse(dataString);
190
+ }
191
+ return getLoginParamsCache;
232
192
  }
233
193
 
234
194
  const userInfoAsync = async (oidc) => {
@@ -238,11 +198,15 @@ const userInfoAsync = async (oidc) => {
238
198
  if(!oidc.tokens){
239
199
  return null;
240
200
  }
241
- if(oidc.syncTokensAsyncPromise){
242
- await oidc.syncTokensAsyncPromise;
243
- }
244
201
  const accessToken = oidc.tokens.accessToken;
245
-
202
+ if(!accessToken){
203
+ return null;
204
+ }
205
+ // We wait the synchronisation before making a request
206
+ while (oidc.tokens && !isTokensValid(oidc.tokens)){
207
+ await sleepAsync(200);
208
+ }
209
+
246
210
  const oidcServerConfiguration = await oidc.initAsync(oidc.configuration.authority, oidc.configuration.authority_configuration);
247
211
  const url = oidcServerConfiguration.userInfoEndpoint;
248
212
  const fetchUserInfo = async (accessToken) => {
@@ -264,32 +228,11 @@ const userInfoAsync = async (oidc) => {
264
228
  return userInfo;
265
229
  }
266
230
 
267
- const setTokensAsync = async (serviceWorker, tokens) =>{
268
- let accessTokenPayload;
269
- if(tokens == null){
270
- if(serviceWorker){
271
- await serviceWorker.clearAsync();
272
- }
273
- return null;
274
- }
275
- if(serviceWorker){
276
- accessTokenPayload = await serviceWorker.getAccessTokenPayloadAsync();
277
- }
278
- else {
279
- accessTokenPayload = extractAccessTokenPayload(tokens);
280
- }
281
- const _idTokenPayload = idTokenPayload(tokens.idToken);
282
-
283
- const idTokenExipreAt =(_idTokenPayload && _idTokenPayload.exp) ? _idTokenPayload.exp: Number.MAX_VALUE;
284
- const accessTokenExpiresAt = (accessTokenPayload && accessTokenPayload.exp)? accessTokenPayload.exp : tokens.issuedAt + tokens.expiresIn;
285
- const expiresAt = idTokenExipreAt < accessTokenExpiresAt ? idTokenExipreAt : accessTokenExpiresAt;
286
- return {...tokens, idTokenPayload: _idTokenPayload, accessTokenPayload, expiresAt};
287
- }
288
-
289
231
  const eventNames = {
290
232
  service_worker_not_supported_by_browser: "service_worker_not_supported_by_browser",
291
233
  token_aquired: "token_aquired",
292
234
  logout_from_another_tab: "logout_from_another_tab",
235
+ logout_from_same_tab: "logout_from_same_tab",
293
236
  token_renewed: "token_renewed",
294
237
  token_timer: "token_timer",
295
238
  loginAsync_begin:"loginAsync_begin",
@@ -382,7 +325,12 @@ export class Oidc {
382
325
  silent_login_uri = `${configuration.silent_redirect_uri.replace("-callback", "").replace("callback", "")}-login`;
383
326
  }
384
327
 
385
- this.configuration = {...configuration, silent_login_uri};
328
+ this.configuration = {...configuration,
329
+ silent_login_uri,
330
+ monitor_session: configuration.monitor_session ?? true,
331
+ refresh_time_before_tokens_expiration_in_second : configuration.refresh_time_before_tokens_expiration_in_second ?? 60,
332
+ silent_login_timeout: configuration.silent_login_timeout ?? 12000,
333
+ };
386
334
  this.configurationName= configurationName;
387
335
  this.tokens = null
388
336
  this.userInfo = null;
@@ -390,7 +338,7 @@ export class Oidc {
390
338
  this.timeoutId = null;
391
339
  this.serviceWorker = null;
392
340
  this.session = null;
393
- this.refreshTokensAsync.bind(this);
341
+ this.synchroniseTokensAsync.bind(this);
394
342
  this.loginCallbackWithAutoTokensRenewAsync.bind(this);
395
343
  this.initAsync.bind(this);
396
344
  this.loginCallbackAsync.bind(this);
@@ -460,17 +408,6 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
460
408
  if (!this.configuration.silent_redirect_uri || !this.configuration.silent_login_uri) {
461
409
  return Promise.resolve(null);
462
410
  }
463
- while (document.hidden) {
464
- await sleepAsync(1000);
465
- this.publishEvent(eventNames.silentLoginAsync, {message:"wait because document is hidden"});
466
- }
467
-
468
- let numberTryOnline = 6;
469
- while (!navigator.onLine && numberTryOnline > 0) {
470
- await sleepAsync(1000);
471
- numberTryOnline--;
472
- this.publishEvent(eventNames.refreshTokensAsync, {message: `wait because navigator is offline try ${numberTryOnline}` });
473
- }
474
411
 
475
412
  try {
476
413
  this.publishEvent(eventNames.silentLoginAsync_begin, {});
@@ -541,7 +478,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
541
478
  }
542
479
  }
543
480
  };
544
- const silentSigninTimeout = configuration.silent_login_timeout ?? 12000
481
+ const silentSigninTimeout = configuration.silent_login_timeout;
545
482
  setTimeout(() => {
546
483
  if (!isResolved) {
547
484
  self.publishEvent(eventNames.silentLoginAsync_error, {reason: "timeout"});
@@ -575,8 +512,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
575
512
 
576
513
  const serviceWorker = await initWorkerAsync(this.configuration.service_worker_relative_url, this.configurationName);
577
514
  const storage = serviceWorker ? window.localStorage : null;
578
- const initAsyncPromise = await fetchFromIssuer(authority, this.configuration.authority_time_cache_wellknowurl_in_second ?? 60 * 60, storage);
579
- return initAsyncPromise;
515
+ return await fetchFromIssuer(authority, this.configuration.authority_time_cache_wellknowurl_in_second ?? 60 * 60, storage);
580
516
  }
581
517
 
582
518
  tryKeepExistingSessionPromise = null;
@@ -600,16 +536,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
600
536
  if (tokens) {
601
537
  serviceWorker.startKeepAliveServiceWorker();
602
538
  // @ts-ignore
603
- const reformattedToken = {
604
- accessToken : tokens.access_token,
605
- refreshToken : tokens.refresh_token,
606
- expiresIn: tokens.expires_in,
607
- idToken: tokens.id_token,
608
- scope: tokens.scope,
609
- tokenType: tokens.token_type,
610
- issuedAt: tokens.issued_at
611
- }
612
- this.tokens = await setTokensAsync(serviceWorker, reformattedToken);
539
+ this.tokens = tokens;
613
540
  this.serviceWorker = serviceWorker;
614
541
  // @ts-ignore
615
542
  this.timeoutId = autoRenewTokens(this, this.tokens.refreshToken, this.tokens.expiresAt);
@@ -636,7 +563,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
636
563
  const {tokens} = await session.initAsync();
637
564
  if (tokens) {
638
565
  // @ts-ignore
639
- this.tokens = await setTokensAsync(serviceWorker, tokens);
566
+ this.tokens = setTokens(tokens);
640
567
  //session.setTokens(this.tokens);
641
568
  this.session = session;
642
569
  // @ts-ignore
@@ -674,7 +601,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
674
601
  }
675
602
 
676
603
  loginPromise: Promise<any>=null;
677
- async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true, state:string=undefined, isSilentSignin:boolean=false, scope:string=undefined) {
604
+ async loginAsync(callbackPath:string=undefined, extras:StringMap=null, state:string=undefined, isSilentSignin:boolean=false, scope:string=undefined) {
678
605
  if(this.loginPromise !== null){
679
606
  return this.loginPromise;
680
607
  }
@@ -691,28 +618,10 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
691
618
  scope = configuration.scope;
692
619
  }
693
620
 
694
- const sessionKey = getLoginSessionKey(this.configurationName, redirectUri);
695
- sessionStorage[sessionKey] = JSON.stringify({callbackPath: url, extras, state});
696
-
621
+ setLoginParams(this.configurationName, redirectUri, {callbackPath: url, extras, state});
622
+
697
623
  let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
698
624
  const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
699
- /*if (serviceWorker && installServiceWorker) {
700
- const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync();
701
- if (!isServiceWorkerProxyActive) {
702
- const isUnregistered = await serviceWorker.unregisterAsync();
703
- console.log("isUnregistered")
704
- console.log(isUnregistered)
705
- if(isUnregistered){
706
- serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
707
- }
708
- const extrasQueries = extras != null ? {...extras}: {};
709
- extrasQueries.callbackPath = url;
710
- extrasQueries.state = state;
711
- const queryString = buildQueries(extrasQueries);
712
- window.location.href = `${redirectUri}/service-worker-install${queryString}`;
713
- //return;
714
- }
715
- }*/
716
625
  let storage;
717
626
  if (serviceWorker) {
718
627
  serviceWorker.startKeepAliveServiceWorker();
@@ -781,9 +690,16 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
781
690
  else {
782
691
  console.debug("SessionMonitor._callback: Different subject signed into OP:", iFrameIdTokenPayload.sub);
783
692
  }
784
- }).catch((e) => {
785
- this.publishEvent(eventNames.logout_from_another_tab, {});
786
- this.destroyAsync();
693
+ }).catch(async (e) => {
694
+ for (const [key, oidc] of Object.entries(oidcDatabase)) {
695
+ //if(oidc !== this) {
696
+ // @ts-ignore
697
+ await oidc.logoutOtherTabAsync(this.configuration.client_id, idTokenPayload.sub);
698
+ //}
699
+ }
700
+ //await this.destroyAsync();
701
+ //this.publishEvent(eventNames.logout_from_another_tab, {message : "SessionMonitor"});
702
+
787
703
  });
788
704
  };
789
705
 
@@ -810,7 +726,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
810
726
  const response = await this._loginCallbackAsync(isSilenSignin);
811
727
  // @ts-ignore
812
728
  const tokens = response.tokens;
813
- const parsedTokens = await setTokensAsync(this.serviceWorker, tokens);
729
+ const parsedTokens = setTokens(tokens);
814
730
  this.tokens = parsedTokens;
815
731
  if(!this.serviceWorker){
816
732
  await this.session.setTokens(parsedTokens);
@@ -910,13 +826,19 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
910
826
  }, tokenRequestTimeout ?? 12000);
911
827
  try {
912
828
  const tokenHandler = new BaseTokenRequestHandler(new FetchRequestor());
913
- tokenHandler.performTokenRequest(oidcServerConfiguration, tokenRequest).then((tokenResponse)=>{
914
- if(timeoutId) {
829
+ tokenHandler.performTokenRequest(oidcServerConfiguration, tokenRequest).then(async (tokenResponse) => {
830
+ if (timeoutId) {
915
831
  clearTimeout(timeoutId);
916
- this.timeoutId=null;
832
+ this.timeoutId = null;
917
833
  const loginParams = getLoginParams(this.configurationName, redirectUri);
834
+
835
+ if (serviceWorker) {
836
+ const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
837
+ tokenResponse = tokens;
838
+ }
839
+
918
840
  // @ts-ignore
919
- this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, clientId, sessionState, isSilentSignin).then(() =>{
841
+ this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, clientId, sessionState, isSilentSignin).then(() => {
920
842
  this.publishEvent(eventNames.loginCallbackAsync_end, {});
921
843
  resolve({
922
844
  tokens: tokenResponse,
@@ -945,157 +867,154 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
945
867
  }
946
868
  }
947
869
 
948
- async refreshTokensAsync(refreshToken) {
870
+ async synchroniseTokensAsync(refreshToken, index=0) {
949
871
 
872
+ if (document.hidden) {
873
+ await sleepAsync(1000);
874
+ this.publishEvent(eventNames.refreshTokensAsync, {message: "wait because document is hidden"});
875
+ return await this.synchroniseTokensAsync(refreshToken, index);
876
+ }
877
+ let numberTryOnline = 6;
878
+ while (!navigator.onLine && numberTryOnline > 0) {
879
+ await sleepAsync(1000);
880
+ numberTryOnline--;
881
+ this.publishEvent(eventNames.refreshTokensAsync, {message: `wait because navigator is offline try ${numberTryOnline}` });
882
+ }
883
+
884
+ const configuration = this.configuration;
950
885
  const localsilentLoginAsync= async () => {
951
886
  try {
952
- const silent_token_response = await this.silentLoginAsync();
887
+ const loginParams = getLoginParams(this.configurationName, configuration.redirect_uri);
888
+ const silent_token_response = await this.silentLoginAsync({
889
+ ...loginParams.extras,
890
+ prompt: "none"
891
+ }, loginParams.state);
953
892
  if (silent_token_response) {
954
- return silent_token_response.tokens;
893
+ this.publishEvent(Oidc.eventNames.token_renewed, {});
894
+ return {tokens:silent_token_response.tokens, status:"LOGGED"};
955
895
  }
956
896
  } catch (exceptionSilent) {
957
897
  console.error(exceptionSilent);
898
+ this.publishEvent(eventNames.refreshTokensAsync_silent_error, {message: "exceptionSilent" ,exception: exceptionSilent.message});
899
+ if(exceptionSilent && exceptionSilent.message && exceptionSilent.message.startsWith("oidc")){
900
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token silent` });
901
+ return {tokens:null, status:"SESSION_LOST"};
902
+ }
903
+ await sleepAsync(1000);
904
+ throw exceptionSilent;
958
905
  }
959
- if(this.timeoutId){
960
- timer.clearTimeout(this.timeoutId);
961
- this.timeoutId=null;
962
- }
963
- this.publishEvent(eventNames.refreshTokensAsync_error, {message: "refresh token and silent refresh failed"});
964
- return null;
906
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token silent return` });
907
+ return {tokens:null, status:"SESSION_LOST"};
965
908
  }
966
-
967
- const configuration = this.configuration;
968
- const clientId = configuration.client_id;
969
- const redirectUri = configuration.redirect_uri;
970
- const authority = configuration.authority;
971
-
972
- if(!refreshToken)
973
- {
974
- return await localsilentLoginAsync();
975
- }
976
909
 
977
- let extras = {};
978
- if(configuration.token_request_extras) {
979
- for (let [key, value] of Object.entries(configuration.token_request_extras)) {
980
- extras[key] = value;
981
- }
982
- }
983
- const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
984
-
985
- const details = {
986
- client_id: clientId,
987
- redirect_uri: redirectUri,
988
- grant_type: GRANT_TYPE_REFRESH_TOKEN,
989
- refresh_token: refreshToken,
990
- };
991
-
992
- let index = 0;
993
- while (index <=4) {
910
+ if (index <=4) {
994
911
  try {
995
- this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken, tryNumber: index});
996
- if(index > 1) {
997
- while (document.hidden) {
998
- await sleepAsync(1000);
999
- this.publishEvent(eventNames.refreshTokensAsync, {message: "wait because document is hidden"});
1000
- }
1001
- let numberTryOnline = 6;
1002
- while (!navigator.onLine && numberTryOnline > 0) {
1003
- await sleepAsync(1000);
1004
- numberTryOnline--;
1005
- this.publishEvent(eventNames.refreshTokensAsync, {message: `wait because navigator is offline try ${numberTryOnline}` });
1006
- }
1007
- }
1008
- const tokenResponse = await performTokenRequestAsync(oidcServerConfiguration.tokenEndpoint, details, extras)
1009
- if (tokenResponse.success) {
1010
- this.publishEvent(eventNames.refreshTokensAsync_end, {success: tokenResponse.success});
1011
- return tokenResponse.data;
1012
- } else {
1013
- this.publishEvent(eventNames.refreshTokensAsync_silent_error, {message: "bad request" , tokenResponse: tokenResponse});
912
+
913
+ if(!refreshToken)
914
+ {
915
+ this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken, tryNumber: index});
1014
916
  return await localsilentLoginAsync();
1015
917
  }
918
+ const { status, tokens } = await this.syncTokensInfoAsync(configuration, this.configurationName, this.tokens);
919
+ switch (status) {
920
+ case "SESSION_LOST":
921
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token session lost` });
922
+ return {tokens:null, status:"SESSION_LOST"};
923
+ case "NOT_CONNECTED":
924
+ return {tokens:null, status:null};
925
+ case "TOKENS_VALID":
926
+ case "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID":
927
+ return {tokens, status:"LOGGED_IN"};
928
+ case "LOGOUT_FROM_ANOTHER_TAB":
929
+ this.publishEvent(eventNames.logout_from_another_tab, {"status": "session syncTokensAsync"});
930
+ return {tokens:null, status:"LOGGED_OUT"};
931
+ case "REQUIRE_SYNC_TOKENS":
932
+ this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken, status, tryNumber: index});
933
+ return await localsilentLoginAsync();
934
+ default:
935
+ this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken, status, tryNumber: index});
936
+ const clientId = configuration.client_id;
937
+ const redirectUri = configuration.redirect_uri;
938
+ const authority = configuration.authority;
939
+ let extras = {};
940
+ if(configuration.token_request_extras) {
941
+ for (let [key, value] of Object.entries(configuration.token_request_extras)) {
942
+ extras[key] = value;
943
+ }
944
+ }
945
+ const details = {
946
+ client_id: clientId,
947
+ redirect_uri: redirectUri,
948
+ grant_type: GRANT_TYPE_REFRESH_TOKEN,
949
+ refresh_token: tokens.refreshToken,
950
+ };
951
+ const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
952
+ const tokenResponse = await performTokenRequestAsync(oidcServerConfiguration.tokenEndpoint, details, extras)
953
+ if (tokenResponse.success) {
954
+ this.publishEvent(eventNames.refreshTokensAsync_end, {success: tokenResponse.success});
955
+ this.publishEvent(Oidc.eventNames.token_renewed, {});
956
+ return {tokens: tokenResponse.data, status:"LOGGED_IN"};
957
+ } else {
958
+ this.publishEvent(eventNames.refreshTokensAsync_silent_error, {
959
+ message: "bad request",
960
+ tokenResponse: tokenResponse
961
+ });
962
+ return await this.synchroniseTokensAsync(null, index+1);
963
+ }
964
+ }
1016
965
  } catch (exception) {
1017
966
  console.error(exception);
1018
967
  this.publishEvent(eventNames.refreshTokensAsync_silent_error, {message: "exception" ,exception: exception.message});
968
+ return this.synchroniseTokensAsync(refreshToken, index+1);
1019
969
  }
1020
- index++;
1021
970
  }
1022
-
1023
- }
1024
971
 
1025
- syncTokensAsyncPromise=null;
1026
- async syncTokensAsync() {
1027
-
1028
- const localSyncTokensAsync = async () => {
1029
- // Service Worker can be killed by the browser (when it wants,for example after 10 seconds of inactivity, so we retreieve the session if it happen)
1030
- const configuration = this.configuration;
1031
- if (!this.tokens) {
1032
- return null;
1033
- }
972
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token` });
973
+ return {tokens:null, status:"SESSION_LOST"};
974
+ }
1034
975
 
1035
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
1036
- const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
1037
- if (serviceWorker) {
1038
- const {isLogin} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
1039
- if (isLogin == false) {
1040
- this.publishEvent(eventNames.logout_from_another_tab, {});
1041
- await this.destroyAsync();
1042
- return null;
1043
- } else if (isLogin == null) {
1044
- try {
1045
- this.publishEvent(eventNames.syncTokensAsync_begin, {});
1046
- const silent_token_response = await this.silentLoginAsync({prompt: "none"});
1047
- if (silent_token_response && silent_token_response.tokens) {
1048
- this.tokens = await setTokensAsync(serviceWorker, silent_token_response.tokens);
1049
- this.publishEvent(eventNames.syncTokensAsync_end, {});
1050
- return this.tokens;
1051
- } else {
1052
- this.publishEvent(eventNames.syncTokensAsync_error, {message: "no token found in result"});
1053
- if (this.timeoutId) {
1054
- timer.clearTimeout(this.timeoutId);
1055
- this.timeoutId = null;
1056
- }
1057
- this.publishEvent(eventNames.syncTokensAsync_end, {});
1058
- return null;
1059
- }
1060
- } catch (exceptionSilent) {
1061
- console.error(exceptionSilent);
1062
- this.publishEvent(eventNames.syncTokensAsync_error, exceptionSilent);
1063
- if (this.timeoutId) {
1064
- timer.clearTimeout(this.timeoutId);
1065
- this.timeoutId = null;
1066
- }
1067
- this.publishEvent(eventNames.syncTokensAsync_end, {});
1068
- return null;
1069
- }
976
+ async syncTokensInfoAsync(configuration, configurationName, currentTokens) {
977
+ // Service Worker can be killed by the browser (when it wants,for example after 10 seconds of inactivity, so we retreieve the session if it happen)
978
+ //const configuration = this.configuration;
979
+ if (!currentTokens) {
980
+ return { tokens : null, status: "NOT_CONNECTED"};
981
+ }
1070
982
 
983
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
984
+ const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, configurationName);
985
+ if (serviceWorker) {
986
+ const {status, tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
987
+ if (status == "LOGGED_OUT") {
988
+ return {tokens: null, status: "LOGOUT_FROM_ANOTHER_TAB"};
989
+ }else if (status == "SESSIONS_LOST") {
990
+ return { tokens : null, status: "SESSIONS_LOST"};
991
+ } else if (!status || !tokens) {
992
+ return { tokens : null, status: "REQUIRE_SYNC_TOKENS"};
993
+ } else if(tokens.issuedAt !== currentTokens.issuedAt) {
994
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
995
+ const status = (timeLeft > 0) ? "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID" : "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID";
996
+ return { tokens : tokens, status };
997
+ }
998
+ } else {
999
+ const session = initSession(configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
1000
+ const {tokens, status } = await session.initAsync();
1001
+ if (!tokens) {
1002
+ return {tokens: null, status: "LOGOUT_FROM_ANOTHER_TAB"};
1003
+ } else if (status == "SESSIONS_LOST") {
1004
+ return { tokens : null, status: "SESSIONS_LOST"};
1071
1005
  }
1072
- } else {
1073
- const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
1074
- const {tokens} = await session.initAsync();
1075
- if (!tokens) {
1076
- this.publishEvent(eventNames.logout_from_another_tab, {});
1077
- await this.destroyAsync();
1078
- return null;
1079
- }
1006
+ else if(tokens.issuedAt !== currentTokens.issuedAt){
1007
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
1008
+ const status = (timeLeft > 0) ? "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID" : "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID";
1009
+ return { tokens : tokens, status };
1080
1010
  }
1081
- return this.tokens;
1082
- }
1083
-
1084
- if(this.syncTokensAsyncPromise){
1085
- return this.syncTokensAsyncPromise;
1086
1011
  }
1087
1012
 
1088
- this.syncTokensAsyncPromise = localSyncTokensAsync().then(result =>{
1089
- if(this.syncTokensAsyncPromise){
1090
- this.syncTokensAsyncPromise = null;
1091
- }
1092
- return result;
1093
- });
1094
-
1095
- return this.syncTokensAsyncPromise
1013
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, currentTokens.expiresAt);
1014
+ const status = (timeLeft > 0) ? "TOKENS_VALID" : "TOKENS_INVALID";
1015
+ return { tokens:currentTokens, status};
1096
1016
  }
1097
1017
 
1098
-
1099
1018
  loginCallbackWithAutoTokensRenewPromise:Promise<loginCallbackResult> = null;
1100
1019
  loginCallbackWithAutoTokensRenewAsync():Promise<loginCallbackResult>{
1101
1020
  if(this.loginCallbackWithAutoTokensRenewPromise !== null){
@@ -1112,24 +1031,39 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
1112
1031
  return userInfoAsync(this);
1113
1032
  }
1114
1033
 
1115
- async destroyAsync() {
1034
+ async destroyAsync(status) {
1116
1035
  timer.clearTimeout(this.timeoutId);
1117
1036
  this.timeoutId=null;
1118
1037
  if(this.checkSessionIFrame){
1119
1038
  this.checkSessionIFrame.stop();
1120
1039
  }
1121
1040
  if(this.serviceWorker){
1122
- await this.serviceWorker.clearAsync();
1041
+ await this.serviceWorker.clearAsync(status);
1123
1042
  }
1124
1043
  if(this.session){
1125
- await this.session.clearAsync();
1044
+ await this.session.clearAsync(status);
1126
1045
  }
1127
1046
  this.tokens = null;
1128
1047
  this.userInfo = null;
1129
- this.events = [];
1130
-
1048
+ // this.events = [];
1131
1049
  }
1132
1050
 
1051
+ async logoutSameTabAsync(clientId, sub){
1052
+ // @ts-ignore
1053
+ if(this.configuration.monitor_session&& this.configuration.client_id === clientId && sub && this.tokens && this.tokens.idTokenPayload && this.tokens.idTokenPayload.sub === sub) {
1054
+ this.publishEvent(eventNames.logout_from_same_tab, {"message": sub});
1055
+ await this.destroyAsync("LOGGED_OUT");
1056
+ }
1057
+ }
1058
+
1059
+ async logoutOtherTabAsync(clientId, sub){
1060
+ // @ts-ignore
1061
+ if(this.configuration.monitor_session && this.configuration.client_id === clientId && sub && this.tokens && this.tokens.idTokenPayload && this.tokens.idTokenPayload.sub === sub) {
1062
+ await this.destroyAsync("LOGGED_OUT");
1063
+ this.publishEvent(eventNames.logout_from_another_tab, {message : "SessionMonitor", "sub": sub});
1064
+ }
1065
+ }
1066
+
1133
1067
  async logoutAsync(callbackPathOrUrl: string | undefined = undefined, extras: StringMap = null) {
1134
1068
  const configuration = this.configuration;
1135
1069
  const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
@@ -1146,7 +1080,16 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
1146
1080
  const url = isUri ? callbackPathOrUrl : window.location.origin + path;
1147
1081
  // @ts-ignore
1148
1082
  const idToken = this.tokens ? this.tokens.idToken : "";
1149
- await this.destroyAsync();
1083
+ // @ts-ignore
1084
+ const sub = this.tokens && this.tokens.idTokenPayload ? this.tokens.idTokenPayload.sub : null;
1085
+ await this.destroyAsync("LOGGED_OUT");
1086
+ for (const [key, oidc] of Object.entries(oidcDatabase)) {
1087
+ if(oidc !== this) {
1088
+ // @ts-ignore
1089
+ await oidc.logoutSameTabAsync(this.configuration.client_id, sub);
1090
+ }
1091
+ }
1092
+
1150
1093
  if(oidcServerConfiguration.endSessionEndpoint) {
1151
1094
  let extraQueryString = "";
1152
1095
  if(extras){