@axa-fr/react-oidc 5.8.0-alpha0 → 5.9.0

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 +33 -2
  2. package/dist/OidcProvider.d.ts +2 -0
  3. package/dist/OidcProvider.d.ts.map +1 -1
  4. package/dist/OidcProvider.js +5 -5
  5. package/dist/OidcProvider.js.map +1 -1
  6. package/dist/OidcSecure.d.ts +2 -1
  7. package/dist/OidcSecure.d.ts.map +1 -1
  8. package/dist/OidcSecure.js +5 -4
  9. package/dist/OidcSecure.js.map +1 -1
  10. package/dist/ReactOidc.d.ts +1 -2
  11. package/dist/ReactOidc.d.ts.map +1 -1
  12. package/dist/ReactOidc.js +4 -7
  13. package/dist/ReactOidc.js.map +1 -1
  14. package/dist/core/default-component/Callback.component.d.ts.map +1 -1
  15. package/dist/core/default-component/Callback.component.js +9 -11
  16. package/dist/core/default-component/Callback.component.js.map +1 -1
  17. package/dist/core/default-component/ServiceWorkerInstall.component.d.ts.map +1 -1
  18. package/dist/core/default-component/ServiceWorkerInstall.component.js +4 -7
  19. package/dist/core/default-component/ServiceWorkerInstall.component.js.map +1 -1
  20. package/dist/core/default-component/SilentCallback.component.d.ts.map +1 -1
  21. package/dist/core/default-component/SilentCallback.component.js +3 -5
  22. package/dist/core/default-component/SilentCallback.component.js.map +1 -1
  23. package/dist/core/routes/OidcRoutes.d.ts +2 -0
  24. package/dist/core/routes/OidcRoutes.d.ts.map +1 -1
  25. package/dist/core/routes/OidcRoutes.js +7 -5
  26. package/dist/core/routes/OidcRoutes.js.map +1 -1
  27. package/dist/core/routes/withRouter.d.ts +3 -2
  28. package/dist/core/routes/withRouter.d.ts.map +1 -1
  29. package/dist/core/routes/withRouter.js.map +1 -1
  30. package/dist/vanilla/initSession.js +9 -9
  31. package/dist/vanilla/initSession.js.map +1 -1
  32. package/dist/vanilla/oidc.d.ts +11 -5
  33. package/dist/vanilla/oidc.d.ts.map +1 -1
  34. package/dist/vanilla/oidc.js +194 -193
  35. package/dist/vanilla/oidc.js.map +1 -1
  36. package/dist/vanilla/timer.d.ts.map +1 -1
  37. package/dist/vanilla/timer.js +14 -6
  38. package/dist/vanilla/timer.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/App.tsx +14 -10
  41. package/src/Home.tsx +5 -3
  42. package/src/MultiAuth.tsx +1 -2
  43. package/src/index.tsx +1 -1
  44. package/src/oidc/OidcProvider.tsx +7 -2
  45. package/src/oidc/OidcSecure.tsx +9 -5
  46. package/src/oidc/ReactOidc.tsx +3 -6
  47. package/src/oidc/core/default-component/Callback.component.tsx +12 -15
  48. package/src/oidc/core/default-component/ServiceWorkerInstall.component.tsx +4 -7
  49. package/src/oidc/core/default-component/SilentCallback.component.tsx +3 -2
  50. package/src/oidc/core/routes/OidcRoutes.tsx +9 -4
  51. package/src/oidc/core/routes/withRouter.tsx +5 -1
  52. package/src/oidc/vanilla/initSession.ts +9 -9
  53. package/src/oidc/vanilla/oidc.ts +132 -109
  54. package/src/oidc/vanilla/timer.ts +15 -7
@@ -45,7 +45,7 @@ const extractAccessTokenPayload = tokens => {
45
45
  }
46
46
  const accessToken = tokens.accessToken;
