@schibsted/account-sdk-browser 6.0.0-alpha.2 → 6.0.0-alpha.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.
Files changed (60) hide show
  1. package/README.md +39 -39
  2. package/dist/account-sdk.d.ts +4 -0
  3. package/dist/account.d.ts +30 -0
  4. package/dist/global-registry.d.ts +1 -1
  5. package/dist/globals.d.ts +2 -0
  6. package/dist/has-access-response.d.ts +2 -0
  7. package/dist/identity.d.ts +37 -237
  8. package/dist/index.d.ts +8 -3
  9. package/dist/index.js +862 -4
  10. package/dist/index.js.map +1 -0
  11. package/dist/monetization.d.ts +7 -43
  12. package/dist/resolve-browser-window.d.ts +1 -0
  13. package/dist/{RESTClient.d.ts → rest-client.d.ts} +22 -11
  14. package/dist/rest-clients.d.ts +10 -0
  15. package/dist/session-response.d.ts +30 -0
  16. package/dist/types/account.d.ts +13 -0
  17. package/dist/types/common.d.ts +22 -0
  18. package/dist/types/identity.d.ts +41 -0
  19. package/dist/types/login.d.ts +120 -0
  20. package/dist/types/monetization-guards.d.ts +2 -0
  21. package/dist/types/monetization.d.ts +21 -0
  22. package/dist/types/session-guards.d.ts +36 -0
  23. package/dist/types/session.d.ts +124 -0
  24. package/dist/version.d.ts +1 -1
  25. package/package.json +1 -9
  26. package/src/account-sdk.ts +74 -0
  27. package/src/account.ts +153 -0
  28. package/src/cache.ts +1 -1
  29. package/src/global-registry.ts +1 -1
  30. package/src/globals.ts +2 -0
  31. package/src/has-access-response.ts +35 -0
  32. package/src/identity.ts +269 -423
  33. package/src/index.ts +28 -3
  34. package/src/monetization.ts +31 -68
  35. package/src/object.ts +1 -1
  36. package/src/resolve-browser-window.ts +11 -0
  37. package/src/{RESTClient.ts → rest-client.ts} +55 -28
  38. package/src/rest-clients.ts +30 -0
  39. package/src/session-response.ts +79 -0
  40. package/src/types/account.ts +27 -0
  41. package/src/types/common.ts +26 -0
  42. package/src/types/identity.ts +55 -0
  43. package/src/types/login.ts +145 -0
  44. package/src/types/monetization-guards.ts +35 -0
  45. package/src/types/monetization.ts +24 -0
  46. package/src/types/session-guards.ts +89 -0
  47. package/src/types/session.ts +147 -0
  48. package/src/validate.ts +1 -1
  49. package/src/version.ts +1 -1
  50. package/dist/identity-s4nofYmB.js +0 -370
  51. package/dist/identity-s4nofYmB.js.map +0 -1
  52. package/dist/identity.js +0 -2
  53. package/dist/monetization.js +0 -72
  54. package/dist/monetization.js.map +0 -1
  55. package/dist/version-spE-k97g.js +0 -289
  56. package/dist/version-spE-k97g.js.map +0 -1
  57. /package/dist/{SDKError.d.ts → sdk-error.d.ts} +0 -0
  58. /package/dist/{spidTalk.d.ts → spid-talk.d.ts} +0 -0
  59. /package/src/{SDKError.ts → sdk-error.ts} +0 -0
  60. /package/src/{spidTalk.ts → spid-talk.ts} +0 -0
package/src/identity.ts CHANGED
@@ -4,202 +4,62 @@
4
4
 
5
5
  import { TinyEmitter } from 'tiny-emitter';
6
6
  import Cache from './cache.js';
7
- import { ENDPOINTS, NAMESPACE } from './config.js';
7
+ import { ENDPOINTS } from './config.js';
8
8
  import { registerAndDispatchInGlobal } from './global-registry.js';
9
9
  import { cloneDeep } from './object.js';
10
10
  import * as popup from './popup.js';
11
- import RESTClient from './RESTClient.js';
12
- import SDKError from './SDKError.js';
13
- import * as spidTalk from './spidTalk.js';
11
+ import type RESTClient from './rest-client.js';
12
+ import { buildClientSdrn, createAccountRestClient } from './rest-clients.js';
13
+ import SDKError from './sdk-error.js';
14
+ import {
15
+ isConnectedSessionResponse,
16
+ isConnectedSessionWithUserIdResponse,
17
+ isRedirectSessionPayload,
18
+ isSessionFailureResponse,
19
+ isSessionSuccessResponse,
20
+ normalizeSessionResponse,
21
+ } from './session-response.js';
22
+ import * as spidTalk from './spid-talk.js';
23
+ import type { LogFunction } from './types/common.js';
24
+ import type {
25
+ IdentityBeforeRedirectCallback,
26
+ IdentityOptions,
27
+ VarnishCookieOptions,
28
+ } from './types/identity.js';
29
+ import {
30
+ hasOpenSimplifiedLoginWidget,
31
+ type LoginOptions,
32
+ type OpenSimplifiedLoginWidget,
33
+ type SimplifiedLoginData,
34
+ type SimplifiedLoginWidgetInitialParams,
35
+ type SimplifiedLoginWidgetLoginOptions,
36
+ type SimplifiedLoginWidgetOptions,
37
+ } from './types/login.js';
38
+ import type {
39
+ ConnectedSessionResponse,
40
+ NormalizedSessionPayload,
41
+ SessionObjectResponse,
42
+ SessionResponse,
43
+ } from './types/session.js';
14
44
  import { urlMapper } from './url.js';
15
45
  import { assert, isNonEmptyString, isObject, isStr, isStrIn, isUrl } from './validate.js';
16
46
  import version from './version.js';
17
47
 
