@schibsted/account-sdk-browser 5.0.1-beta.2 → 5.0.1-beta.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schibsted/account-sdk-browser",
3
- "version": "5.0.1-beta.2",
3
+ "version": "5.0.1-beta.4",
4
4
  "description": "Schibsted account SDK for browsers",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/identity.d.ts CHANGED
@@ -45,9 +45,10 @@ export class Identity extends TinyEmitter {
45
45
  */
46
46
  private _getTabId;
47
47
  /**
48
- * Checks if calling get session is blocked
48
+ * Checks if calling GET session is blocked by a cache storage
49
49
  * @private
50
- * @returns {boolean|void}
50
+ * @param {Cache} cache - cache to check
51
+ * @returns {string|null}
51
52
  */
52
53
  private _isSessionCallBlocked;
53
54
  /**
@@ -61,7 +62,7 @@ export class Identity extends TinyEmitter {
61
62
  * @private
62
63
  * @returns {void}
63
64
  */
64
- private _unblockSessionCall;
65
+ private _unblockSessionCallByTab;
65
66
  /**
66
67
  * Set SPiD server URL
67
68
  * @private
package/src/identity.js CHANGED
@@ -151,6 +151,9 @@ const SESSION_CALL_BLOCKED_TTL = 1000 * 30; //set to 30s, the default period for
151
151
  const TAB_ID_KEY = 'tab-id-cache';
152
152
  const TAB_ID = Math.floor(Math.random() * 100000)
153
153
  const TAB_ID_TTL = 1000 * 60 * 60 * 24 * 30;
154
+ const MAX_SESSION_CALL_RETRIES = 10;
155
+ const MIN_SESSION_CALL_WAIT_TIME = 100;
156
+
154
157
 
155
158
  const globalWindow = () => window;
156
159
 
@@ -177,7 +180,7 @@ export class Identity extends EventEmitter {
177
180
  env = 'PRE',
178
181
  log,
179
182
  window = globalWindow(),
180
- callbackBeforeRedirect = ()=>{}
183
+ callbackBeforeRedirect = () => {}
181
184
  }) {
182
185
  super();
183
186
  assert(isNonEmptyString(clientId), 'clientId parameter is required');
@@ -194,7 +197,7 @@ export class Identity extends EventEmitter {
194
197
  this.window = window;
195
198
  this.clientId = clientId;
196
199
  this.sessionStorageCache = new Cache(() => this.window && this.window.sessionStorage);
197
- this.localStorageCache = new Cache(() =>this.window && this.window.localStorage);
200
+ this.localStorageCache = new Cache(() => this.window && this.window.localStorage);
198
201
  this.redirectUri = redirectUri;
199
202
  this.env = env;
200
203
  this.log = log;
@@ -234,12 +237,13 @@ export class Identity extends EventEmitter {
234
237
  }
235
238
 
236
239
  /**
237
- * Checks if calling GET session is blocked
240
+ * Checks if calling GET session is blocked by a cache storage
238
241
  * @private
239
- * @returns {number|null}
242
+ * @param {Cache} cache - cache to check
243
+ * @returns {string|null}
240
244
  */
241
- _isSessionCallBlocked(){
242
- return this.localStorageCache.get(SESSION_CALL_BLOCKED_CACHE_KEY);
245
+ _isSessionCallBlocked(cache) {
246
+ return cache.get(SESSION_CALL_BLOCKED_CACHE_KEY);
243
247
  }
244
248
 
245
249
  /**
@@ -247,7 +251,15 @@ export class Identity extends EventEmitter {
247
251
  * @private
248
252
  * @returns {void}
249
253
  */
250
- _blockSessionCall(){
254
+ _blockSessionCall() {
255
+ // session storage block protects against single tab, multiple identity instance concurrency
256
+ this.sessionStorageCache.set(
257
+ SESSION_CALL_BLOCKED_CACHE_KEY,
258
+ this._tabId,
259
+ SESSION_CALL_BLOCKED_TTL
260
+ );
261
+
262
+ // local storage block protects against cross-tab concurrency
251
263
  this.localStorageCache.set(
252
264
  SESSION_CALL_BLOCKED_CACHE_KEY,
253
265
  this._tabId,
@@ -261,9 +273,11 @@ export class Identity extends EventEmitter {
261
273
  * @returns {void}
262
274
  */
263
275
  _unblockSessionCallByTab() {
264
- if (this._isSessionCallBlocked() === this._tabId) {
276
+ if (this._isSessionCallBlocked(this.localStorageCache) === this._tabId) {
265
277
  this.localStorageCache.delete(SESSION_CALL_BLOCKED_CACHE_KEY);
266
278
  }
279
+
280
+ this.sessionStorageCache.delete(SESSION_CALL_BLOCKED_CACHE_KEY);
267
281
  }
268
282
 
269
283
  /**
@@ -339,7 +353,7 @@ export class Identity extends EventEmitter {
339
353
  this._globalSessionService = new RESTClient({
340
354
  serverUrl: urlMapper(url, ENDPOINTS.SESSION_SERVICE),
341
355
  log: this.log,
342
- defaultParams: { client_sdrn, sdk_version: version },
356
+ defaultParams: {client_sdrn, sdk_version: version},
343
357
  });
344
358
  }
345
359
 
@@ -503,7 +517,7 @@ export class Identity extends EventEmitter {
503
517
  * @returns {void}
504
518
  */
505
519
  _clearVarnishCookie() {
506
- const baseDomain = this._session && typeof this._session.baseDomain === 'string'
520
+ const baseDomain = this._session && typeof this._session.baseDomain === 'string'
507
521
  ? this._session.baseDomain
508
522
  : document.domain;
509
523
 
@@ -565,74 +579,128 @@ export class Identity extends EventEmitter {
565
579
  this._maybeSetVarnishCookie(sessionData);
566
580
  this._emitSessionEvent(this._session, sessionData);
567
581
  this._session = sessionData;
582
+
568
583
  return sessionData;
569
584
  };
570
585
 
571
- const _checkRedirectionNeed = (sessionData={})=>{
586
+ const _checkRedirectionNeed = (sessionData = {}) => {
572
587
  const sessionDataKeys = Object.keys(sessionData);
573
588
 
574
589
  return sessionDataKeys.length === 1 &&
575
590
  sessionDataKeys[0] === 'redirectURL';
576
- }
591
+ };
577
592
 
578
593
  const _getSession = async () => {
579
- if (this._enableSessionCaching) {
580
- // Try to resolve from cache (it has a TTL)
581
- let cachedSession = this.sessionStorageCache.get(HAS_SESSION_CACHE_KEY);
582
- if (cachedSession) {
583
- return _postProcess(cachedSession);
594
+ const callSessionEndpoint = async () => {
595
+ try {
596
+ /* Blocking future calls to session-service. This lock is removed after the response is processed
597
+ to account for redirection that can happen towards session-service too */
598
+ this._blockSessionCall();
599
+
600
+ return await this._sessionService.get('/v2/session', {tabId: this._tabId});
601
+ } catch (err) {
602
+ if (err && err.code === 400 && this._enableSessionCaching) {
603
+ const expiresIn = 1000 * (err.expiresIn || 300);
604
+ this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, {error: err}, expiresIn);
605
+ }
606
+
607
+ throw err;
584
608
  }
585
- }
609
+ };
586
610
 
587
- // Prevent concurrent calls to session-service
588
- if (this._isSessionCallBlocked()) {
589
- // Returning data directly, without _postProcess since the data returned is the local copy
590
- return this._session;
591
- }
611
+ const useSessionResponseIfValid = async (sessionData) => {
612
+ if (sessionData) {
613
+ // For expiring session and WebKit browsers do a full page redirect to get a new session
614
+ if (_checkRedirectionNeed(sessionData)) {
615
+ await this.callbackBeforeRedirect();
592
616
 
593
- let sessionData = null;
594
- try {
595
- this._blockSessionCall();
617
+ // Doing a return here, to avoid caching the redirect response
618
+ return this.window.location.href = this._sessionService.makeUrl(sessionData.redirectURL, {tabId: this._getTabId()});
619
+ }
596
620
 
597
- sessionData = await this._sessionService.get('/v2/session', {tabId: this._tabId});
598
- } catch (err) {
599
- if (err && err.code === 400 && this._enableSessionCaching) {
600
- const expiresIn = 1000 * (err.expiresIn || 300);
601
- this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, { error: err }, expiresIn);
602
- }
603
- throw err;
604
- }
621
+ if (this._enableSessionCaching) {
622
+ const expiresIn = 1000 * (sessionData.expiresIn || 300);
623
+ this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, sessionData, expiresIn);
624
+ }
605
625
 
606
- if (sessionData){
607
- // For expiring session and Safari browser do full page redirect to get new session
608
- if(_checkRedirectionNeed(sessionData)){
609
- await this.callbackBeforeRedirect();
610
- this.window.location.href = this._sessionService.makeUrl(sessionData.redirectURL, {tabId: this._getTabId()});
626
+ return _postProcess(sessionData);
611
627
  }
628
+ };
612
629
 
630
+ const checkIfSessionCallIsNeededAndSafe = async (blockedAction) => {
613
631
  if (this._enableSessionCaching) {
614
- const expiresIn = 1000 * (sessionData.expiresIn || 300);
615
- this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, sessionData, expiresIn);
632
+ // Try to resolve from cache (it has a TTL)
633
+ let cachedSession = this.sessionStorageCache.get(HAS_SESSION_CACHE_KEY);
634
+ if (cachedSession) {
635
+ return _postProcess(cachedSession);
636
+ }
616
637
  }
617
- }
618
638
 
619
- return _postProcess(sessionData);
639
+ if (this._isSessionCallBlocked(this.sessionStorageCache) || this._isSessionCallBlocked(this.localStorageCache)) {
640
+ if (this._session && this._session.userId) {
641
+ return _postProcess(this._session);
642
+ }
643
+
644
+ // If blockedAction is defined, do that and return the result, otherwise return null
645
+ if (blockedAction) {
646
+ const blockedResult = await blockedAction();
647
+
648
+ return _postProcess(blockedResult);
649
+ }
650
+
651
+ return null;
652
+ }
653
+
654
+ // If session service calls are not blocked, call it
655
+ const sessionData = await callSessionEndpoint();
656
+
657
+ return await useSessionResponseIfValid(sessionData);
658
+ };
659
+
660
+ return await checkIfSessionCallIsNeededAndSafe(async () => {
661
+ let retryCount = 0;
662
+
663
+ // Try to call session-service MAX_SESSION_CALL_RETRIES times, waiting up to 1 second each time
664
+ while (retryCount < MAX_SESSION_CALL_RETRIES) {
665
+ retryCount++;
666
+
667
+ const randomWaitingStep = Math.floor(Math.random() * 9); // ignoring waiting times that are too small to matter
668
+ const randomWaitTime = MIN_SESSION_CALL_WAIT_TIME + (randomWaitingStep * 100);
669
+ await new Promise(resolve => setTimeout(resolve, randomWaitTime));
670
+
671
+ // attempt to call session service, but don't take any action if call is blocked and don't use the result
672
+ const result = await checkIfSessionCallIsNeededAndSafe(null);
673
+ if (result) {
674
+ return result;
675
+ }
676
+ }
677
+
678
+ // Exceeded number of attempts, returning old session info
679
+ if (this._session && this._session.userId) {
680
+ return this._session;
681
+ }
682
+
683
+ throw new SDKError('HasSession exceeded maximum number of attempts');
684
+ });
620
685
  };
686
+
621
687
  this._hasSessionInProgress = _getSession()
622
688
  .then(
623
689
  sessionData => {
624
690
  this._hasSessionInProgress = false;
691
+ this._unblockSessionCallByTab();
625
692
 
626
693
  return sessionData;
627
694
  },
628
695
  err => {
629
696
  this.emit('error', err);
630
697
  this._hasSessionInProgress = false;
698
+ this._unblockSessionCallByTab();
699
+
631
700
  throw new SDKError('HasSession failed', err);
632
701
  }
633
702
  );
634
703
 
635
- this._unblockSessionCallByTab();
636
704
  return this._hasSessionInProgress;
637
705
  }
638
706
 
@@ -673,8 +741,8 @@ export class Identity extends EventEmitter {
673
741
  async isConnected() {
674
742
  try {
675
743
  const data = await this.hasSession();
676
- // if data is not an object, the promise will fail.
677
- // if the result is present, it's boolean. But if it's not, it should be assumed false.
744
+ /* If data is not an object, the promise will fail.
745
+ If the result is present, it's boolean. But if it's not, it should be assumed false. */
678
746
  return !!data.result;
679
747
  } catch (_) {
680
748
  return false;
@@ -749,13 +817,13 @@ export class Identity extends EventEmitter {
749
817
  if (!pairId)
750
818
  throw new SDKError('pairId missing in user session!');
751
819
 
752
- if(!externalParty || externalParty.length === 0) {
820
+ if (!externalParty || externalParty.length === 0) {
753
821
  throw new SDKError('externalParty cannot be empty');
754
822
  }
755
- const _toHexDigest = (hashBuffer) =>{
756
- // convert buffer to byte array
823
+ const _toHexDigest = (hashBuffer) => {
824
+ // Convert buffer to byte array
757
825
  const hashArray = Array.from(new Uint8Array(hashBuffer));
758
- // convert bytes to hex string
826
+ // Convert bytes to hex string
759
827
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
760
828
  }
761
829
 
@@ -831,6 +899,7 @@ export class Identity extends EventEmitter {
831
899
  return null;
832
900
  }
833
901
  }
902
+
834
903
  /**
835
904
  * If a popup is desired, this function needs to be called in response to a user event (like
836
905
  * click or tap) in order to work correctly. Otherwise the popup will be blocked by the
@@ -953,7 +1022,7 @@ export class Identity extends EventEmitter {
953
1022
  prompt = 'select_account',
954
1023
  }) {
955
1024
  if (typeof arguments[0] !== 'object') {
956
- // backward compatibility
1025
+ // Backward compatibility
957
1026
  state = arguments[0];
958
1027
  acrValues = arguments[1];
959
1028
  scope = arguments[2] || scope;
@@ -1081,7 +1150,10 @@ export class Identity extends EventEmitter {
1081
1150
  };
1082
1151
 
1083
1152
  const loginNotYouHandler = async () => {
1084
- this.login(Object.assign(await prepareLoginParams(loginParams), {loginHint: userData.identifier, prompt: 'login'}));
1153
+ this.login(Object.assign(await prepareLoginParams(loginParams), {
1154
+ loginHint: userData.identifier,
1155
+ prompt: 'login'
1156
+ }));
1085
1157
  };
1086
1158
 
1087
1159
  const initHandler = () => {
package/src/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Automatically generated in 'npm version' by scripts/genversion.js
2
2
 
3
3
  'use strict'
4
- const version = '5.0.1-beta.2';
4
+ const version = '5.0.1-beta.4';
5
5
  export default version;