@schibsted/account-sdk-browser 6.0.0-alpha.5 → 6.0.0-alpha.7
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/README.md +278 -224
- package/dist/account-sdk.d.ts +18 -0
- package/dist/account.d.ts +139 -3
- package/dist/identity.d.ts +38 -40
- package/dist/index.d.ts +1 -1
- package/dist/index.js +225 -242
- package/dist/index.js.map +1 -1
- package/dist/monetization.d.ts +4 -10
- package/dist/sdk-error.d.ts +2 -0
- package/dist/types/account.d.ts +30 -8
- package/dist/types/common.d.ts +7 -1
- package/dist/types/login.d.ts +16 -11
- package/dist/types/monetization.d.ts +6 -1
- package/dist/types/session.d.ts +28 -3
- package/dist/version.d.ts +1 -1
- package/package.json +1 -4
- package/src/account-sdk.ts +18 -0
- package/src/account.ts +155 -5
- package/src/identity.ts +125 -110
- package/src/index.ts +1 -1
- package/src/monetization.ts +12 -24
- package/src/sdk-error.ts +2 -0
- package/src/types/account.ts +35 -13
- package/src/types/common.ts +7 -1
- package/src/types/login.ts +16 -11
- package/src/types/monetization.ts +6 -1
- package/src/types/session.ts +28 -3
- package/src/version.ts +1 -1
package/src/identity.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* See LICENSE.md in the project root.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { TinyEmitter } from 'tiny-emitter';
|
|
6
5
|
import Cache from './cache.js';
|
|
7
6
|
import { ENDPOINTS } from './config.js';
|
|
8
7
|
import { cloneDeep } from './object.js';
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
normalizeSessionResponse,
|
|
20
19
|
} from './session-response.js';
|
|
21
20
|
import * as spidTalk from './spid-talk.js';
|
|
21
|
+
import type { AccountEventName } from './types/account.js';
|
|
22
22
|
import type { LogFunction } from './types/common.js';
|
|
23
23
|
import type {
|
|
24
24
|
IdentityBeforeRedirectCallback,
|
|
@@ -63,33 +63,44 @@ const globalWindow = () => window;
|
|
|
63
63
|
/**
|
|
64
64
|
* Provides Identity functionalty to a web page
|
|
65
65
|
*/
|
|
66
|
-
export class Identity
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
export class Identity {
|
|
67
|
+
// Event bus
|
|
68
|
+
private readonly eventTarget = new EventTarget();
|
|
69
|
+
|
|
70
|
+
// Configuration
|
|
71
|
+
private readonly window: Window;
|
|
72
|
+
private readonly clientId: string;
|
|
73
|
+
private readonly redirectUri: string;
|
|
74
|
+
private readonly env: string;
|
|
75
|
+
private readonly log?: LogFunction;
|
|
76
|
+
private readonly callbackBeforeRedirect: IdentityBeforeRedirectCallback;
|
|
77
|
+
|
|
78
|
+
// Caches
|
|
79
|
+
private readonly sessionStorageCache: Cache;
|
|
80
|
+
private readonly localStorageCache: Cache;
|
|
81
|
+
private enableSessionCaching: boolean;
|
|
82
|
+
|
|
83
|
+
// REST clients
|
|
84
|
+
private readonly spid: RESTClient;
|
|
85
|
+
private readonly oauthService: RESTClient;
|
|
86
|
+
private readonly bffService: RESTClient;
|
|
87
|
+
private readonly sessionService: RESTClient;
|
|
88
|
+
private readonly globalSessionService: RESTClient;
|
|
89
|
+
private readonly usedSessionServiceGetSessionEndpoint: 'v2/session' | 'session';
|
|
90
|
+
|
|
91
|
+
// Session state
|
|
77
92
|
/**
|
|
78
93
|
* @internal Starts as `{}` before the first `hasSession()` call, then stores the latest
|
|
79
94
|
* object response from hasSession().
|
|
80
95
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
popup?: Window | null;
|
|
90
|
-
setVarnishCookie?: boolean;
|
|
91
|
-
varnishExpiresIn?: number;
|
|
92
|
-
varnishCookieDomain?: string;
|
|
96
|
+
private session: SessionObjectResponse;
|
|
97
|
+
private hasSessionInProgress?: false | Promise<SessionResponse>;
|
|
98
|
+
|
|
99
|
+
// Popup & Varnish cookie state
|
|
100
|
+
private popup?: Window | null;
|
|
101
|
+
private setVarnishCookie?: boolean;
|
|
102
|
+
private varnishExpiresIn?: number;
|
|
103
|
+
private varnishCookieDomain?: string;
|
|
93
104
|
|
|
94
105
|
/**
|
|
95
106
|
* @param options
|
|
@@ -112,7 +123,6 @@ export class Identity extends TinyEmitter {
|
|
|
112
123
|
window = globalWindow(),
|
|
113
124
|
callbackBeforeRedirect = () => {},
|
|
114
125
|
}: IdentityOptions) {
|
|
115
|
-
super();
|
|
116
126
|
assert(isNonEmptyString(clientId), 'clientId parameter is required');
|
|
117
127
|
assert(isObject(window), 'The reference to window is missing');
|
|
118
128
|
assert(!redirectUri || isUrl(redirectUri), 'redirectUri parameter is invalid');
|
|
@@ -125,67 +135,72 @@ export class Identity extends TinyEmitter {
|
|
|
125
135
|
spidTalk.emulate(window);
|
|
126
136
|
this.window = window;
|
|
127
137
|
this.clientId = clientId;
|
|
128
|
-
this.sessionStorageCache = new Cache(() => this.window && this.window.sessionStorage);
|
|
129
|
-
this.localStorageCache = new Cache(() => this.window && this.window.localStorage);
|
|
130
138
|
this.redirectUri = redirectUri;
|
|
131
139
|
this.env = env;
|
|
132
140
|
this.log = log;
|
|
133
141
|
this.callbackBeforeRedirect = callbackBeforeRedirect;
|
|
134
|
-
this._sessionDomain = sessionDomain;
|
|
135
142
|
|
|
143
|
+
this.sessionStorageCache = new Cache(() => this.window && this.window.sessionStorage);
|
|
144
|
+
this.localStorageCache = new Cache(() => this.window && this.window.localStorage);
|
|
136
145
|
// Internal hack: set to false to always refresh from hassession
|
|
137
|
-
this.
|
|
146
|
+
this.enableSessionCaching = true;
|
|
138
147
|
|
|
139
|
-
|
|
140
|
-
this._session = {};
|
|
148
|
+
this.session = {};
|
|
141
149
|
|
|
142
150
|
const accountClientParams = { client_id: this.clientId, redirect_uri: this.redirectUri };
|
|
143
151
|
const clientSdrn = buildClientSdrn(this.env, this.clientId);
|
|
144
152
|
|
|
145
|
-
this.
|
|
146
|
-
serverUrl:
|
|
153
|
+
this.spid = createAccountRestClient({
|
|
154
|
+
serverUrl: urlMapper(env, ENDPOINTS.SPiD),
|
|
147
155
|
log: this.log,
|
|
148
|
-
defaultParams:
|
|
149
|
-
client_sdrn: clientSdrn,
|
|
150
|
-
redirect_uri: this.redirectUri,
|
|
151
|
-
sdk_version: version,
|
|
152
|
-
},
|
|
156
|
+
defaultParams: accountClientParams,
|
|
153
157
|
});
|
|
154
|
-
this.
|
|
155
|
-
this._sessionService.url.pathname && this._sessionService.url.pathname.length <= 1
|
|
156
|
-
? 'v2/session'
|
|
157
|
-
: 'session';
|
|
158
|
-
|
|
159
|
-
this._spid = createAccountRestClient({
|
|
158
|
+
this.oauthService = createAccountRestClient({
|
|
160
159
|
serverUrl: urlMapper(env, ENDPOINTS.SPiD),
|
|
161
160
|
log: this.log,
|
|
162
161
|
defaultParams: accountClientParams,
|
|
163
162
|
});
|
|
164
|
-
this.
|
|
163
|
+
this.bffService = createAccountRestClient({
|
|
165
164
|
serverUrl: urlMapper(env, ENDPOINTS.BFF),
|
|
166
165
|
log: this.log,
|
|
167
166
|
defaultParams: accountClientParams,
|
|
168
167
|
});
|
|
169
|
-
this.
|
|
170
|
-
serverUrl:
|
|
168
|
+
this.sessionService = createAccountRestClient({
|
|
169
|
+
serverUrl: sessionDomain,
|
|
171
170
|
log: this.log,
|
|
172
|
-
defaultParams:
|
|
171
|
+
defaultParams: {
|
|
172
|
+
client_sdrn: clientSdrn,
|
|
173
|
+
redirect_uri: this.redirectUri,
|
|
174
|
+
sdk_version: version,
|
|
175
|
+
},
|
|
173
176
|
});
|
|
174
|
-
this.
|
|
177
|
+
this.globalSessionService = createAccountRestClient({
|
|
175
178
|
serverUrl: urlMapper(env, ENDPOINTS.SESSION_SERVICE),
|
|
176
179
|
log: this.log,
|
|
177
180
|
defaultParams: { client_sdrn: clientSdrn, sdk_version: version },
|
|
178
181
|
});
|
|
182
|
+
this.usedSessionServiceGetSessionEndpoint =
|
|
183
|
+
this.sessionService.url.pathname && this.sessionService.url.pathname.length <= 1
|
|
184
|
+
? 'v2/session'
|
|
185
|
+
: 'session';
|
|
179
186
|
|
|
180
187
|
this._unblockSessionCall();
|
|
181
188
|
}
|
|
182
189
|
|
|
190
|
+
addEventListener(type: AccountEventName, listener: EventListener): void {
|
|
191
|
+
this.eventTarget.addEventListener(type, listener);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
removeEventListener(type: AccountEventName, listener: EventListener): void {
|
|
195
|
+
this.eventTarget.removeEventListener(type, listener);
|
|
196
|
+
}
|
|
197
|
+
|
|
183
198
|
/**
|
|
184
199
|
* Read tabId from session storage
|
|
185
200
|
* @private
|
|
186
201
|
*/
|
|
187
202
|
_getTabId(): number | undefined {
|
|
188
|
-
if (this.
|
|
203
|
+
if (this.enableSessionCaching) {
|
|
189
204
|
const tabId = this.sessionStorageCache.get(TAB_ID_KEY);
|
|
190
205
|
if (typeof tabId !== 'number') {
|
|
191
206
|
this.sessionStorageCache.set(TAB_ID_KEY, TAB_ID, TAB_ID_TTL);
|
|
@@ -229,33 +244,6 @@ export class Identity extends TinyEmitter {
|
|
|
229
244
|
this.localStorageCache.delete(SESSION_CALL_BLOCKED_CACHE_KEY);
|
|
230
245
|
}
|
|
231
246
|
|
|
232
|
-
/**
|
|
233
|
-
* Emits the relevant events based on the previous and new reply from hasSession
|
|
234
|
-
* @private
|
|
235
|
-
* @param previous
|
|
236
|
-
* @param current
|
|
237
|
-
*/
|
|
238
|
-
_emitSessionEvent(previous: object, current: object): void {
|
|
239
|
-
const previousUserId = isSessionSuccessResponse(previous) ? previous.userId : undefined;
|
|
240
|
-
const currentUserId = isSessionSuccessResponse(current) ? current.userId : undefined;
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Emitted when the user is logged in (This happens as a result of calling
|
|
244
|
-
* {@link Identity#hasSession}, so it is also emitted if the user was previously logged in)
|
|
245
|
-
* @event Identity#login
|
|
246
|
-
*/
|
|
247
|
-
if (currentUserId) {
|
|
248
|
-
this.emit('login', current);
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Emitted when the user logged out
|
|
252
|
-
* @event Identity#logout
|
|
253
|
-
*/
|
|
254
|
-
if (previousUserId && !currentUserId) {
|
|
255
|
-
this.emit('logout', current);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
247
|
/**
|
|
260
248
|
* Close this.popup if it exists and is open
|
|
261
249
|
* @private
|
|
@@ -344,10 +332,10 @@ export class Identity extends TinyEmitter {
|
|
|
344
332
|
*/
|
|
345
333
|
_clearVarnishCookie(): void {
|
|
346
334
|
const baseDomain =
|
|
347
|
-
this.
|
|
348
|
-
isSessionSuccessResponse(this.
|
|
349
|
-
typeof this.
|
|
350
|
-
? this.
|
|
335
|
+
this.session &&
|
|
336
|
+
isSessionSuccessResponse(this.session) &&
|
|
337
|
+
typeof this.session.baseDomain === 'string'
|
|
338
|
+
? this.session.baseDomain
|
|
351
339
|
: this.window.location.hostname;
|
|
352
340
|
|
|
353
341
|
const domain = this.varnishCookieDomain || baseDomain || '';
|
|
@@ -361,19 +349,17 @@ export class Identity extends TinyEmitter {
|
|
|
361
349
|
* determines the status of the user.
|
|
362
350
|
* @throws {SDKError} - If the call to the hasSession service fails in any way (this will happen
|
|
363
351
|
* if, say, the user is not logged in)
|
|
364
|
-
* @fires Identity#login
|
|
365
|
-
* @fires Identity#logout
|
|
366
352
|
*/
|
|
367
353
|
hasSession(): Promise<SessionResponse> {
|
|
368
354
|
const isSessionCallBlocked = this._isSessionCallBlocked();
|
|
369
355
|
|
|
370
356
|
if (isSessionCallBlocked) {
|
|
371
|
-
const sessionData = normalizeSessionResponse(this.
|
|
357
|
+
const sessionData = normalizeSessionResponse(this.session);
|
|
372
358
|
return Promise.resolve(isRedirectSessionPayload(sessionData) ? {} : sessionData);
|
|
373
359
|
}
|
|
374
360
|
|
|
375
|
-
if (this.
|
|
376
|
-
return this.
|
|
361
|
+
if (this.hasSessionInProgress) {
|
|
362
|
+
return this.hasSessionInProgress;
|
|
377
363
|
}
|
|
378
364
|
|
|
379
365
|
const _postProcess = (sessionData: SessionObjectResponse): SessionObjectResponse => {
|
|
@@ -384,8 +370,7 @@ export class Identity extends TinyEmitter {
|
|
|
384
370
|
throw new SDKError('HasSession failed', errorObject);
|
|
385
371
|
}
|
|
386
372
|
this._maybeSetVarnishCookie(sessionData);
|
|
387
|
-
this.
|
|
388
|
-
this._session = sessionData;
|
|
373
|
+
this.session = sessionData;
|
|
389
374
|
return sessionData;
|
|
390
375
|
};
|
|
391
376
|
|
|
@@ -395,7 +380,7 @@ export class Identity extends TinyEmitter {
|
|
|
395
380
|
};
|
|
396
381
|
|
|
397
382
|
const _getSession = async (): Promise<SessionResponse> => {
|
|
398
|
-
if (this.
|
|
383
|
+
if (this.enableSessionCaching) {
|
|
399
384
|
// Try to resolve from cache (it has a TTL)
|
|
400
385
|
const cachedSession = _getCachedSession();
|
|
401
386
|
if (cachedSession) {
|
|
@@ -407,14 +392,14 @@ export class Identity extends TinyEmitter {
|
|
|
407
392
|
}
|
|
408
393
|
let sessionData: NormalizedSessionPayload | null = null;
|
|
409
394
|
try {
|
|
410
|
-
sessionData = await this.
|
|
411
|
-
this.
|
|
395
|
+
sessionData = await this.sessionService.get(
|
|
396
|
+
this.usedSessionServiceGetSessionEndpoint,
|
|
412
397
|
{ tabId: this._getTabId() },
|
|
413
398
|
normalizeSessionResponse,
|
|
414
399
|
);
|
|
415
400
|
} catch (err: unknown) {
|
|
416
401
|
const cacheableError = isObject(err) ? err : undefined;
|
|
417
|
-
if (cacheableError?.code === 400 && this.
|
|
402
|
+
if (cacheableError?.code === 400 && this.enableSessionCaching) {
|
|
418
403
|
const errorExpiresIn =
|
|
419
404
|
typeof cacheableError.expiresIn === 'number'
|
|
420
405
|
? cacheableError.expiresIn
|
|
@@ -426,7 +411,7 @@ export class Identity extends TinyEmitter {
|
|
|
426
411
|
typeof cacheableError.code === 'number' &&
|
|
427
412
|
cacheableError.code >= 500 &&
|
|
428
413
|
cacheableError.code < 600 &&
|
|
429
|
-
this.
|
|
414
|
+
this.enableSessionCaching
|
|
430
415
|
) {
|
|
431
416
|
// Temporary fix: 30 seconds cache to limit number of calls when service is unavailable
|
|
432
417
|
this.sessionStorageCache.set(HAS_SESSION_CACHE_KEY, { error: err }, 30 * 1000);
|
|
@@ -441,13 +426,13 @@ export class Identity extends TinyEmitter {
|
|
|
441
426
|
|
|
442
427
|
await this.callbackBeforeRedirect();
|
|
443
428
|
|
|
444
|
-
const redirectUrl = this.
|
|
429
|
+
const redirectUrl = this.sessionService.makeUrl(sessionData.redirectURL, {
|
|
445
430
|
tabId: this._getTabId(),
|
|
446
431
|
});
|
|
447
432
|
return redirectUrl;
|
|
448
433
|
}
|
|
449
434
|
|
|
450
|
-
if (this.
|
|
435
|
+
if (this.enableSessionCaching) {
|
|
451
436
|
const expiresIn =
|
|
452
437
|
1000 *
|
|
453
438
|
(isSessionSuccessResponse(sessionData)
|
|
@@ -459,21 +444,21 @@ export class Identity extends TinyEmitter {
|
|
|
459
444
|
|
|
460
445
|
return _postProcess(sessionData ?? {});
|
|
461
446
|
};
|
|
462
|
-
this.
|
|
447
|
+
this.hasSessionInProgress = _getSession().then(
|
|
463
448
|
(sessionData) => {
|
|
464
|
-
this.
|
|
449
|
+
this.hasSessionInProgress = false;
|
|
465
450
|
if (typeof sessionData === 'string') {
|
|
466
451
|
this.window.location.href = sessionData;
|
|
467
452
|
}
|
|
468
453
|
return sessionData;
|
|
469
454
|
},
|
|
470
455
|
(err) => {
|
|
471
|
-
this.
|
|
456
|
+
this.hasSessionInProgress = false;
|
|
472
457
|
throw new SDKError('HasSession failed', err);
|
|
473
458
|
},
|
|
474
459
|
);
|
|
475
460
|
|
|
476
|
-
return this.
|
|
461
|
+
return this.hasSessionInProgress;
|
|
477
462
|
}
|
|
478
463
|
|
|
479
464
|
/**
|
|
@@ -490,6 +475,16 @@ export class Identity extends TinyEmitter {
|
|
|
490
475
|
}
|
|
491
476
|
}
|
|
492
477
|
|
|
478
|
+
/**
|
|
479
|
+
* Enables or disables caching for subsequent session lookups.
|
|
480
|
+
*
|
|
481
|
+
* Changing this setting does not remove an existing cached session. Use
|
|
482
|
+
* {@link Identity#clearCachedUserSession} to clear the cache explicitly.
|
|
483
|
+
*/
|
|
484
|
+
setSessionCachingEnabled(enabled: boolean): void {
|
|
485
|
+
this.enableSessionCaching = enabled;
|
|
486
|
+
}
|
|
487
|
+
|
|
493
488
|
/**
|
|
494
489
|
* Removes the cached user session.
|
|
495
490
|
*/
|
|
@@ -592,7 +587,7 @@ export class Identity extends TinyEmitter {
|
|
|
592
587
|
*/
|
|
593
588
|
async getUserContextData(): Promise<SimplifiedLoginData | null> {
|
|
594
589
|
try {
|
|
595
|
-
return await this.
|
|
590
|
+
return await this.globalSessionService.get('user-context', undefined, (value) =>
|
|
596
591
|
isObject(value) && isStr(value.identifier)
|
|
597
592
|
? {
|
|
598
593
|
identifier: value.identifier,
|
|
@@ -630,6 +625,7 @@ export class Identity extends TinyEmitter {
|
|
|
630
625
|
* @param options.xDomainId
|
|
631
626
|
* @param options.xEnvironmentId
|
|
632
627
|
* @param options.originCampaign
|
|
628
|
+
* @fires Identity#login
|
|
633
629
|
* @return - Reference to popup window if created (or `null` otherwise)
|
|
634
630
|
*/
|
|
635
631
|
login({
|
|
@@ -673,11 +669,25 @@ export class Identity extends TinyEmitter {
|
|
|
673
669
|
width: 360,
|
|
674
670
|
height: 570,
|
|
675
671
|
});
|
|
672
|
+
|
|
676
673
|
if (this.popup) {
|
|
674
|
+
this.eventTarget.dispatchEvent(
|
|
675
|
+
new CustomEvent('login', {
|
|
676
|
+
detail: { url, method: 'popup' },
|
|
677
|
+
}),
|
|
678
|
+
);
|
|
679
|
+
|
|
677
680
|
return this.popup;
|
|
678
681
|
}
|
|
679
682
|
}
|
|
683
|
+
|
|
684
|
+
this.eventTarget.dispatchEvent(
|
|
685
|
+
new CustomEvent('login', {
|
|
686
|
+
detail: { url, method: 'default' },
|
|
687
|
+
}),
|
|
688
|
+
);
|
|
680
689
|
this.window.location.href = url;
|
|
690
|
+
|
|
681
691
|
return null;
|
|
682
692
|
}
|
|
683
693
|
|
|
@@ -702,14 +712,19 @@ export class Identity extends TinyEmitter {
|
|
|
702
712
|
|
|
703
713
|
/**
|
|
704
714
|
* @summary Logs the user out from the Identity platform
|
|
705
|
-
* @param redirectUri - Where to redirect the browser after logging out of Schibsted
|
|
706
|
-
*
|
|
715
|
+
* @param redirectUri - Where to redirect the browser after logging out of Schibsted account
|
|
716
|
+
* @fires Identity#logout
|
|
707
717
|
*/
|
|
708
718
|
logout(redirectUri: string = this.redirectUri): void {
|
|
709
719
|
this.sessionStorageCache.delete(HAS_SESSION_CACHE_KEY);
|
|
710
720
|
this._maybeClearVarnishCookie();
|
|
711
|
-
this.
|
|
712
|
-
this.
|
|
721
|
+
const url = this.logoutUrl(redirectUri);
|
|
722
|
+
this.eventTarget.dispatchEvent(
|
|
723
|
+
new CustomEvent('logout', {
|
|
724
|
+
detail: { url },
|
|
725
|
+
}),
|
|
726
|
+
);
|
|
727
|
+
this.window.location.href = url;
|
|
713
728
|
}
|
|
714
729
|
|
|
715
730
|
/**
|
|
@@ -768,7 +783,7 @@ export class Identity extends TinyEmitter {
|
|
|
768
783
|
`the state parameter should be a non empty string but it is ${state}`,
|
|
769
784
|
);
|
|
770
785
|
|
|
771
|
-
return this.
|
|
786
|
+
return this.oauthService.makeUrl('oauth/authorize', {
|
|
772
787
|
response_type: 'code',
|
|
773
788
|
redirect_uri: redirectUri,
|
|
774
789
|
scope,
|
|
@@ -795,7 +810,7 @@ export class Identity extends TinyEmitter {
|
|
|
795
810
|
logoutUrl(redirectUri: string = this.redirectUri): string {
|
|
796
811
|
assert(isUrl(redirectUri), `logoutUrl(): redirectUri is invalid`);
|
|
797
812
|
const params = { redirect_uri: redirectUri };
|
|
798
|
-
return this.
|
|
813
|
+
return this.sessionService.makeUrl('logout', params);
|
|
799
814
|
}
|
|
800
815
|
|
|
801
816
|
/**
|
|
@@ -803,7 +818,7 @@ export class Identity extends TinyEmitter {
|
|
|
803
818
|
* @param redirectUri
|
|
804
819
|
*/
|
|
805
820
|
accountUrl(redirectUri: string = this.redirectUri): string {
|
|
806
|
-
return this.
|
|
821
|
+
return this.spid.makeUrl('profile-pages', {
|
|
807
822
|
response_type: 'code',
|
|
808
823
|
redirect_uri: redirectUri,
|
|
809
824
|
});
|
|
@@ -833,7 +848,7 @@ export class Identity extends TinyEmitter {
|
|
|
833
848
|
if (options && options.encoding) {
|
|
834
849
|
queryParams.encoding = options.encoding;
|
|
835
850
|
}
|
|
836
|
-
const widgetUrl = this.
|
|
851
|
+
const widgetUrl = this.bffService.makeUrl('simplified-login-widget', queryParams, false);
|
|
837
852
|
|
|
838
853
|
const prepareLoginParams = async (
|
|
839
854
|
loginParams: SimplifiedLoginWidgetLoginOptions,
|
|
@@ -893,11 +908,11 @@ export class Identity extends TinyEmitter {
|
|
|
893
908
|
* Emitted when the simplified login widget is displayed on the screen
|
|
894
909
|
* @event Identity#simplifiedLoginOpened
|
|
895
910
|
*/
|
|
896
|
-
this.
|
|
911
|
+
this.eventTarget.dispatchEvent(new Event('simplifiedLoginOpened'));
|
|
897
912
|
};
|
|
898
913
|
|
|
899
914
|
const cancelLoginHandler = () => {
|
|
900
|
-
this.
|
|
915
|
+
this.eventTarget.dispatchEvent(new Event('simplifiedLoginCancelled'));
|
|
901
916
|
};
|
|
902
917
|
|
|
903
918
|
if (hasOpenSimplifiedLoginWidget(window)) {
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { default as SDKError } from './sdk-error.js';
|
|
|
10
10
|
/* public available types */
|
|
11
11
|
export type {
|
|
12
12
|
AccountConfig,
|
|
13
|
-
|
|
13
|
+
AccountEventListener,
|
|
14
14
|
AccountEventName,
|
|
15
15
|
} from './types/account.js';
|
|
16
16
|
export type { AccountEnvironment, LogFunction } from './types/common.js';
|
package/src/monetization.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import Cache from './cache.js';
|
|
6
|
-
import { ENDPOINTS } from './config.js';
|
|
7
6
|
import { parseHasAccessResult } from './has-access-response.js';
|
|
8
7
|
import type RESTClient from './rest-client.js';
|
|
9
8
|
import { buildClientSdrn, createAccountRestClient } from './rest-clients.js';
|
|
@@ -11,7 +10,6 @@ import SDKError from './sdk-error.js';
|
|
|
11
10
|
import * as spidTalk from './spid-talk.js';
|
|
12
11
|
import type { HasAccessResult, MonetizationOptions } from './types/monetization.js';
|
|
13
12
|
import type { SessionUserId } from './types/session.js';
|
|
14
|
-
import { urlMapper } from './url.js';
|
|
15
13
|
import { assert, isNonEmptyString, isStr, isUrl } from './validate.js';
|
|
16
14
|
import version from './version.js';
|
|
17
15
|
|
|
@@ -21,13 +19,9 @@ const globalWindow = () => window;
|
|
|
21
19
|
* Provides features related to monetization
|
|
22
20
|
*/
|
|
23
21
|
export class Monetization {
|
|
24
|
-
cache: Cache;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
redirectUri?: string;
|
|
28
|
-
_spid: RESTClient;
|
|
29
|
-
_sessionService?: RESTClient;
|
|
30
|
-
private pendingHasAccessRequests: Record<string, Promise<HasAccessResult>>;
|
|
22
|
+
private readonly cache: Cache;
|
|
23
|
+
private readonly sessionService?: RESTClient;
|
|
24
|
+
private readonly pendingHasAccessRequests: Record<string, Promise<HasAccessResult>>;
|
|
31
25
|
|
|
32
26
|
/**
|
|
33
27
|
* @param options - Monetization configuration
|
|
@@ -43,24 +37,18 @@ export class Monetization {
|
|
|
43
37
|
spidTalk.emulate(window);
|
|
44
38
|
assert(isNonEmptyString(clientId), 'clientId parameter is required');
|
|
45
39
|
assert(isStr(env), `env parameter is invalid: ${env}`);
|
|
40
|
+
const clientSdrn = buildClientSdrn(env, clientId);
|
|
46
41
|
|
|
47
42
|
this.cache = new Cache(() => window && window.sessionStorage);
|
|
48
|
-
this.clientId = clientId;
|
|
49
|
-
this.env = env;
|
|
50
|
-
this.redirectUri = redirectUri;
|
|
51
43
|
this.pendingHasAccessRequests = {};
|
|
52
|
-
this._spid = createAccountRestClient({
|
|
53
|
-
serverUrl: urlMapper(env, ENDPOINTS.SPiD),
|
|
54
|
-
defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri },
|
|
55
|
-
});
|
|
56
44
|
|
|
57
45
|
if (sessionDomain) {
|
|
58
46
|
assert(isUrl(sessionDomain), 'sessionDomain parameter is not a valid URL');
|
|
59
|
-
this.
|
|
47
|
+
this.sessionService = createAccountRestClient({
|
|
60
48
|
serverUrl: sessionDomain,
|
|
61
49
|
defaultParams: {
|
|
62
|
-
client_sdrn:
|
|
63
|
-
redirect_uri:
|
|
50
|
+
client_sdrn: clientSdrn,
|
|
51
|
+
redirect_uri: redirectUri,
|
|
64
52
|
sdk_version: version,
|
|
65
53
|
},
|
|
66
54
|
});
|
|
@@ -77,7 +65,7 @@ export class Monetization {
|
|
|
77
65
|
* doesn't have access to any of the given products/features)
|
|
78
66
|
*/
|
|
79
67
|
async hasAccess(productIds: string[], userId: SessionUserId): Promise<HasAccessResult | null> {
|
|
80
|
-
if (!this.
|
|
68
|
+
if (!this.sessionService) {
|
|
81
69
|
throw new SDKError(`hasAccess can only be called if 'sessionDomain' is configured`);
|
|
82
70
|
}
|
|
83
71
|
if (!userId) {
|
|
@@ -88,11 +76,11 @@ export class Monetization {
|
|
|
88
76
|
}
|
|
89
77
|
|
|
90
78
|
const sortedIds = [...productIds].sort();
|
|
91
|
-
const cacheKey = this.
|
|
79
|
+
const cacheKey = this.accessCacheKey(sortedIds, userId);
|
|
92
80
|
let data = this.cache.get(cacheKey) as HasAccessResult | null;
|
|
93
81
|
if (!data) {
|
|
94
82
|
if (!this.pendingHasAccessRequests[cacheKey]) {
|
|
95
|
-
this.pendingHasAccessRequests[cacheKey] = this.
|
|
83
|
+
this.pendingHasAccessRequests[cacheKey] = this.sessionService.get(
|
|
96
84
|
`/hasAccess/${sortedIds.join(',')}`,
|
|
97
85
|
undefined,
|
|
98
86
|
parseHasAccessResult,
|
|
@@ -123,7 +111,7 @@ export class Monetization {
|
|
|
123
111
|
* @param userId - id of currently logged in user
|
|
124
112
|
*/
|
|
125
113
|
clearCachedAccessResult(productIds: string[], userId: SessionUserId): void {
|
|
126
|
-
this.cache.delete(this.
|
|
114
|
+
this.cache.delete(this.accessCacheKey(productIds, userId));
|
|
127
115
|
}
|
|
128
116
|
|
|
129
117
|
/**
|
|
@@ -132,7 +120,7 @@ export class Monetization {
|
|
|
132
120
|
* @param productIds - which products/features to check
|
|
133
121
|
* @param userId - id of currently logged in user
|
|
134
122
|
*/
|
|
135
|
-
private
|
|
123
|
+
private accessCacheKey(productIds: string[], userId: SessionUserId): string {
|
|
136
124
|
return `prd_${[...productIds].sort()}_${userId}`;
|
|
137
125
|
}
|
|
138
126
|
}
|
package/src/sdk-error.ts
CHANGED
|
@@ -14,6 +14,8 @@ const STRINGIFY_TYPES = ['boolean', 'number', 'string'];
|
|
|
14
14
|
* Custom error class thrown by all SDK methods on failure.
|
|
15
15
|
*
|
|
16
16
|
* All rejected promises from API calls reject with an instance of this class.
|
|
17
|
+
*
|
|
18
|
+
* @group Errors
|
|
17
19
|
*/
|
|
18
20
|
export default class SDKError extends Error {
|
|
19
21
|
/** HTTP status code from the server error payload;
|
package/src/types/account.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { AccountBaseConfig, LogFunction } from './common';
|
|
2
2
|
import type { IdentityBeforeRedirectCallback, VarnishCookieOptions } from './identity';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Configuration used to create an {@link Account} instance.
|
|
6
|
+
*
|
|
7
|
+
* @group Configuration
|
|
8
|
+
*/
|
|
4
9
|
export type AccountConfig = AccountBaseConfig & {
|
|
5
10
|
/** Receives debug log information. If not set, no logging will be done. */
|
|
6
11
|
log?: LogFunction;
|
|
@@ -10,19 +15,36 @@ export type AccountConfig = AccountBaseConfig & {
|
|
|
10
15
|
varnish?: VarnishCookieOptions;
|
|
11
16
|
};
|
|
12
17
|
|
|
18
|
+
type LoginEventDetail = {
|
|
19
|
+
url: string;
|
|
20
|
+
method: 'popup' | 'default';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type LogoutEventDetail = {
|
|
24
|
+
url: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type AccountEventMap = {
|
|
28
|
+
/** Emitted with the login URL and method when `login()` initiates a login flow. */
|
|
29
|
+
login: CustomEvent<LoginEventDetail>;
|
|
30
|
+
/** Emitted with the logout URL when `logout()` is called. */
|
|
31
|
+
logout: CustomEvent<LogoutEventDetail>;
|
|
32
|
+
/** Emitted when the simplified login widget is displayed. */
|
|
33
|
+
simplifiedLoginOpened: Event;
|
|
34
|
+
/** Emitted when the simplified login widget is closed. */
|
|
35
|
+
simplifiedLoginCancelled: Event;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Account event names emitted by the SDK. */
|
|
39
|
+
export type AccountEventName = keyof AccountEventMap;
|
|
40
|
+
|
|
13
41
|
/**
|
|
14
|
-
*
|
|
42
|
+
* Native event listener used by Account event subscriptions.
|
|
43
|
+
*
|
|
44
|
+
* Payload-bearing events are `CustomEvent` instances and expose their payload through `detail`.
|
|
15
45
|
*
|
|
16
|
-
*
|
|
17
|
-
* - `logout`: emitted when a previously logged-in user is no longer logged in, or when
|
|
18
|
-
* `logout()` is called.
|
|
19
|
-
* - `simplifiedLoginOpened`: emitted when the simplified login widget is displayed.
|
|
20
|
-
* - `simplifiedLoginCancelled`: emitted when the simplified login widget is closed.
|
|
46
|
+
* @group Events
|
|
21
47
|
*/
|
|
22
|
-
export type AccountEventName =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| 'simplifiedLoginOpened'
|
|
26
|
-
| 'simplifiedLoginCancelled';
|
|
27
|
-
|
|
28
|
-
export type AccountEventHandler = (...args: unknown[]) => void;
|
|
48
|
+
export type AccountEventListener<EventName extends AccountEventName = AccountEventName> = (
|
|
49
|
+
event: AccountEventMap[EventName],
|
|
50
|
+
) => void;
|
package/src/types/common.ts
CHANGED
|
@@ -3,15 +3,21 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Log callback used by the SDK for debug output.
|
|
5
5
|
* Receives a pre-formatted log line.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* @group Configuration
|
|
7
8
|
*/
|
|
8
9
|
export type LogFunction = (message: string) => void;
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Supported Schibsted account environment keys.
|
|
13
|
+
*
|
|
14
|
+
* @group Configuration
|
|
12
15
|
*/
|
|
13
16
|
export type AccountEnvironment = 'LOCAL' | 'DEV' | 'PRE' | 'PRO' | 'PRO_NO' | 'PRO_FI' | 'PRO_DK';
|
|
14
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Shared Account configuration fields.
|
|
20
|
+
*/
|
|
15
21
|
export type AccountBaseConfig = {
|
|
16
22
|
/** Mandatory client id. Example: "1234567890abcdef12345678" */
|
|
17
23
|
clientId: string;
|