18
- /**
19
- * Options accepted by {@link Identity#login} and related login URL helpers.
20
- */
21
- export type LoginOptions = {
22
- /**
23
- * An opaque value used by the client to maintain state between the request and callback.
24
- * It's also recommended to prevent CSRF {@link https://tools.ietf.org/html/rfc6749#section-10.12}
25
- */
26
- state: string;
27
- /**
28
- * Authentication Context Class Reference Values. If omitted, the user will be asked to
29
- * authenticate using username+password. For 2FA (Two-Factor Authentication) possible values
30
- * are `sms`, `otp` (one time password), `password` (will force password confirmation, even if
31
- * user is already logged in), `eid`. Those values might be mixed as space-separated string.
32
- * To make sure that user has authenticated with 2FA you need to verify AMR (Authentication
33
- * Methods References) claim in ID token. Might also be used to ensure additional acr
34
- * (sms, otp, eid) for already logged in users. Supported value is also 'otp-email' means one
35
- * time password using email.
36
- */
37
- acrValues?: string;
38
- /**
39
- * The OAuth scopes for the tokens. This is a list of scopes, separated by space. If the list
40
- * of scopes contains `openid`, the generated tokens includes the id token which can be useful
41
- * for getting information about the user. Omitting scope is allowed, while `invalid_scope` is
42
- * returned when the client asks for a scope you aren't allowed to request.
43
- * {@link https://tools.ietf.org/html/rfc6749#section-3.3}
44
- * Defaults to `openid`.
45
- */
46
- scope?: string;
47
- /**
48
- * Redirect uri that will receive the code. Must exactly match a redirectUri from your client
49
- * in self-service
50
- */
51
- redirectUri?: string;
52
- /** Should we try to open a popup window? Defaults to `false`. */
53
- preferPopup?: boolean;
54
- /** user email or UUID hint */
55
- loginHint?: string;
56
- /** Pulse tag */
57
- tag?: string;
58
- /** Teaser slug. Teaser with given slug will be displayed in place of default teaser */
59
- teaser?: string;
60
- /**
61
- * Specifies the allowable elapsed time in seconds since the last time the End-User was actively
62
- * authenticated. If last authentication time is more than maxAge seconds in the past,
63
- * re-authentication will be required. See the OpenID Connect spec section 3.1.2.1 for more
64
- * information
65
- */
66
- maxAge?: number | string;
67
- /**
68
- * Optional parameter to overwrite client locale setting.
69
- * New flows supports nb_NO, fi_FI, sv_SE, en_US
70
- */
71
- locale?: string;
72
- /** display username and password on one screen. Defaults to `false`. */
73
- oneStepLogin?: boolean;
74
- /**
75
- * String that specifies whether the Authorization Server prompts the End-User for
76
- * reauthentication or confirm account screen. Supported values: `select_account` or `login`.
77
- * Defaults to `select_account`.
78
- */
79
- prompt?: string;
80
- /** Identifier for cross-domain tracking in Pulse */
81
- xDomainId?: string;
82
- /** Environment for cross-domain tracking in Pulse */
83
- xEnvironmentId?: string;
84
- /** Campaign identifier for tracking in Pulse */
85
- originCampaign?: string;
86
- };
87
-
88
- /**
89
- * Identical to {@link LoginOptions} except that `state` may also be a (possibly async) function;
90
- * all other fields reuse the `LoginOptions` documentation.
91
- */
92
- export type SimplifiedLoginWidgetLoginOptions = Omit<LoginOptions, 'state'> & {
93
- /**
94
- * An opaque value used by the client to maintain state between the request and callback.
95
- * It's also recommended to prevent CSRF {@link https://tools.ietf.org/html/rfc6749#section-10.12}
96
- */
97
- state: string | (() => string | Promise<string>);
98
- };
99
-
100
- /**
101
- * Successful response returned from {@link Identity#hasSession}.
102
- */
103
- export type HasSessionSuccessResponse = {
104
- /**
105
- * Is the user connected to the merchant? (it means that the merchant id is in the list of
106
- * merchants listed of this user in the database)? Example: false
107
- */
108
- result: boolean;
109
- /** Example: 'notConnected' or 'connected'. Deprecated, use `Identity.isConnected()` */
110
- userStatus: string;
111
- /** Example: 'localhost' */
112
- baseDomain: string;
113
- /** Example: '58eca10fdbb9f6df72c3368f'. Obsolete */
114
- id: string;
115
- /** Example: 37162 */
116
- userId: number;
117
- /** Example: 'b3b23aa7-34f2-5d02-a10e-5a3455c6ab2c' */
118
- uuid: string;
119
- /** Example: 'eyJjbGllbnRfaWQ...' */
120
- sp_id: string;
121
- /** Example: 30 * 60 * 1000 (for 30 minutes) */
122
- expiresIn: number;
123
- /** Example: 1506285759 */
124
- serverTime: number;
125
- /**
126
- * Example: 'NCdzXaz4ZRb7...' The sig parameter is a concatenation of an HMAC SHA-256 signature
127
- * string, a dot (.) and a base64url encoded JSON object (session).
128
- * {@link http://techdocs.spid.no/sdks/js/response-signature-and-validation/}
129
- */
130
- sig: string;
131
- /** (Only for connected users) Example: 'batman' */
132
- displayName: string;
133
- /** (Only for connected users) Example: 'Bruce' */
134
- givenName: string;
135
- /** (Only for connected users) Example: 'Wayne' */
136
- familyName: string;
137
- /** (Only for connected users) Example: 'male', 'female', 'undisclosed' */
138
- gender: string;
139
- /** (Only for connected users) Example: 'http://www.srv.com/some/picture.jpg' */
140
- photo: string;
141
- /** (Only for connected users) */
142
- tracking: boolean;
143
- /** (Only for connected users) */
144
- clientAgreementAccepted: boolean;
145
- /** (Only for connected users) */
146
- defaultAgreementAccepted: boolean;
147
- pairId: string;
148
- sdrn: string;
149
- };
150
-
151
- /**
152
- * Failure response returned from {@link Identity#hasSession}.
153
- */
154
- export type HasSessionFailureResponse = {
155
- error: {
156
- /** Typically an HTTP response code. Example: 401 */
157
- code: number;
158
- /** Example: "No session found!" */
159
- description: string;
160
- /** Example: "UserException" */
161
- type: string;
162
- };
163
- response: {
164
- /** Example: "localhost" */
165
- baseDomain: string;
166
- /** Time span in milliseconds. Example: 30 * 60 * 1000 (for 30 minutes) */
167
- expiresIn: number;
168
- result: boolean;
169
- /** Server time in seconds since the Unix Epoch. Example: 1506287788 */
170
- serverTime: number;
171
- };
172
- };
173
-
174
- /**
175
- * Minimal user information used by the simplified login widget flow.
176
- */
177
- export type SimplifiedLoginData = {
178
- /** Deprecated: User UUID, to be be used as `loginHint` for {@link Identity#login} */
179
- identifier: string;
180
- /** Human-readable user identifier */
181
- display_text: string;
182
- /** Client name */
183
- client_name: string;
184
- };
185
-
186
- /**
187
- * Configuration options for {@link Identity#showSimplifiedLoginWidget}.
188
- */
189
- export type SimplifiedLoginWidgetOptions = {
190
- /** expected encoding of simplified login widget. Could be utf-8 (default), iso-8859-1 or iso-8859-15 */
191
- encoding: string;
192
- /**
193
- * expected locale of simplified login widget. Should be provided in a short format like 'nb',
194
- * 'sv'. If not set, a value from the env variable is used.
195
- */
196
- locale?: 'nb' | 'sv' | 'fi' | 'da' | 'en';
197
- };
198
-
199
- /**
200
- * Emitted when an error happens (useful for debugging)
201
- * @event Identity#error
202
- */
48
+ export { isConnectedSessionResponse, isSessionSuccessResponse } from './session-response.js';
49
+ export type {
50
+ ConnectedSessionResponse,
51
+ SessionFailureResponse,
52
+ SessionObjectResponse,
53
+ SessionResponse,
54
+ SessionSuccessResponse,
55
+ SessionUserId,
56
+ } from './types/session.js';
57
+
58
+ declare global {
59
+ interface Window {
60
+ openSimplifiedLoginWidget?: OpenSimplifiedLoginWidget;
61
+ }
62
+ }
203
63
 
