@schibsted/account-sdk-browser 5.0.0 → 5.0.1-beta.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schibsted/account-sdk-browser",
3
- "version": "5.0.0",
3
+ "version": "5.0.1-beta.2",
4
4
  "description": "Schibsted account SDK for browsers",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/cache.d.ts CHANGED
@@ -14,7 +14,6 @@ export default class Cache {
14
14
  /**
15
15
  * Get a value from cache (checks that the object has not expired)
16
16
  * @param {string} key
17
- * @private
18
17
  * @returns {*} - The value if it exists, otherwise null
19
18
  */
20
19
  private get;
@@ -23,14 +22,12 @@ export default class Cache {
23
22
  * @param {string} key
24
23
  * @param {*} value
25
24
  * @param {Number} expiresIn - Value in milliseconds until the entry expires
26
- * @private
27
25
  * @returns {void}
28
26
  */
29
27
  private set;
30
28
  /**
31
29
  * Delete a cache entry
32
30
  * @param {string} key
33
- * @private
34
31
  * @returns {void}
35
32
  */
36
33
  private delete;
package/src/cache.js CHANGED
@@ -89,7 +89,6 @@ export default class Cache {
89
89
  /**
90
90
  * Get a value from cache (checks that the object has not expired)
91
91
  * @param {string} key
92
- * @private
93
92
  * @returns {*} - The value if it exists, otherwise null
94
93
  */
95
94
  get(key) {
@@ -124,7 +123,6 @@ export default class Cache {
124
123
  * @param {string} key
125
124
  * @param {*} value
126
125
  * @param {Number} expiresIn - Value in milliseconds until the entry expires
127
- * @private
128
126
  * @returns {void}
129
127
  */
130
128
  set(key, value, expiresIn = 0) {
@@ -145,7 +143,6 @@ export default class Cache {
145
143
  /**
146
144
  * Delete a cache entry
147
145
  * @param {string} key
148
- * @private
149
146
  * @returns {void}
150
147
  */
151
148
  delete(key) {
package/src/identity.d.ts CHANGED
@@ -28,7 +28,8 @@ export class Identity extends TinyEmitter {
28
28
  _sessionInitiatedSent: boolean;
29
29
  window: any;
30
30
  clientId: string;
31
- cache: any;
31
+ sessionStorageCache: any;
32
+ localStorageCache: any;
32
33
  redirectUri: string;
33
34
  env: string;
34
35
  log: Function;
@@ -36,6 +37,31 @@ export class Identity extends TinyEmitter {
36
37
  _sessionDomain: string;
37
38
  _enableSessionCaching: boolean;
38
39
  _session: {};
40
+
41
+ /**
42
+ * Read tabId from session storage
43
+ * @returns {number}
44
+ * @private
45
+ */
46
+ private _getTabId;
47
+ /**
48
+ * Checks if calling get session is blocked
49
+ * @private
50
+ * @returns {boolean|void}
51
+ */
52
+ private _isSessionCallBlocked;
53
+ /**
54
+ * Block calls to get session. This is done to prevent concurrent calls which can log user out if session is refreshed by one of them
55
+ * @private
56
+ * @returns {void}
57
+ */
58
+ private _blockSessionCall;
59
+ /**
60
+ * Unblocks calls to get session if the lock was put by the same tab
61
+ * @private
62
+ * @returns {void}
63
+ */
64
+ private _unblockSessionCall;
39
65
  /**
40
66
  * Set SPiD server URL
41
67
  * @private
@@ -77,7 +103,7 @@ export class Identity extends TinyEmitter {
77
103
  private _setGlobalSessionServiceUrl;
78
104
  _globalSessionService: RESTClient;
79
105
  /**
80
- * Emits the relevant events based on the previous and new reply from hassession
106
+ * Emits the relevant events based on the previous and new reply from {@link Identity#hasSession}
81
107
  * @private
82
108
  * @param {object} previous
83
109
  * @param {object} current
@@ -92,7 +118,7 @@ export class Identity extends TinyEmitter {
92
118
  private _closePopup;
93
119
  popup: Window;
94
120
  /**
95
- * Set the Varnish cookie (`SP_ID`) when hasSession() is called. Note that most browsers require
121
+ * Set the Varnish cookie (`SP_ID`) when {@link Identity#hasSession} is called. Note that most browsers require
96
122
  * that you are on a "real domain" for this to work — so, **not** `localhost`
97
123
  * @param {object} [options]
98
124
  * @param {number} [options.expiresIn] Override this to set number of seconds before the varnish
@@ -195,7 +221,7 @@ export class Identity extends TinyEmitter {
195
221
  * @description This function calls {@link Identity#hasSession} internally and thus has the side
196
222
  * effect that it might perform an auto-login on the user
197
223
  * @throws {SDKError} If the user isn't connected to the merchant
198
- * @return {Promise<string>} The `userId` field (not to be confused with the `uuid`)
224
+ * @return {number} The `userId` field (not to be confused with the `uuid`)
199
225
  */
200
226
  getUserId(): Promise<string>;
201
227
  /**
@@ -355,7 +381,7 @@ export type LoginOptions = {
355
381
  * `password` (will force password confirmation, even if user is already logged in), `eid`. Those values might
356
382
  * be mixed as space-separated string. To make sure that user has authenticated with 2FA you need
357
383
  * to verify AMR (Authentication Methods References) claim in ID token.
358
- * Might also be used to ensure additional acr (sms, otp, eid) for already logged in users.
384
+ * Might also be used to ensure additional acr (sms, otp, eid) for already logged-in users.
359
385
  * Supported value is also 'otp-email' means one time password using email.
360
386
  */
361
387
  acrValues?: string;
@@ -424,7 +450,7 @@ export type SimplifiedLoginWidgetLoginOptions = {
424
450
  * `password` (will force password confirmation, even if user is already logged in). Those values might
425
451
  * be mixed as space-separated string. To make sure that user has authenticated with 2FA you need
426
452
  * to verify AMR (Authentication Methods References) claim in ID token.
427
- * Might also be used to ensure additional acr (sms, otp) for already logged in users.
453
+ * Might also be used to ensure additional acr (sms, otp) for already logged-in users.
428
454
  * Supported value is also 'otp-email' means one time password using email.
429
455
  */
430
456
  acrValues?: string;
@@ -592,7 +618,7 @@ export type HasSessionFailureResponse = {
592
618
  };
593
619
  export type SimplifiedLoginData = {
594
620
  /**
595
- * - Deprecated: User UUID, to be be used as `loginHint` for {@link Identity#login}
621
+ * - Deprecated: User UUID, to be used as `loginHint` for {@link Identity#login}
596
622
  */
597
623
  identifier: string;
598
624
  /**
package/src/identity.js CHANGED
@@ -26,7 +26,7 @@ import version from './version.js';
26
26
  * `password` (will force password confirmation, even if user is already logged in), `eid`. Those values might
27
27
  * be mixed as space-separated string. To make sure that user has authenticated with 2FA you need
28
28
  * to verify AMR (Authentication Methods References) claim in ID token.
29
- * Might also be used to ensure additional acr (sms, otp) for already logged in users.
29
+ * Might also be used to ensure additional acr (sms, otp) for already logged-in users.
30
30
  * Supported value is also 'otp-email' means one time password using email.
31
31
  * @property {string} [scope] - The OAuth scopes for the tokens. This is a list of
32
32
  * scopes, separated by space. If the list of scopes contains `openid`, the generated tokens
@@ -60,7 +60,7 @@ import version from './version.js';
60
60
  * `password` (will force password confirmation, even if user is already logged in). Those values might
61
61
  * be mixed as space-separated string. To make sure that user has authenticated with 2FA you need
62
62
  * to verify AMR (Authentication Methods References) claim in ID token.
63
- * Might also be used to ensure additional acr (sms, otp) for already logged in users.
63
+ * Might also be used to ensure additional acr (sms, otp) for already logged-in users.
64
64
  * Supported value is also 'otp-email' means one time password using email.
65
65
  * @property {string} [scope] - The OAuth scopes for the tokens. This is a list of
66
66
  * scopes, separated by space. If the list of scopes contains `openid`, the generated tokens
@@ -134,7 +134,7 @@ import version from './version.js';
134
134
 
135
135
  /**
136
136
  * @typedef {object} SimplifiedLoginData
137
- * @property {string} identifier - Deprecated: User UUID, to be be used as `loginHint` for {@link Identity#login}
137
+ * @property {string} identifier - Deprecated: User UUID, to be as `loginHint` for {@link Identity#login}
138
138
  * @property {string} display_text - Human-readable user identifier
139
139
  * @property {string} client_name - Client name
140
140
  */
@@ -146,12 +146,16 @@ import version from './version.js';
146
146
 
147
147
  const HAS_SESSION_CACHE_KEY = 'hasSession-cache';
148
148
  const SESSION_CALL_BLOCKED_CACHE_KEY = 'sessionCallBlocked-cache';
149
- const SESSION_CALL_BLOCKED_TTL = 1000 * 60 * 5;
149
+ const SESSION_CALL_BLOCKED_TTL = 1000 * 30; //set to 30s, the default period for a request timeout
150
+
151
+ const TAB_ID_KEY = 'tab-id-cache';
152
+ const TAB_ID = Math.floor(Math.random() * 100000)
153
+ const TAB_ID_TTL = 1000 * 60 * 60 * 24 * 30;
150
154
 
151
155
  const globalWindow = () => window;
152
156
 
153
157
  /**
154
- * Provides Identity functionalty to a web page
158
+ * Provides Identity functionality to a web page
155
159
  */
156
160
  export class Identity extends EventEmitter {
157
161
  /**
@@ -182,18 +186,21 @@ export class Identity extends EventEmitter {
182
186
  assert(sessionDomain && isUrl(sessionDomain), 'sessionDomain parameter is not a valid URL');
183
187
 
184
188
  spidTalk.emulate(window);
189
+
190
+ // Internal hack: set as false to always refresh from hasSession
191
+ this._enableSessionCaching = true;
192
+
185
193
  this._sessionInitiatedSent = false;
186
194
  this.window = window;
187
195
  this.clientId = clientId;
188
- this.cache = new Cache(() => this.window && this.window.sessionStorage);
196
+ this.sessionStorageCache = new Cache(() => this.window && this.window.sessionStorage);
197
+ this.localStorageCache = new Cache(() =>this.window && this.window.localStorage);
189
198
  this.redirectUri = redirectUri;
190
199
  this.env = env;
191
200
  this.log = log;
192
201
  this.callbackBeforeRedirect = callbackBeforeRedirect;
193
202
  this._sessionDomain = sessionDomain;
194
-
195
- // Internal hack: set to false to always refresh from hassession
196
- this._enableSessionCaching = true;
203
+ this._tabId = this._getTabId();
197
204
 
198
205
  // Old session
199
206
  this._session = {};
@@ -203,49 +210,59 @@ export class Identity extends EventEmitter {
203
210
  this._setBffServerUrl(env);
204
211
  this._setOauthServerUrl(env);
205
212
  this._setGlobalSessionServiceUrl(env);
206
-
207
- this._unblockSessionCall();
213
+ this._unblockSessionCallByTab();
208
214
  }
209
215
 
210
216
  /**
211
- * Checks if getting session is blocked
217
+ * Read tabId from session storage if possible, otherwise save tabId to session storage and return it
218
+ * @returns {number}
212
219
  * @private
213
- *
214
- * @returns {boolean|void}
215
220
  */
216
- _isSessionCallBlocked(){
221
+ _getTabId() {
217
222
  if (this._enableSessionCaching) {
218
- return this.cache.get(SESSION_CALL_BLOCKED_CACHE_KEY);
223
+ const tabId = this.sessionStorageCache.get(TAB_ID_KEY);
224
+ if (!tabId) {
225
+ this.sessionStorageCache.set(TAB_ID_KEY, TAB_ID, TAB_ID_TTL);
226
+
227
+ return TAB_ID;
228
+ }
229
+
230
+ return tabId;
219
231
  }
232
+
233
+ return TAB_ID;
220
234
  }
221
235
 
222
236
  /**
223
- * Block calls to get session
237
+ * Checks if calling GET session is blocked
238
+ * @private
239
+ * @returns {number|null}
240
+ */
241
+ _isSessionCallBlocked(){
242
+ return this.localStorageCache.get(SESSION_CALL_BLOCKED_CACHE_KEY);
243
+ }
244
+
245
+ /**
246
+ * Block calls to get session. This is done to prevent concurrent calls which can log user out if session is refreshed by one of them
224
247
  * @private
225
- *
226
248
  * @returns {void}
227
249
  */
228
250
  _blockSessionCall(){
229
- if (this._enableSessionCaching) {
230
- const SESSION_CALL_BLOCKED = true;
231
-
232
- this.cache.set(
233
- SESSION_CALL_BLOCKED_CACHE_KEY,
234
- SESSION_CALL_BLOCKED,
235
- SESSION_CALL_BLOCKED_TTL
236
- );
237
- }
251
+ this.localStorageCache.set(
252
+ SESSION_CALL_BLOCKED_CACHE_KEY,
253
+ this._tabId,
254
+ SESSION_CALL_BLOCKED_TTL
255
+ );
238
256
  }
239
257
 
240
258
  /**
241
- * Unblocks calls to get session
259
+ * Unblocks calls to get session if the lock was put by the same tab
242
260
  * @private
243
- *
244
261
  * @returns {void}
245
262
  */
246
- _unblockSessionCall(){
247
- if (this._enableSessionCaching) {
248
- this.cache.delete(SESSION_CALL_BLOCKED_CACHE_KEY);
263
+ _unblockSessionCallByTab() {
264
+ if (this._isSessionCallBlocked() === this._tabId) {
265
+ this.localStorageCache.delete(SESSION_CALL_BLOCKED_CACHE_KEY);
249
266
  }
250
267
  }
251
268
 
@@ -327,7 +344,7 @@ export class Identity extends EventEmitter {
327
344
  }
328
345
 
329
346
  /**
330
- * Emits the relevant events based on the previous and new reply from hassession
347
+ * Emits the relevant events based on the previous and new reply from {@link Identity#hasSession}
331
348
  * @private
332
349
  * @param {object} previous
333
350
  * @param {object} current
@@ -407,7 +424,7 @@ export class Identity extends EventEmitter {
407
424
  }
408
425
 
409
426
  /**
410
- * Set the Varnish cookie (`SP_ID`) when hasSession() is called. Note that most browsers require
427
+ * Set the Varnish cookie (`SP_ID`) when {@link Identity#hasSession} is called. Note that most browsers require
411
428
  * that you are on a "real domain" for this to work — so, **not** `localhost`
412
429
  * @param {object} [options]
413
430
  * @param {number} [options.expiresIn] Override this to set number of seconds before the varnish
@@ -537,11 +554,6 @@ export class Identity extends EventEmitter {
537
554
  * @return {Promise<HasSessionSuccessResponse|HasSessionFailureResponse>}
538
555
  */
539
556
  hasSession() {
540
- const isSessionCallBlocked = this._isSessionCallBlocked()
541
- if (isSessionCallBlocked) {
542
- return this._session;
543
- }
544
-
545
557
  if (this._hasSessionInProgress) {
546
558
  return this._hasSessionInProgress;
547
559
  }
@@ -566,35 +578,41 @@ export class Identity extends EventEmitter {
566
578
  const _getSession = async () => {
567
579
  if (this._enableSessionCaching) {
568
580
  // Try to resolve from cache (it has a TTL)
569
- let cachedSession = this.cache.get(HAS_SESSION_CACHE_KEY);
581
+ let cachedSession = this.sessionStorageCache.get(HAS_SESSION_CACHE_KEY);
570
582
  if (cachedSession) {
571
583
  return _postProcess(cachedSession);
572
584
  }
573
585
  }
586
+
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
+ }
592
+
574
593
  let sessionData = null;
575
594
  try {
576
- sessionData = await this._sessionService.get('/v2/session');
595
+ this._blockSessionCall();
596
+
597
+ sessionData = await this._sessionService.get('/v2/session', {tabId: this._tabId});
577
598
  } catch (err) {
578
599
  if (err && err.code === 400 && this._enableSessionCaching) {
579
600
  const expiresIn = 1000 * (err.expiresIn || 300);
580
- this.cache.set(HAS_SESSION_CACHE_KEY, { error: err }, expiresIn);
601
+ this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, { error: err }, expiresIn);
581
602
  }
582
603
  throw err;
583
604
  }
584
605
 
585
606
  if (sessionData){
586
- // for expiring session and safari browser do full page redirect to gain new session
607
+ // For expiring session and Safari browser do full page redirect to get new session
587
608
  if(_checkRedirectionNeed(sessionData)){
588
- this._blockSessionCall();
589
-
590
609
  await this.callbackBeforeRedirect();
591
-
592
- return this._sessionService.makeUrl(sessionData.redirectURL);
610
+ this.window.location.href = this._sessionService.makeUrl(sessionData.redirectURL, {tabId: this._getTabId()});
593
611
  }
594
612
 
595
613
  if (this._enableSessionCaching) {
596
614
  const expiresIn = 1000 * (sessionData.expiresIn || 300);
597
- this.cache.set(HAS_SESSION_CACHE_KEY, sessionData, expiresIn);
615
+ this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, sessionData, expiresIn);
598
616
  }
599
617
  }
600
618
 
@@ -605,10 +623,6 @@ export class Identity extends EventEmitter {
605
623
  sessionData => {
606
624
  this._hasSessionInProgress = false;
607
625
 
608
- if (isUrl(sessionData)) {
609
- return this.window.location.href = sessionData;
610
- }
611
-
612
626
  return sessionData;
613
627
  },
614
628
  err => {
@@ -618,6 +632,7 @@ export class Identity extends EventEmitter {
618
632
  }
619
633
  );
620
634
 
635
+ this._unblockSessionCallByTab();
621
636
  return this._hasSessionInProgress;
622
637
  }
623
638
 
@@ -642,7 +657,7 @@ export class Identity extends EventEmitter {
642
657
  * @returns {void}
643
658
  */
644
659
  clearCachedUserSession() {
645
- this.cache.delete(HAS_SESSION_CACHE_KEY);
660
+ this.sessionStorageCache.delete(HAS_SESSION_CACHE_KEY);
646
661
  }
647
662
 
648
663
  /**
@@ -696,7 +711,7 @@ export class Identity extends EventEmitter {
696
711
  * @description This function calls {@link Identity#hasSession} internally and thus has the side
697
712
  * effect that it might perform an auto-login on the user
698
713
  * @throws {SDKError} If the user isn't connected to the merchant
699
- * @return {Promise<string>} The `userId` field (not to be confused with the `uuid`)
714
+ * @return {number} The `userId` field (not to be confused with the `uuid`)
700
715
  */
701
716
  async getUserId() {
702
717
  const user = await this.hasSession();
@@ -853,7 +868,7 @@ export class Identity extends EventEmitter {
853
868
  prompt = 'select_account'
854
869
  }) {
855
870
  this._closePopup();
856
- this.cache.delete(HAS_SESSION_CACHE_KEY);
871
+ this.sessionStorageCache.delete(HAS_SESSION_CACHE_KEY);
857
872
  const url = this.loginUrl({
858
873
  state,
859
874
  acrValues,
@@ -902,7 +917,7 @@ export class Identity extends EventEmitter {
902
917
  * @return {void}
903
918
  */
904
919
  logout(redirectUri = this.redirectUri) {
905
- this.cache.delete(HAS_SESSION_CACHE_KEY);
920
+ this.sessionStorageCache.delete(HAS_SESSION_CACHE_KEY);
906
921
  this._maybeClearVarnishCookie();
907
922
  this.emit('logout');
908
923
  this.window.location.href = this.logoutUrl(redirectUri);
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.0';
4
+ const version = '5.0.1-beta.2';
5
5
  export default version;