@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.
@@ -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
- /** Receives debug log information. If not set, no logging will be done. */
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. */
@@ -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
  *
@@ -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
- /** Receives debug log information. If not set, no logging will be done. */
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}.
@@ -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: string;
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: string | (() => string | Promise<string>);
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.13";
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.13",
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 { log, callbackBeforeRedirect, defaultProductIds, varnish } = config;
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);
@@ -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 { assert, isNonEmptyString, isObject, isStr, isStrIn, isUrl } from './validate.js';
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 - A function that receives debug log information. If not set,
113
- * no logging will be done
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 this.oauthService.makeUrl('oauth/authorize', {
859
- response_type: 'code',
860
- redirect_uri: redirectUri,
861
- scope,
862
- state,
863
- acr_values: acrValues,
864
- login_hint: loginHint,
865
- tag,
866
- teaser,
867
- max_age: maxAge,
868
- locale,
869
- one_step_login: oneStepLogin || '',
870
- prompt: acrValues ? '' : prompt,
871
- x_domain_id: xDomainId,
872
- x_env_id: xEnvironmentId,
873
- utm_campaign: originCampaign,
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 {
@@ -1,5 +1,10 @@
1
1
  import type { AccountBaseConfig, LogFunction } from './common';
2
- import type { IdentityBeforeRedirectCallback, VarnishCookieOptions } from './identity';
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
- /** Receives debug log information. If not set, no logging will be done. */
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. */
@@ -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.
@@ -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
- /** Receives debug log information. If not set, no logging will be done. */
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
  /**
@@ -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: string;
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: string | (() => string | Promise<string>);
101
+ state?: string | (() => string | Promise<string>);
92
102
  };
93
103
 
94
104
  /**