204
64
  const HAS_SESSION_CACHE_KEY = 'hasSession-cache';
205
65
  const SESSION_CALL_BLOCKED_CACHE_KEY = 'sessionCallBlocked-cache';
@@ -216,24 +76,28 @@ const globalWindow = () => window;
216
76
  */
217
77
  export class Identity extends TinyEmitter {
218
78
  _sessionInitiatedSent: boolean;
219
- window: any;
79
+ window: Window;
220
80
  clientId: string;
221
- sessionStorageCache: any;
222
- localStorageCache: any;
81
+ sessionStorageCache: Cache;
82
+ localStorageCache: Cache;
223
83
  redirectUri: string;
224
84
  env: string;
225
- log?: Function;
226
- callbackBeforeRedirect: Function;
85
+ log?: LogFunction;
86
+ callbackBeforeRedirect: IdentityBeforeRedirectCallback;
227
87
  _sessionDomain: string;
228
88
  _enableSessionCaching: boolean;
229
- _session: any;
230
- _spid!: RESTClient;
231
- _oauthService!: RESTClient;
232
- _bffService!: RESTClient;
233
- _sessionService!: RESTClient;
234
- _globalSessionService!: RESTClient;
235
- _usedSessionServiceGetSessionEndpoint: any;
236
- _hasSessionInProgress?: boolean | Promise<any>;
89
+ /**
90
+ * @internal Starts as `{}` before the first `hasSession()` call, then stores the latest
91
+ * object response from hasSession().
92
+ */
93
+ _session: SessionObjectResponse;
94
+ _spid: RESTClient;
95
+ _oauthService: RESTClient;
96
+ _bffService: RESTClient;
97
+ _sessionService: RESTClient;
98
+ _globalSessionService: RESTClient;
99
+ _usedSessionServiceGetSessionEndpoint: 'v2/session' | 'session';
100
+ _hasSessionInProgress?: false | Promise<SessionResponse>;
237
101
  popup?: Window | null;
238
102
  setVarnishCookie?: boolean;
239
103
  varnishExpiresIn?: number;
@@ -259,15 +123,7 @@ export class Identity extends TinyEmitter {
259
123
  log,
260
124
  window = globalWindow(),
261
125
  callbackBeforeRedirect = () => {},
262
- }: {
263
- clientId: string;
264
- sessionDomain: string;
265
- redirectUri: string;
266
- env?: string;
267
- log?: Function;
268
- window?: Window;
269
- callbackBeforeRedirect?: Function;
270
- }) {
126
+ }: IdentityOptions) {
271
127
  super();
272
128
  assert(isNonEmptyString(clientId), 'clientId parameter is required');
273
129
  assert(isObject(window), 'The reference to window is missing');
@@ -276,6 +132,7 @@ export class Identity extends TinyEmitter {
276
132
  isNonEmptyString(sessionDomain) && isUrl(sessionDomain),
277
133
  'sessionDomain parameter is not a valid URL',
278
134
  );
135
+ assert(isStr(env), `env parameter is invalid: ${env}`);
279
136
 
280
137
  spidTalk.emulate(window);
281
138
  this._sessionInitiatedSent = false;
@@ -295,16 +152,43 @@ export class Identity extends TinyEmitter {
295
152
  // Old session
296
153
  this._session = {};
297
154
 
298
- this._setSessionServiceUrl(sessionDomain);
155
+ const accountClientParams = { client_id: this.clientId, redirect_uri: this.redirectUri };
156
+ const clientSdrn = buildClientSdrn(this.env, this.clientId);
157
+
158
+ this._sessionService = createAccountRestClient({
159
+ serverUrl: sessionDomain,
160
+ log: this.log,
161
+ defaultParams: {
162
+ client_sdrn: clientSdrn,
163
+ redirect_uri: this.redirectUri,
164
+ sdk_version: version,
165
+ },
166
+ });
299
167
  this._usedSessionServiceGetSessionEndpoint =
300
168
  this._sessionService.url.pathname && this._sessionService.url.pathname.length <= 1
301
169
  ? 'v2/session'
302
170
  : 'session';
303
171
 
304
- this._setSpidServerUrl(env);
305
- this._setBffServerUrl(env);
306
- this._setOauthServerUrl(env);
307
- this._setGlobalSessionServiceUrl(env);
172
+ this._spid = createAccountRestClient({
173
+ serverUrl: urlMapper(env, ENDPOINTS.SPiD),
174
+ log: this.log,
175
+ defaultParams: accountClientParams,
176
+ });
177
+ this._bffService = createAccountRestClient({
178
+ serverUrl: urlMapper(env, ENDPOINTS.BFF),
179
+ log: this.log,
180
+ defaultParams: accountClientParams,
181
+ });
182
+ this._oauthService = createAccountRestClient({
183
+ serverUrl: urlMapper(env, ENDPOINTS.SPiD),
184
+ log: this.log,
185
+ defaultParams: accountClientParams,
186
+ });
187
+ this._globalSessionService = createAccountRestClient({
188
+ serverUrl: urlMapper(env, ENDPOINTS.SESSION_SERVICE),
189
+ log: this.log,
190
+ defaultParams: { client_sdrn: clientSdrn, sdk_version: version },
191
+ });
308
192
 
309
193
  this._unblockSessionCall();
310
194
 
@@ -318,7 +202,7 @@ export class Identity extends TinyEmitter {
318
202
  _getTabId(): number | undefined {
319
203
  if (this._enableSessionCaching) {
320
204
  const tabId = this.sessionStorageCache.get(TAB_ID_KEY);
321
- if (!tabId) {
205
+ if (typeof tabId !== 'number') {
322
206
  this.sessionStorageCache.set(TAB_ID_KEY, TAB_ID, TAB_ID_TTL);
323
207
  return TAB_ID;
324
208
  }
@@ -332,8 +216,8 @@ export class Identity extends TinyEmitter {
332
216
  * @private
333
217
  *
334
218
  */
335
- _isSessionCallBlocked(): boolean | void {
336
- return this.localStorageCache.get(SESSION_CALL_BLOCKED_CACHE_KEY);
219
+ _isSessionCallBlocked(): boolean {
220
+ return Boolean(this.localStorageCache.get(SESSION_CALL_BLOCKED_CACHE_KEY));
337
221
  }
338
222
 
339
223
  /**
@@ -361,97 +245,34 @@ export class Identity extends TinyEmitter {
361
245
  }
362
246
 
363
247
  /**
364
- * Set SPiD server URL
365
- * @private
366
- * @param url - real URL or 'PRE' style key
367
- */
368
- _setSpidServerUrl(url: string): void {
369
- assert(isStr(url), `url parameter is invalid: ${url}`);
370
- this._spid = new RESTClient({
371
- serverUrl: urlMapper(url, ENDPOINTS.SPiD),
372
- log: this.log,
373
- defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri },
374
- });
375
- }
376
-
377
- /**
378
- * Set OAuth server URL
379
- * @private
380
- * @param url - real URL or 'PRE' style key
381
- */
382
- _setOauthServerUrl(url: string): void {
383
- assert(isStr(url), `url parameter is invalid: ${url}`);
384
- this._oauthService = new RESTClient({
385
- serverUrl: urlMapper(url, ENDPOINTS.SPiD),
386
- log: this.log,
387
- defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri },
388
- });
389
- }
390
-
391
- /**
392
- * Set BFF server URL
393
- * @private
394
- * @param url - real URL or 'PRE' style key
395
- */
396
- _setBffServerUrl(url: string): void {
397
- assert(isStr(url), `url parameter is invalid: ${url}`);
398
- this._bffService = new RESTClient({
399
- serverUrl: urlMapper(url, ENDPOINTS.BFF),
400
- log: this.log,
401
- defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri },
402
- });
403
- }
404
-
405
- /**
406
- * Set site-specific session-service domain
407
- * @private
408
- * @param domain - real URL — (**not** 'PRE' style env key)
409
- */
410
- _setSessionServiceUrl(domain: string): void {
411
- assert(isStr(domain), `domain parameter is invalid: ${domain}`);
412
- const client_sdrn = `sdrn:${NAMESPACE[this.env as keyof typeof NAMESPACE]}:client:${this.clientId}`;
413
- this._sessionService = new RESTClient({
414
- serverUrl: domain,
415
- log: this.log,
416
- defaultParams: { client_sdrn, redirect_uri: this.redirectUri, sdk_version: version },
417
- });
418
- }
419
-
420
- /**
421
- * Set global session-service server URL
422
- * @private
423
- * @param url - real URL or 'PRE' style key
424
- */
425
- _setGlobalSessionServiceUrl(url: string): void {
426
- assert(isStr(url), `url parameter is invalid: ${url}`);
427
- const client_sdrn = `sdrn:${NAMESPACE[this.env as keyof typeof NAMESPACE]}:client:${this.clientId}`;
428
- this._globalSessionService = new RESTClient({
429
- serverUrl: urlMapper(url, ENDPOINTS.SESSION_SERVICE),
430
- log: this.log,
431
- defaultParams: { client_sdrn, sdk_version: version },
432
- });
433
- }
434
-
435
- /**
436
- * Emits the relevant events based on the previous and new reply from hassession
248
+ * Emits the relevant events based on the previous and new reply from hasSession
437
249
  * @private
438
250
  * @param previous
439
251
  * @param current
440
252
  */
441
- _emitSessionEvent(previous: any, current: any): void {
253
+ _emitSessionEvent(previous: object, current: object): void {
254
+ const previousUserId = isSessionSuccessResponse(previous) ? previous.userId : undefined;
255
+ const currentUserId = isSessionSuccessResponse(current) ? current.userId : undefined;
256
+ const previousUserStatus = isSessionSuccessResponse(previous)
257
+ ? previous.userStatus
258
+ : undefined;
259
+ const currentUserStatus = isSessionSuccessResponse(current)
260
+ ? current.userStatus
261
+ : undefined;
262
+
442
263
  /**
443
264
  * Emitted when the user is logged in (This happens as a result of calling
444
265
  * {@link Identity#hasSession}, so it is also emitted if the user was previously logged in)
445
266
  * @event Identity#login
446
267
  */
447
- if (current.userId) {
268
+ if (currentUserId) {
448
269
  this.emit('login', current);
449
270
  }
450
271
  /**
451
272
  * Emitted when the user logged out
452
273
  * @event Identity#logout
453
274
  */
454
- if (previous.userId && !current.userId) {
275
+ if (previousUserId && !currentUserId) {
455
276
  this.emit('logout', current);
456
277
  }
457
278
  /**
@@ -460,10 +281,10 @@ export class Identity extends TinyEmitter {
460
281
  * this invocation, and the userId has now changed
461
282
  * @event Identity#userChange
462
283
  */
463
- if (previous.userId && current.userId && previous.userId !== current.userId) {
284
+ if (previousUserId && currentUserId && previousUserId !== currentUserId) {
464
285
  this.emit('userChange', current);
465
286
  }
466
- if (previous.userId || current.userId) {
287
+ if (previousUserId || currentUserId) {
467
288
  /**
468
289
  * Emitted when the session is changed. More accurately, this event is emitted if there
469
290
  * was a logged-in user either before or after {@link Identity#hasSession} was called.
@@ -483,7 +304,7 @@ export class Identity extends TinyEmitter {
483
304
  * Emitted when the session is first created
484
305
  * @event Identity#sessionInit
485
306
  */
486
- if (current.userId && !this._sessionInitiatedSent) {
307
+ if (currentUserId && !this._sessionInitiatedSent) {
487
308
  this._sessionInitiatedSent = true;
488
309
  this.emit('sessionInit', current);
489
310
  }
@@ -492,7 +313,7 @@ export class Identity extends TinyEmitter {
492
313
  * {@link Identity#hasSession}
493
314
  * @event Identity#statusChange
494
315
  */
495
- if (previous.userStatus !== current.userStatus) {
316
+ if (previousUserStatus !== currentUserStatus) {
496
317
  this.emit('statusChange', current);
497
318
  }
498
319
  }
@@ -518,16 +339,13 @@ export class Identity extends TinyEmitter {
518
339
  * cookie expires. The default is to use the same time that hasSession responses are cached for
519
340
  * @param options.domain Override cookie domain. E.g. «vg.no» instead of «www.vg.no»
520
341
  */
521
- enableVarnishCookie(options?: { expiresIn?: number; domain?: string }): void {
522
- let expiresIn = 0;
523
- let domain: string | undefined;
524
- if (Number.isInteger(options)) {
525
- expiresIn = options as any;
526
- } else if (typeof options == 'object') {
527
- expiresIn = (options as any).expiresIn || expiresIn;
528
- domain = (options as any).domain || domain;
342
+ enableVarnishCookie(options: VarnishCookieOptions): void {
343
+ if (typeof options !== 'object') {
344
+ throw new SDKError('VarnishCookieOptions must be an object');
529
345
  }
530
346
 
347
+ const { expiresIn = 0, domain } = options;
348
+
531
349
  assert(Number.isInteger(expiresIn), `'expiresIn' must be an integer`);
532
350
  assert(expiresIn >= 0, `'expiresIn' cannot be negative`);
533
351
  this.setVarnishCookie = true;
@@ -540,8 +358,8 @@ export class Identity extends TinyEmitter {
540
358
  * @private
541
359
  * @param sessionData
542
360
  */
543
- _maybeSetVarnishCookie(sessionData: HasSessionSuccessResponse): void {
544
- if (!this.setVarnishCookie) {
361
+ _maybeSetVarnishCookie(sessionData: object): void {
362
+ if (!this.setVarnishCookie || !isSessionSuccessResponse(sessionData)) {
545
363
  return;
546
364
  }
547
365
  const date = new Date();
@@ -549,7 +367,7 @@ export class Identity extends TinyEmitter {
549
367
  this.varnishExpiresIn ||
550
368
  (typeof sessionData.expiresIn === 'number' && sessionData.expiresIn > 0);
551
369
  if (validExpires) {
552
- const expires = this.varnishExpiresIn || sessionData.expiresIn;
370
+ const expires = this.varnishExpiresIn || sessionData.expiresIn || 0;
553
371
  date.setTime(date.getTime() + expires * 1000);
554
372
  } else {
555
373
  date.setTime(0);
@@ -560,7 +378,7 @@ export class Identity extends TinyEmitter {
560
378
  this.varnishCookieDomain ||
561
379
  (typeof sessionData.baseDomain === 'string'
562
380
  ? sessionData.baseDomain
563
- : document.domain) ||
381
+ : this.window.location.hostname) ||
564
382
  '';
565
383
 
566
384
  const cookie = [
@@ -588,9 +406,11 @@ export class Identity extends TinyEmitter {
588
406
  */
589
407
  _clearVarnishCookie(): void {
590
408
  const baseDomain =
591
- this._session && typeof this._session.baseDomain === 'string'
409
+ this._session &&
410
+ isSessionSuccessResponse(this._session) &&
411
+ typeof this._session.baseDomain === 'string'
592
412
  ? this._session.baseDomain
593
- : document.domain;
413
+ : this.window.location.hostname;
594
414
 
595
415
  const domain = this.varnishCookieDomain || baseDomain || '';
596
416
 
@@ -634,21 +454,24 @@ export class Identity extends TinyEmitter {
634
454
  * @fires Identity#statusChange
635
455
  * @fires Identity#error
636
456
  */
637
- hasSession(): Promise<HasSessionSuccessResponse | HasSessionFailureResponse> {
457
+ hasSession(): Promise<SessionResponse> {
638
458
  const isSessionCallBlocked = this._isSessionCallBlocked();
459
+
639
460
  if (isSessionCallBlocked) {
640
- return this._session;
461
+ const sessionData = normalizeSessionResponse(this._session);
462
+ return Promise.resolve(isRedirectSessionPayload(sessionData) ? {} : sessionData);
641
463
  }
642
464
 
643
465
  if (this._hasSessionInProgress) {
644
- return this._hasSessionInProgress as Promise<
645
- HasSessionSuccessResponse | HasSessionFailureResponse
646
- >;
466
+ return this._hasSessionInProgress;
647
467
  }
648
468
 
649
- const _postProcess = (sessionData: any) => {
650
- if (sessionData.error) {
651
- throw new SDKError('HasSession failed', sessionData.error);
469
+ const _postProcess = (sessionData: SessionObjectResponse): SessionObjectResponse => {
470
+ if (isSessionFailureResponse(sessionData)) {
471
+ const errorObject = isObject(sessionData.error)
472
+ ? sessionData.error
473
+ : { description: String(sessionData.error) };
474
+ throw new SDKError('HasSession failed', errorObject);
652
475
  }
653
476
  this._maybeSetVarnishCookie(sessionData);
654
477
  this._emitSessionEvent(this._session, sessionData);
@@ -656,31 +479,45 @@ export class Identity extends TinyEmitter {
656
479
  return sessionData;
657
480
  };
658
481
 
659
- const _checkRedirectionNeed = (sessionData: any = {}) => {
660
- const sessionDataKeys = Object.keys(sessionData);
661
-
662
- return sessionDataKeys.length === 1 && sessionDataKeys[0] === 'redirectURL';
482
+ const _getCachedSession = (): NormalizedSessionPayload | null => {
483
+ const cachedSession = this.sessionStorageCache.get(HAS_SESSION_CACHE_KEY);
484
+ return cachedSession ? normalizeSessionResponse(cachedSession) : null;
663
485
  };
664
486
 
665
- const _getSession = async () => {
487
+ const _getSession = async (): Promise<SessionResponse> => {
666
488
  if (this._enableSessionCaching) {
667
489
  // Try to resolve from cache (it has a TTL)
668
- const cachedSession = this.sessionStorageCache.get(HAS_SESSION_CACHE_KEY);
490
+ const cachedSession = _getCachedSession();
669
491
  if (cachedSession) {
492
+ if (isRedirectSessionPayload(cachedSession)) {
493
+ return {};
494
+ }
670
495
  return _postProcess(cachedSession);
671
496
  }
672
497
  }
673
- let sessionData = null;
498
+ let sessionData: NormalizedSessionPayload | null = null;
674
499
  try {
675
500
  sessionData = await this._sessionService.get(
676
501
  this._usedSessionServiceGetSessionEndpoint,
677
502
  { tabId: this._getTabId() },
503
+ normalizeSessionResponse,
678
504
  );
679
- } catch (err: any) {
680
- if (err && err.code === 400 && this._enableSessionCaching) {
681
- const expiresIn = 1000 * (err.expiresIn || 300);
505
+ } catch (err: unknown) {
506
+ const cacheableError = isObject(err) ? err : undefined;
507
+ if (cacheableError?.code === 400 && this._enableSessionCaching) {
508
+ const errorExpiresIn =
509
+ typeof cacheableError.expiresIn === 'number'
510
+ ? cacheableError.expiresIn
511
+ : 300;
512
+ const expiresIn = 1000 * errorExpiresIn;
682
513
  this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, { error: err }, expiresIn);
683
- } else if (err && err.code >= 500 && err.code < 600 && this._enableSessionCaching) {
514
+ } else if (
515
+ cacheableError &&
516
+ typeof cacheableError.code === 'number' &&
517
+ cacheableError.code >= 500 &&
518
+ cacheableError.code < 600 &&
519
+ this._enableSessionCaching
520
+ ) {
684
521
  // Temporary fix: 30 seconds cache to limit number of calls when service is unavailable
685
522
  this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, { error: err }, 30 * 1000);
686
523
  }
@@ -689,33 +526,35 @@ export class Identity extends TinyEmitter {
689
526
 
690
527
  if (sessionData) {
691
528
  // for expiring session and safari browser do full page redirect to gain new session
692
- if (_checkRedirectionNeed(sessionData)) {
529
+ if (isRedirectSessionPayload(sessionData)) {
693
530
  this._blockSessionCall();
694
531
 
695
532
  await this.callbackBeforeRedirect();
696
533
 
697
- return this._sessionService.makeUrl(sessionData.redirectURL, {
534
+ const redirectUrl = this._sessionService.makeUrl(sessionData.redirectURL, {
698
535
  tabId: this._getTabId(),
699
536
  });
537
+ return redirectUrl;
700
538
  }
701
539
 
702
540
  if (this._enableSessionCaching) {
703
- const expiresIn = 1000 * (sessionData.expiresIn || 300);
541
+ const expiresIn =
542
+ 1000 *
543
+ (isSessionSuccessResponse(sessionData)
544
+ ? sessionData.expiresIn || 300
545
+ : 300);
704
546
  this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, sessionData, expiresIn);
705
547
  }
706
548
  }
707
549
 
708
- return _postProcess(sessionData);
550
+ return _postProcess(sessionData ?? {});
709
551
  };
710
552
  this._hasSessionInProgress = _getSession().then(
711
553
  (sessionData) => {
712
554
  this._hasSessionInProgress = false;
713
-
714
- if (isUrl(sessionData)) {
555
+ if (typeof sessionData === 'string') {
715
556
  this.window.location.href = sessionData;
716
- return sessionData;
717
557
  }
718
-
719
558
  return sessionData;
720
559
  },
721
560
  (err) => {
@@ -725,9 +564,7 @@ export class Identity extends TinyEmitter {
725
564
  },
726
565
  );
727
566
 
728
- return this._hasSessionInProgress as Promise<
729
- HasSessionSuccessResponse | HasSessionFailureResponse
730
- >;
567
+ return this._hasSessionInProgress;
731
568
  }
732
569
 
733
570
  /**
@@ -738,7 +575,7 @@ export class Identity extends TinyEmitter {
738
575
  async isLoggedIn(): Promise<boolean> {
739
576
  try {
740
577
  const data = await this.hasSession();
741
- return 'result' in data;
578
+ return isSessionSuccessResponse(data);
742
579
  } catch (_) {
743
580
  return false;
744
581
  }
@@ -761,10 +598,7 @@ export class Identity extends TinyEmitter {
761
598
  */
762
599
  async isConnected(): Promise<boolean> {
763
600
  try {
764
- const data = (await this.hasSession()) as any;
765
- // if data is not an object, the promise will fail.
766
- // if the result is present, it's boolean. But if it's not, it should be assumed false.
767
- return !!data.result;
601
+ return isConnectedSessionResponse(await this.hasSession());
768
602
  } catch (_) {
769
603
  return false;
770
604
  }
@@ -777,9 +611,9 @@ export class Identity extends TinyEmitter {
777
611
  * @throws {SDKError} If the user isn't connected to the merchant
778
612
  * @throws {SDKError} If we couldn't get the user
779
613
  */
780
- async getUser(): Promise<HasSessionSuccessResponse> {
781
- const user = (await this.hasSession()) as any;
782
- if (!user.result) {
614
+ async getUser(): Promise<ConnectedSessionResponse> {
615
+ const user = await this.hasSession();
616
+ if (!isConnectedSessionResponse(user)) {
783
617
  throw new SDKError('The user is not connected to this merchant');
784
618
  }
785
619
  return cloneDeep(user);
@@ -800,9 +634,9 @@ export class Identity extends TinyEmitter {
800
634
  * @return The `userId` field (not to be confused with the `uuid`)
801
635
  */
802
636
  async getUserId(): Promise<string> {
803
- const user = (await this.hasSession()) as any;
804
- if (user.userId && user.result) {
805
- return user.userId;
637
+ const user = await this.hasSession();
638
+ if (isConnectedSessionWithUserIdResponse(user)) {
639
+ return String(user.userId);
806
640
  }
807
641
  throw new SDKError('The user is not connected to this merchant');
808
642
  }
@@ -829,30 +663,35 @@ export class Identity extends TinyEmitter {
829
663
  * @return The merchant- and 3rd-party-specific `externalId`
830
664
  */
831
665
  async getExternalId(externalParty: string, optionalSuffix: string = ''): Promise<string> {
832
- const { pairId } = (await this.hasSession()) as any;
666
+ const user = await this.hasSession();
667
+ const pairId = isSessionSuccessResponse(user) ? user.pairId : undefined;
833
668
 
834
669
  if (!pairId) throw new SDKError('pairId missing in user session!');
835
670
 
836
671
  if (!externalParty || externalParty.length === 0) {
837
672
  throw new SDKError('externalParty cannot be empty');
838
673
  }
839
- const _toHexDigest = (hashBuffer: any) => {
674
+ const _toHexDigest = (hashBuffer: ArrayBuffer) => {
840
675
  // convert buffer to byte array
841
676
  const hashArray = Array.from(new Uint8Array(hashBuffer));
842
677
  // convert bytes to hex string
843
678
  return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
844
679
  };
845
680
 
846
- const _getSha256Digest = (data: any) => {
681
+ const _getSha256Digest = (data: BufferSource) => {
847
682
  return crypto.subtle.digest('SHA-256', data);
848
683
  };
849
684
 
850
- const _hashMessage = async (message: any) => {
685
+ const _hashMessage = async (message: string) => {
851
686
  const msgUint8 = new TextEncoder().encode(message);
852
- return _getSha256Digest(msgUint8).then((it: any) => _toHexDigest(it));
687
+ return _getSha256Digest(msgUint8).then((it) => _toHexDigest(it));
853
688
  };
854
689
 
855
- const _constructMessage = (pairId: any, externalParty: any, optionalSuffix: any) => {
690
+ const _constructMessage = (
691
+ pairId: string,
692
+ externalParty: string,
693
+ optionalSuffix: string,
694
+ ) => {
856
695
  return optionalSuffix
857
696
  ? `${pairId}:${externalParty}:${optionalSuffix}`
858
697
  : `${pairId}:${externalParty}`;
@@ -868,9 +707,9 @@ export class Identity extends TinyEmitter {
868
707
  * @throws {SDKError} If the SDRN is missing in user session object.
869
708
  */
870
709
  async getUserSDRN(): Promise<string> {
871
- const { sdrn } = (await this.hasSession()) as any;
872
- if (sdrn) {
873
- return sdrn;
710
+ const user = await this.hasSession();
711
+ if (isSessionSuccessResponse(user) && user.sdrn) {
712
+ return user.sdrn;
874
713
  }
875
714
  throw new SDKError('Failed to get SDRN from user session');
876
715
  }
@@ -888,8 +727,8 @@ export class Identity extends TinyEmitter {
888
727
  * @return The `uuid` field (not to be confused with the `userId`)
889
728
  */
890
729
  async getUserUuid(): Promise<string> {
891
- const user = (await this.hasSession()) as any;
892
- if (user.uuid && user.result) {
730
+ const user = await this.hasSession();
731
+ if (isSessionSuccessResponse(user) && user.uuid && user.result) {
893
732
  return user.uuid;
894
733
  }
895
734
  throw new SDKError('The user is not connected to this merchant');
@@ -905,7 +744,16 @@ export class Identity extends TinyEmitter {
905
744
  */
906
745
  async getUserContextData(): Promise<SimplifiedLoginData | null> {
907
746
  try {
908
- return await this._globalSessionService.get('user-context');
747
+ return await this._globalSessionService.get('user-context', undefined, (value) =>
748
+ isObject(value) && isStr(value.identifier)
749
+ ? {
750
+ identifier: value.identifier,
751
+ display_text: isStr(value.display_text) ? value.display_text : '',
752
+ client_name: isStr(value.client_name) ? value.client_name : '',
753
+ provider_id: isStr(value.provider_id) ? value.provider_id : undefined,
754
+ }
755
+ : null,
756
+ );
909
757
  } catch (_) {
910
758
  return null;
911
759
  }
@@ -938,7 +786,7 @@ export class Identity extends TinyEmitter {
938
786
  */
939
787
  login({
940
788
  state,
941
- acrValues = '',
789
+ acrValues,
942
790
  scope = 'openid',
943
791
  redirectUri = this.redirectUri,
944
792
  preferPopup = false,
@@ -946,7 +794,7 @@ export class Identity extends TinyEmitter {
946
794
  tag = '',
947
795
  teaser = '',
948
796
  maxAge = '',
949
- locale = '',
797
+ locale,
950
798
  oneStepLogin = false,
951
799
  prompt = 'select_account',
952
800
  xDomainId = '',
@@ -993,8 +841,12 @@ export class Identity extends TinyEmitter {
993
841
  */
994
842
  async getSpId(): Promise<string | null> {
995
843
  try {
996
- const user = (await this.hasSession()) as any;
997
- return user.sp_id || null;
844
+ const user = await this.hasSession();
845
+ if (isSessionSuccessResponse(user)) {
846
+ return user.sp_id || null;
847
+ }
848
+ const spId = isObject(user) && 'sp_id' in user ? user.sp_id : undefined;
849
+ return isStr(spId) ? spId : null;
998
850
  } catch (_) {
999
851
  return null;
1000
852
  }
@@ -1031,37 +883,23 @@ export class Identity extends TinyEmitter {
1031
883
  * @param options.originCampaign
1032
884
  * @return - The url
1033
885
  */
1034
- loginUrl(
1035
- {
1036
- state,
1037
- acrValues = '',
1038
- scope = 'openid',
1039
- redirectUri = this.redirectUri,
1040
- loginHint = '',
1041
- tag = '',
1042
- teaser = '',
1043
- maxAge = '',
1044
- locale = '',
1045
- oneStepLogin = false,
1046
- prompt = 'select_account',
1047
- xDomainId = '',
1048
- xEnvironmentId = '',
1049
- originCampaign = '',
1050
- }: LoginOptions,
1051
- ..._args: any[]
1052
- ): string {
1053
- if (typeof arguments[0] !== 'object') {
1054
- // backward compatibility
1055
- state = arguments[0];
1056
- acrValues = arguments[1];
1057
- scope = arguments[2] || scope;
1058
- redirectUri = arguments[3] || redirectUri;
1059
- loginHint = arguments[4] || loginHint;
1060
- tag = arguments[5] || tag;
1061
- teaser = arguments[6] || teaser;
1062
- maxAge = isNaN(arguments[7]) ? maxAge : arguments[7];
1063
- }
1064
- const isValidAcrValue = (acrValue: any) =>
886
+ loginUrl({
887
+ state,
888
+ acrValues = '',
889
+ scope = 'openid',
890
+ redirectUri = this.redirectUri,
891
+ loginHint = '',
892
+ tag = '',
893
+ teaser = '',
894
+ maxAge = '',
895
+ locale,
896
+ oneStepLogin = false,
897
+ prompt = 'select_account',
898
+ xDomainId = '',
899
+ xEnvironmentId = '',
900
+ originCampaign = '',
901
+ }: LoginOptions): string {
902
+ const isValidAcrValue = (acrValue: string) =>
1065
903
  isStrIn(
1066
904
  acrValue,
1067
905
  ['password', 'otp', 'sms', 'eid-dk', 'eid-no', 'eid-se', 'eid-fi', 'eid'],
@@ -1074,7 +912,7 @@ export class Identity extends TinyEmitter {
1074
912
  `The acrValues parameter is not acceptable: ${acrValues}`,
1075
913
  );
1076
914
  assert(
1077
- isUrl(redirectUri as string),
915
+ isUrl(redirectUri),
1078
916
  `loginUrl(): redirectUri must be a valid url but is ${redirectUri}`,
1079
917
  );
1080
918
  assert(
@@ -1144,12 +982,13 @@ export class Identity extends TinyEmitter {
1144
982
  * @param options - additional configuration of Simplified Login Widget
1145
983
  * @fires Identity#simplifiedLoginOpened
1146
984
  * @fires Identity#simplifiedLoginCancelled
1147
- * @return - will resolve to true if widget will be display. Otherwise, will throw SDKError
985
+ * @throws {SDKError} - If user context data is missing or the widget script cannot be loaded
986
+ * @return - will resolve to true if widget will be displayed
1148
987
  */
1149
988
  async showSimplifiedLoginWidget(
1150
989
  loginParams: SimplifiedLoginWidgetLoginOptions,
1151
990
  options?: SimplifiedLoginWidgetOptions,
1152
- ): Promise<boolean | SDKError> {
991
+ ): Promise<boolean> {
1153
992
  // getUserContextData doesn't throw exception
1154
993
  const userData = await this.getUserContextData();
1155
994
 
@@ -1159,25 +998,32 @@ export class Identity extends TinyEmitter {
1159
998
  }
1160
999
  const widgetUrl = this._bffService.makeUrl('simplified-login-widget', queryParams, false);
1161
1000
 
1162
- const prepareLoginParams = async (loginPrams: any) => {
1163
- if (typeof loginPrams.state === 'function') {
1164
- loginPrams.state = await loginPrams.state();
1165
- }
1166
-
1167
- return loginPrams;
1001
+ const prepareLoginParams = async (
1002
+ loginParams: SimplifiedLoginWidgetLoginOptions,
1003
+ ): Promise<LoginOptions> => {
1004
+ const state =
1005
+ typeof loginParams.state === 'function'
1006
+ ? await loginParams.state()
1007
+ : loginParams.state;
1008
+
1009
+ return {
1010
+ ...loginParams,
1011
+ state,
1012
+ };
1168
1013
  };
1169
1014
 
1170
- return new Promise((resolve, reject) => {
1171
- if (!userData || !userData.display_text || !userData.identifier) {
1172
- return reject(new SDKError('Missing user data'));
1015
+ return new Promise<boolean>((resolve, reject) => {
1016
+ if (!userData?.display_text || !userData.identifier) {
1017
+ reject(new SDKError('Missing user data'));
1018
+ return;
1173
1019
  }
1174
1020
 
1175
- const initialParams: Record<string, any> = {
1021
+ const initialParams: SimplifiedLoginWidgetInitialParams = {
1176
1022
  displayText: userData.display_text,
1177
1023
  env: this.env,
1178
- clientName: userData.client_name,
1024
+ clientName: userData.client_name || '',
1179
1025
  clientId: this.clientId,
1180
- providerId: (userData as any).provider_id,
1026
+ providerId: userData.provider_id,
1181
1027
  windowWidth: () => window.innerWidth,
1182
1028
  windowOnResize: (f: () => void) => {
1183
1029
  window.onresize = f;
@@ -1214,15 +1060,11 @@ export class Identity extends TinyEmitter {
1214
1060
  };
1215
1061
 
1216
1062
  const cancelLoginHandler = () => {
1217
- /**
1218
- * Emitted when the user closes the simplified login widget
1219
- * @event Identity#simplifiedLoginCancelled
1220
- */
1221
1063
  this.emit('simplifiedLoginCancelled');
1222
1064
  };
1223
1065
 
1224
- if ((window as any).openSimplifiedLoginWidget) {
1225
- (window as any).openSimplifiedLoginWidget(
1066
+ if (hasOpenSimplifiedLoginWidget(window)) {
1067
+ window.openSimplifiedLoginWidget(
1226
1068
  initialParams,
1227
1069
  loginHandler,
1228
1070
  loginNotYouHandler,
@@ -1236,7 +1078,11 @@ export class Identity extends TinyEmitter {
1236
1078
  simplifiedLoginWidget.type = 'text/javascript';
1237
1079
  simplifiedLoginWidget.src = widgetUrl;
1238
1080
  simplifiedLoginWidget.onload = () => {
1239
- (window as any).openSimplifiedLoginWidget(
1081
+ if (!hasOpenSimplifiedLoginWidget(window)) {
1082
+ reject(new SDKError('Error when loading simplified login widget content'));
1083
+ return;
1084
+ }
1085
+ window.openSimplifiedLoginWidget(
1240
1086
  initialParams,
1241
1087
  loginHandler,
1242
1088
  loginNotYouHandler,