47
47
  try{
48
- if (!accessToken || countLetter(accessToken,'.') === 2) {
48
+ if (!accessToken || countLetter(accessToken,'.') != 2) {
49
49
  return null;
50
50
  }
51
51
  return JSON.parse(atob(accessToken.split('.')[1]));
@@ -59,6 +59,11 @@ export interface StringMap {
59
59
  [key: string]: string;
60
60
  }
61
61
 
62
+ export interface loginCallbackResult {
63
+ state: string,
64
+ callbackPath: string,
65
+ }
66
+
62
67
  export interface AuthorityConfiguration {
63
68
  authorization_endpoint: string;
64
69
  token_endpoint: string;
@@ -101,39 +106,36 @@ const loginCallbackWithAutoTokensRenewAsync = async (oidc) => {
101
106
  }
102
107
  oidc.publishEvent(Oidc.eventNames.token_aquired, oidc.tokens);
103
108
  oidc.timeoutId = await autoRenewTokensAsync(oidc, tokens.refreshToken, oidc.tokens.expiresAt)
104
- return response.state;
105
- }
106
-
107
- const renewTokenAsync = async (oidc, refreshToken, extras:StringMap=null) =>{
108
- const tokens = await oidc.refreshTokensAsync(refreshToken, false, extras);
109
- oidc.tokens= await setTokensAsync(oidc.serviceWorker, tokens);
110
- if(!oidc.serviceWorker){
111
- await oidc.session.setTokens(oidc.tokens);
112
- }
113
- if(!oidc.tokens){
114
- return null;
115
- }
116
- oidc.publishEvent(Oidc.eventNames.token_renewed, oidc.tokens);
117
- return oidc.tokens;
109
+ return { state:response.state, callbackPath : response.callbackPath };
118
110
  }
119
111
 
120
112
  const autoRenewTokensAsync = async (oidc, refreshToken, expiresAt) => {
121
113
  const refreshTimeBeforeTokensExpirationInSecond = oidc.configuration.refresh_time_before_tokens_expiration_in_second ?? 60;
122
- return timer.setTimeout(async () => {
114
+ return timer.setTimeout(async () => {
123
115
  const currentTimeUnixSecond = new Date().getTime() /1000;
124
116
  const timeInfo = { timeLeft:((expiresAt - refreshTimeBeforeTokensExpirationInSecond)- currentTimeUnixSecond)};
125
117
  oidc.publishEvent(Oidc.eventNames.token_timer, timeInfo);
126
118
  if(currentTimeUnixSecond > (expiresAt - refreshTimeBeforeTokensExpirationInSecond)) {
127
- const tokens = await renewTokenAsync(oidc, refreshToken);
128
- if(tokens) {
129
- oidc.timeoutId = await autoRenewTokensAsync(oidc, tokens.refreshToken, oidc.tokens.expiresAt);
119
+ const tokens = await oidc.refreshTokensAsync(refreshToken);
120
+ oidc.tokens= await setTokensAsync(oidc.serviceWorker, tokens);
121
+ if(!oidc.serviceWorker){
122
+ await oidc.session.setTokens(oidc.tokens);
130
123
  }
124
+ if(!oidc.tokens){
125
+ return;
126
+ }
127
+ oidc.publishEvent(Oidc.eventNames.token_renewed, oidc.tokens);
128
+ oidc.timeoutId = await autoRenewTokensAsync(oidc, tokens.refreshToken, oidc.tokens.expiresAt);
131
129
  } else{
132
130
  oidc.timeoutId = await autoRenewTokensAsync(oidc, refreshToken, expiresAt)
133
131
  }
134
132
  }, 1000);
135
133
  }
136
134
 
135
+ export const getLoginParams = (configurationName) => {
136
+ return JSON.parse(sessionStorage[`oidc_login.${configurationName}`]);
137
+ }
138
+
137
139
  const userInfoAsync = async (oidc) => {
138
140
  if(oidc.userInfo != null){
139
141
  return oidc.userInfo;
@@ -244,8 +246,8 @@ export class Oidc {
244
246
  }
245
247
 
246
248
  removeEventSubscription(id){
247
- const newEvents = this.events.filter(e => e.id === id);
248
- this.events = newEvents;
249
+ const newEvents = this.events.filter(e => e.id !== id);
250
+ this.events = newEvents;
249
251
  }
250
252
 
251
253
  publishEvent(eventName, data){
@@ -257,7 +259,8 @@ export class Oidc {
257
259
  return oidcFactory(configuration, name);
258
260
  }
259
261
  static get(name="default") {
260
- if(!oidcDatabase.hasOwnProperty(name)){
262
+ const insideBrowser = (typeof process === 'undefined');
263
+ if(!oidcDatabase.hasOwnProperty(name) && insideBrowser){
261
264
  throw Error(`Oidc library does seem initialized.
262
265
  Please checkout that you are using OIDC hook inside a <OidcProvider configurationName="${name}"></OidcProvider> compoment.`)
263
266
  }
@@ -327,79 +330,105 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
327
330
  }
328
331
  return await AuthorizationServiceConfiguration.fetchFromIssuer(authority, new FetchRequestor());
329
332
  }
330
-
333
+
334
+ tryKeepExistingSessionPromise = null;
331
335
  async tryKeepExistingSessionAsync() {
332
- let serviceWorker
333
- if(this.tokens != null){
334
- return false;
336
+ if(this.tryKeepExistingSessionPromise !== null){
337
+ return this.tryKeepExistingSessionPromise;
335
338
  }
336
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_begin, {});
337
- try {
338
- const configuration = this.configuration;
339
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
340
- serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
341
- if(serviceWorker) {
342
- const { tokens } = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync");
343
- if (tokens) {
344
- serviceWorker.startKeepAliveServiceWorker();
345
- const updatedTokens = await this.refreshTokensAsync(tokens.refresh_token, true);
346
- // @ts-ignore
347
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
348
- this.serviceWorker = serviceWorker;
349
- // @ts-ignore
350
- this.timeoutId = await autoRenewTokensAsync(this, updatedTokens.refreshToken, this.tokens.expiresAt);
351
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {success: true, message : "tokens inside ServiceWorker are valid"});
352
- return true;
353
- }
354
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {success: false, message : "no exiting session found"});
355
- } else {
356
- if(configuration.service_worker_relative_url) {
357
- this.publishEvent(eventNames.service_worker_not_supported_by_browser, {
358
- message: "service worker is not supported by this browser"
339
+
340
+ const funcAsync =async () => {
341
+ let serviceWorker
342
+ if (this.tokens != null) {
343
+ return false;
344
+ }
345
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_begin, {});
346
+ try {
347
+ const configuration = this.configuration;
348
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
349
+ serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
350
+ if (serviceWorker) {
351
+ const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync");
352
+ if (tokens) {
353
+ serviceWorker.startKeepAliveServiceWorker();
354
+ const updatedTokens = await this.refreshTokensAsync(tokens.refresh_token, true);
355
+ // @ts-ignore
356
+ this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
357
+ this.serviceWorker = serviceWorker;
358
+ // @ts-ignore
359
+ this.timeoutId = await autoRenewTokensAsync(this, updatedTokens.refreshToken, this.tokens.expiresAt);
360
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
361
+ success: true,
362
+ message: "tokens inside ServiceWorker are valid"
363
+ });
364
+ return true;
365
+ }
366
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
367
+ success: false,
368
+ message: "no exiting session found"
359
369
  });
370
+ } else {
371
+ if (configuration.service_worker_relative_url) {
372
+ this.publishEvent(eventNames.service_worker_not_supported_by_browser, {
373
+ message: "service worker is not supported by this browser"
374
+ });
375
+ }
376
+ const session = initSession(this.configurationName);
377
+ const {tokens} = await session.initAsync();
378
+ if (tokens) {
379
+ const updatedTokens = await this.refreshTokensAsync(tokens.refreshToken, true);
380
+ // @ts-ignore
381
+ this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
382
+ session.setTokens(this.tokens);
383
+ this.session = session;
384
+ // @ts-ignore
385
+ this.timeoutId = await autoRenewTokensAsync(this, updatedTokens.refreshToken, this.tokens.expiresAt);
386
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
387
+ success: true,
388
+ message: "tokens inside ServiceWorker are valid"
389
+ });
390
+ return true;
391
+ }
360
392
  }
361
- const session = initSession(this.configurationName);
362
- const {tokens} = await session.initAsync();
363
- if (tokens) {
364
- const updatedTokens = await this.refreshTokensAsync(tokens.refreshToken, true);
365
- // @ts-ignore
366
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
367
- session.setTokens(this.tokens);
368
- this.session = session;
369
- // @ts-ignore
370
- this.timeoutId = await autoRenewTokensAsync(this, updatedTokens.refreshToken, this.tokens.expiresAt);
371
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {success: true, message : "tokens inside ServiceWorker are valid"});
372
- return true;
393
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
394
+ success: false,
395
+ message: "no service worker"
396
+ });
397
+ return false;
398
+ } catch (exception) {
399
+ if (serviceWorker) {
400
+ await serviceWorker.clearAsync();
373
401
  }
402
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_error, "tokens inside ServiceWorker are invalid");
403
+ return false;
374
404
  }
375
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {success: false, message : "no service worker"});
376
- return false;
377
- } catch (exception) {
378
- if(serviceWorker){
379
- await serviceWorker.clearAsync();
380
- }
381
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_error, "tokens inside ServiceWorker are invalid");
382
- return false;
383
405
  }
