@schibsted/account-sdk-browser 6.0.0-alpha.13 → 6.0.0-alpha.15
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 +4 -0
- package/dist/account.d.ts +9 -6
- package/dist/consents.d.ts +29 -0
- package/dist/identity.d.ts +24 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +239 -158
- package/dist/index.js.map +1 -1
- package/dist/types/account.d.ts +15 -2
- package/dist/types/common.d.ts +2 -2
- package/dist/types/identity.d.ts +13 -1
- package/dist/types/login.d.ts +11 -2
- package/dist/version.d.ts +1 -1
- package/package.json +5 -1
- package/src/account.ts +30 -7
- package/src/consents.ts +94 -0
- package/src/identity.ts +105 -22
- package/src/index.ts +2 -0
- package/src/types/account.ts +19 -2
- package/src/types/common.ts +2 -2
- package/src/types/identity.ts +14 -1
- package/src/types/login.ts +12 -2
- package/src/version.ts +1 -1
package/dist/types/account.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AccountBaseConfig, LogFunction } from './common';
|
|
2
|
-
import { IdentityBeforeRedirectCallback, VarnishCookieOptions } from './identity';
|
|
2
|
+
import { ConsentReader, IdentityBeforeRedirectCallback, VarnishCookieOptions } from './identity';
|
|
3
|
+
import { StateGenerator } from './login';
|
|
3
4
|
import { SessionResponse } from './session';
|
|
4
5
|
/**
|
|
5
6
|
* Configuration used to create an {@link Account} instance.
|
|
@@ -9,10 +10,22 @@ import { SessionResponse } from './session';
|
|
|
9
10
|
export type AccountConfig = Omit<AccountBaseConfig, 'env'> & {
|
|
10
11
|
/** Schibsted account environment. Defaults to PRE. */
|
|
11
12
|
env?: AccountBaseConfig['env'];
|
|
12
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Receives debug logs and consent reader errors.
|
|
15
|
+
* Without it, consent reader errors are reported through console.warn.
|
|
16
|
+
*/
|
|
13
17
|
log?: LogFunction;
|
|
14
18
|
/** Callback triggered before a session refresh redirect happens. */
|
|
15
19
|
callbackBeforeRedirect?: IdentityBeforeRedirectCallback;
|
|
20
|
+
/** Reads consents forwarded in login URLs. Defaults to the Schibsted Sourcepoint integration. */
|
|
21
|
+
getConsents?: ConsentReader;
|
|
22
|
+
/**
|
|
23
|
+
* Generates the OAuth `state` value used by `login()`, `loginUrl()`, and the simplified login
|
|
24
|
+
* widget when no explicit `state` is passed.
|
|
25
|
+
*/
|
|
26
|
+
generateState: StateGenerator;
|
|
27
|
+
/** Refresh consents of rendered login links when clicked. Defaults to true. */
|
|
28
|
+
refreshLoginLinks?: boolean;
|
|
16
29
|
/** Product or feature ids used by `hasAccess()` when no ids are passed to the method. */
|
|
17
30
|
defaultProductIds?: readonly string[];
|
|
18
31
|
/** Optional Varnish cookie setup applied during Account initialization. */
|
package/dist/types/common.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Log callback used by the SDK for debug output.
|
|
3
|
-
* Receives a pre-formatted log line.
|
|
3
|
+
* Receives a pre-formatted log line and, when reading consents fails, the error.
|
|
4
4
|
*
|
|
5
5
|
* @group Configuration
|
|
6
6
|
*/
|
|
7
|
-
export type LogFunction = (message: string) => void;
|
|
7
|
+
export type LogFunction = (message: string, error?: unknown) => void;
|
|
8
8
|
/**
|
|
9
9
|
* Supported Schibsted account environment keys.
|
|
10
10
|
*
|
package/dist/types/identity.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { AccountBaseConfig, LogFunction } from './common';
|
|
2
|
+
import { StateGenerator } from './login';
|
|
3
|
+
/**
|
|
4
|
+
* Synchronously returns the current consents as query parameters; must not wait for the user's decision.
|
|
5
|
+
*/
|
|
6
|
+
export type ConsentReader = () => URLSearchParams | null;
|
|
2
7
|
/**
|
|
3
8
|
* Callback invoked before the SDK performs a full-page session refresh redirect.
|
|
4
9
|
*/
|
|
@@ -7,10 +12,17 @@ export type IdentityBeforeRedirectCallback = () => void | Promise<void>;
|
|
|
7
12
|
* Options accepted by the {@link Identity} constructor.
|
|
8
13
|
*/
|
|
9
14
|
export type IdentityOptions = AccountBaseConfig & {
|
|
10
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Receives debug logs and consent reader errors.
|
|
17
|
+
* Without it, consent reader errors are reported through console.warn.
|
|
18
|
+
*/
|
|
11
19
|
log?: LogFunction;
|
|
12
20
|
/** Callback triggered before a session refresh redirect happens. */
|
|
13
21
|
callbackBeforeRedirect?: IdentityBeforeRedirectCallback;
|
|
22
|
+
/** Reads consents forwarded in login URLs. Defaults to the Schibsted Sourcepoint integration. */
|
|
23
|
+
getConsents?: ConsentReader;
|
|
24
|
+
/** Generates the OAuth `state` value when login options do not provide one. */
|
|
25
|
+
generateState?: StateGenerator;
|
|
14
26
|
};
|
|
15
27
|
/**
|
|
16
28
|
* Options for {@link Identity#enableVarnishCookie}.
|
package/dist/types/login.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the OAuth `state` value for a login flow. Receives the window the SDK was configured
|
|
3
|
+
* with and must synchronously return a non-empty string.
|
|
4
|
+
*
|
|
5
|
+
* @group Login & Widget
|
|
6
|
+
*/
|
|
7
|
+
export type StateGenerator = (window: Window) => string;
|
|
1
8
|
/**
|
|
2
9
|
* Options accepted by {@link Account#login} and related login URL helpers.
|
|
3
10
|
*
|
|
@@ -7,8 +14,9 @@ export type LoginOptions = {
|
|
|
7
14
|
/**
|
|
8
15
|
* An opaque value used by the client to maintain state between the request and callback.
|
|
9
16
|
* It's also recommended to prevent CSRF {@link https://tools.ietf.org/html/rfc6749#section-10.12}
|
|
17
|
+
* Defaults to the value returned by the configured `generateState` function.
|
|
10
18
|
*/
|
|
11
|
-
state
|
|
19
|
+
state?: string;
|
|
12
20
|
/**
|
|
13
21
|
* Authentication Context Class Reference values. If omitted, the default account flow is used.
|
|
14
22
|
* Supported values include `password`, `otp`, `sms`, `otp-email`, `eid`, `eid-no`, `eid-se`,
|
|
@@ -76,8 +84,9 @@ export type SimplifiedLoginWidgetLoginOptions = Omit<LoginOptions, 'state'> & {
|
|
|
76
84
|
/**
|
|
77
85
|
* An opaque value used by the client to maintain state between the request and callback.
|
|
78
86
|
* It's also recommended to prevent CSRF {@link https://tools.ietf.org/html/rfc6749#section-10.12}
|
|
87
|
+
* Defaults to the value returned by the configured `generateState` function.
|
|
79
88
|
*/
|
|
80
|
-
state
|
|
89
|
+
state?: string | (() => string | Promise<string>);
|
|
81
90
|
};
|
|
82
91
|
/**
|
|
83
92
|
* Minimal user information used by the simplified login widget flow.
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const version = "6.0.0-alpha.
|
|
1
|
+
declare const version = "6.0.0-alpha.15";
|
|
2
2
|
export default version;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schibsted/account-sdk-browser",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.15",
|
|
4
4
|
"description": "Schibsted account SDK for browsers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@biomejs/biome": "2.4.15",
|
|
38
|
+
"@schibsted/consent-url-utils": "^2.0.1",
|
|
38
39
|
"@vitest/coverage-v8": "^4.1.7",
|
|
39
40
|
"jsdom": "^29.1.1",
|
|
40
41
|
"rimraf": "^6.1.3",
|
|
@@ -48,5 +49,8 @@
|
|
|
48
49
|
"repository": {
|
|
49
50
|
"type": "git",
|
|
50
51
|
"url": "git://schibsted.ghe.com/user-identity/account-sdk-browser.git"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"registry": "https://registry.npmjs.org/"
|
|
51
55
|
}
|
|
52
56
|
}
|
package/src/account.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
} from './types/login.js';
|
|
18
18
|
import type { HasAccessResult } from './types/monetization.js';
|
|
19
19
|
import type { ConnectedSessionResponse, SessionResponse, SessionUserId } from './types/session.js';
|
|
20
|
+
import { assert } from './validate.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Unified client-side interface for a configured Schibsted Account integration.
|
|
@@ -46,7 +47,15 @@ export class Account {
|
|
|
46
47
|
throw new SDKError('Schibsted Account SDK has already been initialized');
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
const {
|
|
50
|
+
const {
|
|
51
|
+
log,
|
|
52
|
+
callbackBeforeRedirect,
|
|
53
|
+
getConsents,
|
|
54
|
+
generateState,
|
|
55
|
+
refreshLoginLinks = true,
|
|
56
|
+
defaultProductIds,
|
|
57
|
+
varnish,
|
|
58
|
+
} = config;
|
|
50
59
|
const sharedConfig = {
|
|
51
60
|
clientId: config.clientId,
|
|
52
61
|
redirectUri: config.redirectUri,
|
|
@@ -55,10 +64,18 @@ export class Account {
|
|
|
55
64
|
window: browserWindow,
|
|
56
65
|
};
|
|
57
66
|
|
|
67
|
+
assert(typeof generateState === 'function', 'generateState parameter must be a function');
|
|
68
|
+
assert(
|
|
69
|
+
typeof refreshLoginLinks === 'boolean',
|
|
70
|
+
'refreshLoginLinks parameter must be a boolean',
|
|
71
|
+
);
|
|
72
|
+
|
|
58
73
|
this.#identity = new Identity({
|
|
59
74
|
...sharedConfig,
|
|
60
75
|
log,
|
|
61
76
|
callbackBeforeRedirect,
|
|
77
|
+
getConsents,
|
|
78
|
+
generateState,
|
|
62
79
|
});
|
|
63
80
|
|
|
64
81
|
if (defaultProductIds !== undefined && !Array.isArray(defaultProductIds)) {
|
|
@@ -84,6 +101,9 @@ export class Account {
|
|
|
84
101
|
if (sessionState) {
|
|
85
102
|
sessionStates.set(this, sessionState);
|
|
86
103
|
}
|
|
104
|
+
if (refreshLoginLinks) {
|
|
105
|
+
this.#identity.enableLoginLinkRefresh();
|
|
106
|
+
}
|
|
87
107
|
browserWindow.schAccount = this;
|
|
88
108
|
browserWindow.dispatchEvent(
|
|
89
109
|
new CustomEvent('schAccount:ready', { detail: { instance: this } }),
|
|
@@ -219,10 +239,11 @@ export class Account {
|
|
|
219
239
|
* Call this from a user gesture when `preferPopup` is enabled, otherwise browser popup blockers
|
|
220
240
|
* may block the popup and the SDK will fall back to redirect navigation.
|
|
221
241
|
*
|
|
222
|
-
* @param options - Login URL parameters.
|
|
242
|
+
* @param options - Login URL parameters. When `state` is omitted, the configured
|
|
243
|
+
* `generateState` function provides it.
|
|
223
244
|
* @returns A popup window reference when a popup is opened; otherwise `null`.
|
|
224
245
|
*/
|
|
225
|
-
login(options: LoginOptions): Window | null {
|
|
246
|
+
login(options: LoginOptions = {}): Window | null {
|
|
226
247
|
return this.#identity.login(options);
|
|
227
248
|
}
|
|
228
249
|
|
|
@@ -238,9 +259,10 @@ export class Account {
|
|
|
238
259
|
/**
|
|
239
260
|
* Builds the login URL without navigating the browser.
|
|
240
261
|
*
|
|
241
|
-
* @param options - Login URL parameters.
|
|
262
|
+
* @param options - Login URL parameters. When `state` is omitted, the configured
|
|
263
|
+
* `generateState` function provides it.
|
|
242
264
|
*/
|
|
243
|
-
loginUrl(options: LoginOptions): string {
|
|
265
|
+
loginUrl(options: LoginOptions = {}): string {
|
|
244
266
|
return this.#identity.loginUrl(options);
|
|
245
267
|
}
|
|
246
268
|
|
|
@@ -266,14 +288,15 @@ export class Account {
|
|
|
266
288
|
* Loads and displays the simplified login widget when user context is available.
|
|
267
289
|
*
|
|
268
290
|
* Your application decides how often to call this method. The `state` field in `loginParams`
|
|
269
|
-
* may be a string or a function that resolves to a string.
|
|
291
|
+
* may be a string or a function that resolves to a string. When omitted, the configured
|
|
292
|
+
* `generateState` function provides it.
|
|
270
293
|
* Widget open and cancel events can be observed with `on()`.
|
|
271
294
|
*
|
|
272
295
|
* @returns `true` when the widget was displayed.
|
|
273
296
|
* @throws {SDKError} If user context data is missing or the widget script cannot be loaded.
|
|
274
297
|
*/
|
|
275
298
|
showSimplifiedLoginWidget(
|
|
276
|
-
loginParams: SimplifiedLoginWidgetLoginOptions,
|
|
299
|
+
loginParams: SimplifiedLoginWidgetLoginOptions = {},
|
|
277
300
|
options?: SimplifiedLoginWidgetOptions,
|
|
278
301
|
): Promise<boolean> {
|
|
279
302
|
return this.#identity.showSimplifiedLoginWidget(loginParams, options);
|
package/src/consents.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* Copyright 2026 Schibsted Products & Technology AS. Licensed under the terms of the MIT license.
|
|
2
|
+
* See LICENSE.md in the project root.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getConsentsSync, serializeConsents } from '@schibsted/consent-url-utils';
|
|
6
|
+
|
|
7
|
+
const LINK_ACTIVATION_EVENTS = ['click', 'auxclick'];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reads consents from the Schibsted Sourcepoint integration, the default consent reader.
|
|
11
|
+
* Pages without the integration have no consents to forward, so they are skipped before
|
|
12
|
+
* `getConsentsSync` would warn about the missing consent manager on every login URL.
|
|
13
|
+
* @private
|
|
14
|
+
*/
|
|
15
|
+
export function readSourcepointConsents(window: Window): URLSearchParams | null {
|
|
16
|
+
if (!window._tcf_ && !window._scc_) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return serializeConsents(getConsentsSync(window));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sets the given parameters on a URL, replacing previous values of the same parameters.
|
|
24
|
+
* @private
|
|
25
|
+
* @param href
|
|
26
|
+
* @param consentParams
|
|
27
|
+
* @return - The URL with the parameters replaced
|
|
28
|
+
*/
|
|
29
|
+
export function replaceConsentParams(href: string, consentParams: URLSearchParams): string {
|
|
30
|
+
const url = new URL(href);
|
|
31
|
+
// Two passes on purpose: the reader may return repeated keys (e.g. `purpose=a&purpose=b`).
|
|
32
|
+
// A single `set()` pass would keep only the last value, and a single `append()` pass would keep
|
|
33
|
+
// stale values of the link. So first drop every stale value, then append all fresh ones.
|
|
34
|
+
consentParams.forEach((_, key) => {
|
|
35
|
+
url.searchParams.delete(key);
|
|
36
|
+
});
|
|
37
|
+
consentParams.forEach((value, key) => {
|
|
38
|
+
url.searchParams.append(key, value);
|
|
39
|
+
});
|
|
40
|
+
return url.href;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Finds the link that was activated by a click, including links inside open shadow roots.
|
|
45
|
+
* @private
|
|
46
|
+
* @param event
|
|
47
|
+
*/
|
|
48
|
+
function findActivatedLink(event: Event): HTMLAnchorElement | undefined {
|
|
49
|
+
const path = typeof event.composedPath === 'function' ? event.composedPath() : [event.target];
|
|
50
|
+
return path.find((node): node is HTMLAnchorElement => {
|
|
51
|
+
const link = node as Partial<HTMLAnchorElement> | null;
|
|
52
|
+
return !!link && link.tagName === 'A' && typeof link.href === 'string';
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Refreshes consents of already rendered login links right before the browser follows them.
|
|
58
|
+
* Links are often generated before the user answers the consent dialog, so their consents are stale.
|
|
59
|
+
* The listener runs in the capture phase, before page handlers read the link.
|
|
60
|
+
* @private
|
|
61
|
+
* @param document
|
|
62
|
+
* @param options
|
|
63
|
+
* @param options.isLoginUrl - Tells whether a URL is a login URL to refresh
|
|
64
|
+
* @param options.getConsentParams - Current consent parameters
|
|
65
|
+
*/
|
|
66
|
+
export function refreshLoginLinksOnActivation(
|
|
67
|
+
document: Document,
|
|
68
|
+
{
|
|
69
|
+
isLoginUrl,
|
|
70
|
+
getConsentParams,
|
|
71
|
+
}: {
|
|
72
|
+
isLoginUrl: (href: string) => boolean;
|
|
73
|
+
getConsentParams: () => URLSearchParams;
|
|
74
|
+
},
|
|
75
|
+
): void {
|
|
76
|
+
const refreshLink = (event: Event) => {
|
|
77
|
+
const link = findActivatedLink(event);
|
|
78
|
+
if (!link || !isLoginUrl(link.href)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const consentParams = getConsentParams();
|
|
83
|
+
// Unknown consents never overwrite the ones the link was created with.
|
|
84
|
+
if (consentParams.toString() === '') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
link.href = replaceConsentParams(link.href, consentParams);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
LINK_ACTIVATION_EVENTS.forEach((type) => {
|
|
92
|
+
document.addEventListener(type, refreshLink, true);
|
|
93
|
+
});
|
|
94
|
+
}
|
package/src/identity.ts
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import Cache from './cache.js';
|
|
6
6
|
import { ENDPOINTS } from './config.js';
|
|
7
|
+
import {
|
|
8
|
+
readSourcepointConsents,
|
|
9
|
+
refreshLoginLinksOnActivation,
|
|
10
|
+
replaceConsentParams,
|
|
11
|
+
} from './consents.js';
|
|
7
12
|
import { cloneDeep } from './object.js';
|
|
8
13
|
import * as popup from './popup.js';
|
|
9
14
|
import type RESTClient from './rest-client.js';
|
|
@@ -22,6 +27,7 @@ import * as spidTalk from './spid-talk.js';
|
|
|
22
27
|
import type { AccountEventName } from './types/account.js';
|
|
23
28
|
import type { LogFunction } from './types/common.js';
|
|
24
29
|
import type {
|
|
30
|
+
ConsentReader,
|
|
25
31
|
IdentityBeforeRedirectCallback,
|
|
26
32
|
IdentityOptions,
|
|
27
33
|
VarnishCookieOptions,
|
|
@@ -34,6 +40,7 @@ import {
|
|
|
34
40
|
type SimplifiedLoginWidgetInitialParams,
|
|
35
41
|
type SimplifiedLoginWidgetLoginOptions,
|
|
36
42
|
type SimplifiedLoginWidgetOptions,
|
|
43
|
+
type StateGenerator,
|
|
37
44
|
} from './types/login.js';
|
|
38
45
|
import type {
|
|
39
46
|
ConnectedSessionResponse,
|
|
@@ -42,7 +49,15 @@ import type {
|
|
|
42
49
|
SessionResponse,
|
|
43
50
|
} from './types/session.js';
|
|
44
51
|
import { urlMapper } from './url.js';
|
|
45
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
assert,
|
|
54
|
+
isFunction,
|
|
55
|
+
isNonEmptyString,
|
|
56
|
+
isObject,
|
|
57
|
+
isStr,
|
|
58
|
+
isStrIn,
|
|
59
|
+
isUrl,
|
|
60
|
+
} from './validate.js';
|
|
46
61
|
import version from './version.js';
|
|
47
62
|
|
|
48
63
|
declare global {
|
|
@@ -75,6 +90,8 @@ export class Identity {
|
|
|
75
90
|
private readonly env: string;
|
|
76
91
|
private readonly log?: LogFunction;
|
|
77
92
|
private readonly callbackBeforeRedirect: IdentityBeforeRedirectCallback;
|
|
93
|
+
private readonly getConsents: ConsentReader;
|
|
94
|
+
private readonly generateState?: StateGenerator;
|
|
78
95
|
|
|
79
96
|
// Caches
|
|
80
97
|
private readonly sessionStorageCache: Cache;
|
|
@@ -109,10 +126,13 @@ export class Identity {
|
|
|
109
126
|
* @param options.sessionDomain - Example: "https://id.site.com"
|
|
110
127
|
* @param options.redirectUri - Example: "https://site.com"
|
|
111
128
|
* @param options.env - Schibsted account environment: `PRE` (default), `PRO`, `PRO_NO`, `PRO_FI` or `PRO_DK`
|
|
112
|
-
* @param options.log -
|
|
113
|
-
*
|
|
129
|
+
* @param options.log - Receives debug logs and consent reader errors.
|
|
130
|
+
* Without it, consent reader errors are reported through console.warn.
|
|
114
131
|
* @param options.window - window object
|
|
115
132
|
* @param options.callbackBeforeRedirect - callback triggered before session refresh redirect happen
|
|
133
|
+
* @param options.getConsents - reads consents forwarded in login URLs.
|
|
134
|
+
* Defaults to the Schibsted Sourcepoint integration
|
|
135
|
+
* @param options.generateState - generates the OAuth state when login options do not provide one
|
|
116
136
|
* @throws {SDKError} - If any of options are invalid
|
|
117
137
|
*/
|
|
118
138
|
constructor({
|
|
@@ -123,6 +143,8 @@ export class Identity {
|
|
|
123
143
|
log,
|
|
124
144
|
window = globalWindow(),
|
|
125
145
|
callbackBeforeRedirect = () => {},
|
|
146
|
+
getConsents = () => readSourcepointConsents(window),
|
|
147
|
+
generateState,
|
|
126
148
|
}: IdentityOptions) {
|
|
127
149
|
assert(isNonEmptyString(clientId), 'clientId parameter is required');
|
|
128
150
|
assert(isObject(window), 'The reference to window is missing');
|
|
@@ -133,6 +155,12 @@ export class Identity {
|
|
|
133
155
|
);
|
|
134
156
|
assert(isStr(env), `env parameter is invalid: ${env}`);
|
|
135
157
|
|
|
158
|
+
assert(isFunction(getConsents), 'getConsents parameter must be a function');
|
|
159
|
+
assert(
|
|
160
|
+
generateState === undefined || isFunction(generateState),
|
|
161
|
+
'generateState parameter must be a function',
|
|
162
|
+
);
|
|
163
|
+
|
|
136
164
|
spidTalk.emulate(window);
|
|
137
165
|
this.window = window;
|
|
138
166
|
this.clientId = clientId;
|
|
@@ -140,6 +168,8 @@ export class Identity {
|
|
|
140
168
|
this.env = env;
|
|
141
169
|
this.log = log;
|
|
142
170
|
this.callbackBeforeRedirect = callbackBeforeRedirect;
|
|
171
|
+
this.getConsents = getConsents;
|
|
172
|
+
this.generateState = generateState;
|
|
143
173
|
|
|
144
174
|
this.sessionStorageCache = new Cache(() => this.window && this.window.sessionStorage);
|
|
145
175
|
this.localStorageCache = new Cache(() => this.window && this.window.localStorage);
|
|
@@ -189,6 +219,17 @@ export class Identity {
|
|
|
189
219
|
this._unblockSessionCall();
|
|
190
220
|
}
|
|
191
221
|
|
|
222
|
+
/** @internal */
|
|
223
|
+
enableLoginLinkRefresh(): void {
|
|
224
|
+
const { document } = this.window;
|
|
225
|
+
if (document && isFunction(document.addEventListener)) {
|
|
226
|
+
refreshLoginLinksOnActivation(document, {
|
|
227
|
+
isLoginUrl: (href) => this.isLoginUrl(href),
|
|
228
|
+
getConsentParams: () => this.getConsentParams(),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
192
233
|
addEventListener(type: AccountEventName, listener: EventListener): void {
|
|
193
234
|
this.eventTarget.addEventListener(type, listener);
|
|
194
235
|
}
|
|
@@ -802,7 +843,7 @@ export class Identity {
|
|
|
802
843
|
/**
|
|
803
844
|
* Generates the link to the new login page that'll be used in the popup or redirect flow
|
|
804
845
|
* @param options
|
|
805
|
-
* @param options.state
|
|
846
|
+
* @param options.state - defaults to the value returned by `generateState`
|
|
806
847
|
* @param options.acrValues
|
|
807
848
|
* @param options.scope
|
|
808
849
|
* @param options.redirectUri
|
|
@@ -819,7 +860,7 @@ export class Identity {
|
|
|
819
860
|
* @return - The url
|
|
820
861
|
*/
|
|
821
862
|
loginUrl({
|
|
822
|
-
state,
|
|
863
|
+
state = this.generateState?.(this.window),
|
|
823
864
|
acrValues = '',
|
|
824
865
|
scope = 'openid',
|
|
825
866
|
redirectUri = this.redirectUri,
|
|
@@ -855,23 +896,65 @@ export class Identity {
|
|
|
855
896
|
`the state parameter should be a non empty string but it is ${state}`,
|
|
856
897
|
);
|
|
857
898
|
|
|
858
|
-
return
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
899
|
+
return replaceConsentParams(
|
|
900
|
+
this.oauthService.makeUrl('oauth/authorize', {
|
|
901
|
+
response_type: 'code',
|
|
902
|
+
redirect_uri: redirectUri,
|
|
903
|
+
scope,
|
|
904
|
+
state,
|
|
905
|
+
acr_values: acrValues,
|
|
906
|
+
login_hint: loginHint,
|
|
907
|
+
tag,
|
|
908
|
+
teaser,
|
|
909
|
+
max_age: maxAge,
|
|
910
|
+
locale,
|
|
911
|
+
one_step_login: oneStepLogin || '',
|
|
912
|
+
prompt: acrValues ? '' : prompt,
|
|
913
|
+
x_domain_id: xDomainId,
|
|
914
|
+
x_env_id: xEnvironmentId,
|
|
915
|
+
utm_campaign: originCampaign,
|
|
916
|
+
}),
|
|
917
|
+
this.getConsentParams(),
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Reads the current consent parameters. A failing consent reader must not prevent a login,
|
|
923
|
+
* so the failure is logged and the login continues without consents.
|
|
924
|
+
* @private
|
|
925
|
+
* @return - Consent query parameters; empty when unavailable
|
|
926
|
+
*/
|
|
927
|
+
private getConsentParams(): URLSearchParams {
|
|
928
|
+
try {
|
|
929
|
+
const consentParams = this.getConsents() || new URLSearchParams();
|
|
930
|
+
assert(
|
|
931
|
+
consentParams instanceof URLSearchParams,
|
|
932
|
+
`getConsents must return URLSearchParams but it returned ${consentParams}`,
|
|
933
|
+
);
|
|
934
|
+
return consentParams;
|
|
935
|
+
} catch (error) {
|
|
936
|
+
const log = this.log || console.warn;
|
|
937
|
+
log('Schibsted account SDK: could not read consents, logging in without them', error);
|
|
938
|
+
return new URLSearchParams();
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Tells whether a URL is a login URL generated by this instance
|
|
944
|
+
* @private
|
|
945
|
+
* @param href
|
|
946
|
+
*/
|
|
947
|
+
private isLoginUrl(href: string): boolean {
|
|
948
|
+
if (!isUrl(href)) {
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
const url = new URL(href);
|
|
952
|
+
const authorizeUrl = new URL(this.oauthService.makeUrl('oauth/authorize', {}, false));
|
|
953
|
+
return (
|
|
954
|
+
url.origin === authorizeUrl.origin &&
|
|
955
|
+
url.pathname === authorizeUrl.pathname &&
|
|
956
|
+
url.searchParams.get('client_id') === this.clientId
|
|
957
|
+
);
|
|
875
958
|
}
|
|
876
959
|
|
|
877
960
|
/**
|
package/src/index.ts
CHANGED
|
@@ -25,11 +25,13 @@ export type {
|
|
|
25
25
|
LogoutRequestedEventDetail,
|
|
26
26
|
} from './types/account.js';
|
|
27
27
|
export type { AccountEnvironment, LogFunction } from './types/common.js';
|
|
28
|
+
export type { ConsentReader } from './types/identity.js';
|
|
28
29
|
export type {
|
|
29
30
|
LoginOptions,
|
|
30
31
|
SimplifiedLoginData,
|
|
31
32
|
SimplifiedLoginWidgetLoginOptions,
|
|
32
33
|
SimplifiedLoginWidgetOptions,
|
|
34
|
+
StateGenerator,
|
|
33
35
|
} from './types/login.js';
|
|
34
36
|
export type { HasAccessResult } from './types/monetization.js';
|
|
35
37
|
export type {
|
package/src/types/account.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { AccountBaseConfig, LogFunction } from './common';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ConsentReader,
|
|
4
|
+
IdentityBeforeRedirectCallback,
|
|
5
|
+
VarnishCookieOptions,
|
|
6
|
+
} from './identity';
|
|
7
|
+
import type { StateGenerator } from './login';
|
|
3
8
|
import type { SessionResponse } from './session';
|
|
4
9
|
|
|
5
10
|
/**
|
|
@@ -10,10 +15,22 @@ import type { SessionResponse } from './session';
|
|
|
10
15
|
export type AccountConfig = Omit<AccountBaseConfig, 'env'> & {
|
|
11
16
|
/** Schibsted account environment. Defaults to PRE. */
|
|
12
17
|
env?: AccountBaseConfig['env'];
|
|
13
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Receives debug logs and consent reader errors.
|
|
20
|
+
* Without it, consent reader errors are reported through console.warn.
|
|
21
|
+
*/
|
|
14
22
|
log?: LogFunction;
|
|
15
23
|
/** Callback triggered before a session refresh redirect happens. */
|
|
16
24
|
callbackBeforeRedirect?: IdentityBeforeRedirectCallback;
|
|
25
|
+
/** Reads consents forwarded in login URLs. Defaults to the Schibsted Sourcepoint integration. */
|
|
26
|
+
getConsents?: ConsentReader;
|
|
27
|
+
/**
|
|
28
|
+
* Generates the OAuth `state` value used by `login()`, `loginUrl()`, and the simplified login
|
|
29
|
+
* widget when no explicit `state` is passed.
|
|
30
|
+
*/
|
|
31
|
+
generateState: StateGenerator;
|
|
32
|
+
/** Refresh consents of rendered login links when clicked. Defaults to true. */
|
|
33
|
+
refreshLoginLinks?: boolean;
|
|
17
34
|
/** Product or feature ids used by `hasAccess()` when no ids are passed to the method. */
|
|
18
35
|
defaultProductIds?: readonly string[];
|
|
19
36
|
/** Optional Varnish cookie setup applied during Account initialization. */
|
package/src/types/common.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Log callback used by the SDK for debug output.
|
|
5
|
-
* Receives a pre-formatted log line.
|
|
5
|
+
* Receives a pre-formatted log line and, when reading consents fails, the error.
|
|
6
6
|
*
|
|
7
7
|
* @group Configuration
|
|
8
8
|
*/
|
|
9
|
-
export type LogFunction = (message: string) => void;
|
|
9
|
+
export type LogFunction = (message: string, error?: unknown) => void;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Supported Schibsted account environment keys.
|
package/src/types/identity.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { AccountBaseConfig, LogFunction } from './common';
|
|
2
|
+
import type { StateGenerator } from './login';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Synchronously returns the current consents as query parameters; must not wait for the user's decision.
|
|
6
|
+
*/
|
|
7
|
+
export type ConsentReader = () => URLSearchParams | null;
|
|
2
8
|
|
|
3
9
|
/**
|
|
4
10
|
* Callback invoked before the SDK performs a full-page session refresh redirect.
|
|
@@ -9,10 +15,17 @@ export type IdentityBeforeRedirectCallback = () => void | Promise<void>;
|
|
|
9
15
|
* Options accepted by the {@link Identity} constructor.
|
|
10
16
|
*/
|
|
11
17
|
export type IdentityOptions = AccountBaseConfig & {
|
|
12
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Receives debug logs and consent reader errors.
|
|
20
|
+
* Without it, consent reader errors are reported through console.warn.
|
|
21
|
+
*/
|
|
13
22
|
log?: LogFunction;
|
|
14
23
|
/** Callback triggered before a session refresh redirect happens. */
|
|
15
24
|
callbackBeforeRedirect?: IdentityBeforeRedirectCallback;
|
|
25
|
+
/** Reads consents forwarded in login URLs. Defaults to the Schibsted Sourcepoint integration. */
|
|
26
|
+
getConsents?: ConsentReader;
|
|
27
|
+
/** Generates the OAuth `state` value when login options do not provide one. */
|
|
28
|
+
generateState?: StateGenerator;
|
|
16
29
|
};
|
|
17
30
|
|
|
18
31
|
/**
|
package/src/types/login.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the OAuth `state` value for a login flow. Receives the window the SDK was configured
|
|
3
|
+
* with and must synchronously return a non-empty string.
|
|
4
|
+
*
|
|
5
|
+
* @group Login & Widget
|
|
6
|
+
*/
|
|
7
|
+
export type StateGenerator = (window: Window) => string;
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Options accepted by {@link Account#login} and related login URL helpers.
|
|
3
11
|
*
|
|
@@ -7,8 +15,9 @@ export type LoginOptions = {
|
|
|
7
15
|
/**
|
|
8
16
|
* An opaque value used by the client to maintain state between the request and callback.
|
|
9
17
|
* It's also recommended to prevent CSRF {@link https://tools.ietf.org/html/rfc6749#section-10.12}
|
|
18
|
+
* Defaults to the value returned by the configured `generateState` function.
|
|
10
19
|
*/
|
|
11
|
-
state
|
|
20
|
+
state?: string;
|
|
12
21
|
/**
|
|
13
22
|
* Authentication Context Class Reference values. If omitted, the default account flow is used.
|
|
14
23
|
* Supported values include `password`, `otp`, `sms`, `otp-email`, `eid`, `eid-no`, `eid-se`,
|
|
@@ -87,8 +96,9 @@ export type SimplifiedLoginWidgetLoginOptions = Omit<LoginOptions, 'state'> & {
|
|
|
87
96
|
/**
|
|
88
97
|
* An opaque value used by the client to maintain state between the request and callback.
|
|
89
98
|
* It's also recommended to prevent CSRF {@link https://tools.ietf.org/html/rfc6749#section-10.12}
|
|
99
|
+
* Defaults to the value returned by the configured `generateState` function.
|
|
90
100
|
*/
|
|
91
|
-
state
|
|
101
|
+
state?: string | (() => string | Promise<string>);
|
|
92
102
|
};
|
|
93
103
|
|
|
94
104
|
/**
|