406
+
407
+ this.tryKeepExistingSessionPromise = funcAsync();
408
+ return this.tryKeepExistingSessionPromise.then((result) => {
409
+ this.tryKeepExistingSessionPromise =null;
410
+ return result;
411
+ });
384
412
  }
385
413
 
386
- async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true) {
414
+ async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true, state:string=undefined) {
387
415
  try {
388
416
  const location = window.location;
389
417
  const url = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
390
- const state = url;
391
418
  this.publishEvent(eventNames.loginAsync_begin, {});
392
419
  const configuration = this.configuration
393
420
  // Security we cannot loggin from Iframe
394
421
  if (!configuration.silent_redirect_uri && isInIframe()) {
395
422
  throw new Error("Login from iframe is forbidden");
396
423
  }
424
+ sessionStorage[`oidc_login.${this.configurationName}`] = JSON.stringify({callbackPath:url,extras,state});
425
+
397
426
  let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
398
427
  const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
399
428
  if(serviceWorker && installServiceWorker) {
400
- const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync()
429
+ const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync();
401
430
  if(!isServiceWorkerProxyActive) {
402
- window.location.href = configuration.redirect_uri + "/service-worker-install?callbackPath=" + encodeURIComponent(url);
431
+ window.location.href = `${configuration.redirect_uri}/service-worker-install`;
403
432
  return;
404
433
  }
405
434
  }
@@ -424,13 +453,13 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
424
453
  extras: extras ?? configuration.extras
425
454
  });
426
455
  authorizationHandler.performAuthorizationRequest(oidcServerConfiguration, authRequest);
427
- } catch(exception){
428
- this.publishEvent(eventNames.loginAsync_error, exception);
429
- throw exception;
456
+ } catch(exception) {
457
+ this.publishEvent(eventNames.loginAsync_error, exception);
458
+ throw exception;
430
459
  }
431
460
  }
432
461
 
433
- async loginCallbackAsync() {
462
+ async loginCallbackAsync(){
434
463
  try {
435
464
  this.publishEvent(eventNames.loginCallbackAsync_begin, {});
436
465
  const configuration = this.configuration;
@@ -452,7 +481,6 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
452
481
  const items = await session.loadItemsAsync();
453
482
  storage = new MemoryStorageBackend(session.saveItemsAsync, items);
454
483
  }
455
-
456
484
  const promise = new Promise((resolve, reject) => {
457
485
  const tokenHandler = new BaseTokenRequestHandler(new FetchRequestor());
458
486
  // @ts-ignore
@@ -465,6 +493,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
465
493
  reject(error);
466
494
  }
467
495
  if (!response) {
496
+ reject("no response");
468
497
  return;
469
498
  }
470
499
 
@@ -490,7 +519,12 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
490
519
 
491
520
  try {
492
521
  const tokenResponse = await tokenHandler.performTokenRequest(oidcServerConfiguration, tokenRequest);
493
- resolve({tokens:tokenResponse, state: request.state});
522
+ const loginParams = getLoginParams(this.configurationName);
523
+ resolve({
524
+ tokens:tokenResponse,
525
+ state: request.state,
526
+ callbackPath : loginParams.callbackPath,
527
+ });
494
528
  this.publishEvent(eventNames.loginCallbackAsync_end, {})
495
529
  } catch(exception){
496
530
  this.publishEvent(eventNames.loginCallbackAsync_error, exception);
@@ -508,20 +542,8 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
508
542
  }
509
543
 
510
544
  }
511
-
512
- async renewTokensAsync(extras:StringMap=null)
513
- {
514
- // @ts-ignore
515
- if(this.tokens && this.tokens.refreshToken && this.timeoutId) {
516
- // @ts-ignore
517
- timer.clearTimeout(this.timeoutId);
518
- // @ts-ignore
519
- const tokens = await renewTokenAsync(this, this.tokens.refreshToken, extras);
520
- await autoRenewTokensAsync(this, tokens.refreshToken, tokens.expiresAt);
521
- }
522
- }
523
545
 
524
- async refreshTokensAsync(refreshToken, silentEvent = false, extras:StringMap=null) {
546
+ async refreshTokensAsync(refreshToken, silentEvent = false) {
525
547
  const localSilentSigninAsync= async (exception=null) => {
526
548
  try {
527
549
  const silent_token_response = await this.silentSigninAsync();
@@ -549,17 +571,11 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
549
571
  }
550
572
  const tokenHandler = new BaseTokenRequestHandler(new FetchRequestor());
551
573
 
552
- let extrasRequest = undefined;
553
- if(extras){
554
- extrasRequest = {}
555
- for (let [key, value] of Object.entries(extras)) {
556
- extrasRequest[key] = value;
557
- }
558
- }
559
- else if(configuration.token_request_extras) {
560
- extrasRequest = {}
574
+ let extras = undefined;
575
+ if(configuration.token_request_extras) {
576
+ extras = {}
561
577
  for (let [key, value] of Object.entries(configuration.token_request_extras)) {
562
- extrasRequest[key] = value;
578
+ extras[key] = value;
563
579
  }
564
580
  }
565
581
 
@@ -570,7 +586,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
570
586
  grant_type: GRANT_TYPE_REFRESH_TOKEN,
571
587
  code: undefined,
572
588
  refresh_token: refreshToken,
573
- extras: extrasRequest
589
+ extras
574
590
  });
575
591
 
576
592
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
@@ -582,9 +598,17 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
582
598
  return await localSilentSigninAsync(exception);
583
599
  }
584
600
  }
585
-
586
- loginCallbackWithAutoTokensRenewAsync():Promise<string>{
587
- return loginCallbackWithAutoTokensRenewAsync(this);
601
+
602
+ loginCallbackWithAutoTokensRenewPromise:Promise<loginCallbackResult> = null;
603
+ loginCallbackWithAutoTokensRenewAsync():Promise<loginCallbackResult>{
604
+ if(this.loginCallbackWithAutoTokensRenewPromise !== null){
605
+ return this.loginCallbackWithAutoTokensRenewPromise;
606
+ }
607
+ this.loginCallbackWithAutoTokensRenewPromise = loginCallbackWithAutoTokensRenewAsync(this);
608
+ return this.loginCallbackWithAutoTokensRenewPromise.then(result =>{
609
+ this.loginCallbackWithAutoTokensRenewPromise = null;
610
+ return result;
611
+ })
588
612
  }
589
613
 
590
614
  userInfoAsync(){
@@ -607,13 +631,12 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
607
631
  async logoutAsync(callbackPath: string | undefined = undefined, extras:StringMap=null) {
608
632
  const configuration = this.configuration;
609
633
  const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
610
- // TODO implement real logout
611
634
  if(callbackPath && (typeof callbackPath !== 'string'))
612
635
  {
613
636
  callbackPath = undefined;
614
637
  console.warn('callbackPath path is not a string');
615
638
  }
616
- const path = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
639
+ const path = (callbackPath === null || callbackPath === undefined) ? location.pathname + (location.search || '') + (location.hash || '') : callbackPath;
617
640
  const url = window.location.origin +path;
618
641
  // @ts-ignore
619
642
  const idToken = this.tokens ? this.tokens.idToken : "";
@@ -634,4 +657,4 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
634
657
  }
635
658
 
636
659
 
637
- export default Oidc;
660
+ export default Oidc;
@@ -62,7 +62,7 @@
62
62
  } catch (error) {
63
63
  return null;
64
64
  }
65
-
65
+ const insideBrowser = (typeof process === 'undefined');
66
66
  try {
67
67
  if (SharedWorker) {
68
68
  worker = new SharedWorker(blobURL);
@@ -70,7 +70,9 @@
70
70
  }
71
71
  } catch (error)
72
72
  {
73
- console.log("SharedWorker not available");
73
+ if(insideBrowser) {
74
+ console.warn("SharedWorker not available");
75
+ }
74
76
  }
75
77
  try {
76
78
  if (Worker) {
@@ -79,18 +81,24 @@
79
81
  }
80
82
  } catch (error)
81
83
  {
82
- console.log("Worker not available");
84
+ if(insideBrowser) {
85
+ console.warn("Worker not available");
86
+ }
83
87
  }
84
88
 
85
89
  return null;
86
90
  }());
87
91
 
88
92
  if (!workerPort) {
93
+ // In NextJS with SSR (Server Side Rendering) during rending in Node JS, the window object is undefined,
94
+ // the global object is used instead as it is the closest approximation of a browsers window object.
95
+ const bindContext = (typeof window === 'undefined')? global: window;
96
+
89
97
  return {
90
- setTimeout: setTimeout.bind(window),
91
- clearTimeout: clearTimeout.bind(window),
92
- setInterval: setInterval.bind(window),
93
- clearInterval: clearInterval.bind(window)
98
+ setTimeout: setTimeout.bind(bindContext),
99
+ clearTimeout: clearTimeout.bind(bindContext),
100
+ setInterval: setInterval.bind(bindContext),
101
+ clearInterval: clearInterval.bind(bindContext)
94
102
  };
95
103
  }
96
104