@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.
- package/README.md +39 -39
- package/dist/account-sdk.d.ts +4 -0
- package/dist/account.d.ts +30 -0
- package/dist/global-registry.d.ts +1 -1
- package/dist/globals.d.ts +2 -0
- package/dist/has-access-response.d.ts +2 -0
- package/dist/identity.d.ts +37 -237
- package/dist/index.d.ts +8 -3
- package/dist/index.js +862 -4
- package/dist/index.js.map +1 -0
- package/dist/monetization.d.ts +7 -43
- package/dist/resolve-browser-window.d.ts +1 -0
- package/dist/{RESTClient.d.ts → rest-client.d.ts} +22 -11
- package/dist/rest-clients.d.ts +10 -0
- package/dist/session-response.d.ts +30 -0
- package/dist/types/account.d.ts +13 -0
- package/dist/types/common.d.ts +22 -0
- package/dist/types/identity.d.ts +41 -0
- package/dist/types/login.d.ts +120 -0
- package/dist/types/monetization-guards.d.ts +2 -0
- package/dist/types/monetization.d.ts +21 -0
- package/dist/types/session-guards.d.ts +36 -0
- package/dist/types/session.d.ts +124 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -9
- package/src/account-sdk.ts +74 -0
- package/src/account.ts +153 -0
- package/src/cache.ts +1 -1
- package/src/global-registry.ts +1 -1
- package/src/globals.ts +2 -0
- package/src/has-access-response.ts +35 -0
- package/src/identity.ts +269 -423
- package/src/index.ts +28 -3
- package/src/monetization.ts +31 -68
- package/src/object.ts +1 -1
- package/src/resolve-browser-window.ts +11 -0
- package/src/{RESTClient.ts → rest-client.ts} +55 -28
- package/src/rest-clients.ts +30 -0
- package/src/session-response.ts +79 -0
- package/src/types/account.ts +27 -0
- package/src/types/common.ts +26 -0
- package/src/types/identity.ts +55 -0
- package/src/types/login.ts +145 -0
- package/src/types/monetization-guards.ts +35 -0
- package/src/types/monetization.ts +24 -0
- package/src/types/session-guards.ts +89 -0
- package/src/types/session.ts +147 -0
- package/src/validate.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/identity-s4nofYmB.js +0 -370
- package/dist/identity-s4nofYmB.js.map +0 -1
- package/dist/identity.js +0 -2
- package/dist/monetization.js +0 -72
- package/dist/monetization.js.map +0 -1
- package/dist/version-spE-k97g.js +0 -289
- package/dist/version-spE-k97g.js.map +0 -1
- /package/dist/{SDKError.d.ts → sdk-error.d.ts} +0 -0
- /package/dist/{spidTalk.d.ts → spid-talk.d.ts} +0 -0
- /package/src/{SDKError.ts → sdk-error.ts} +0 -0
- /package/src/{spidTalk.ts → spid-talk.ts} +0 -0
package/src/index.ts
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
* See LICENSE.md in the project root.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export { default as SDKError } from './
|
|
5
|
+
export { Account } from './account.js';
|
|
6
|
+
export { createAccountSdk, getAccountSdk } from './account-sdk.js';
|
|
7
|
+
export { default as SDKError } from './sdk-error.js';
|
|
8
|
+
|
|
9
|
+
/* public available types */
|
|
10
|
+
export type {
|
|
11
|
+
AccountConfig,
|
|
12
|
+
AccountEventHandler,
|
|
13
|
+
AccountEventName,
|
|
14
|
+
AccountSessionEventName,
|
|
15
|
+
} from './types/account.js';
|
|
16
|
+
export type { AccountEnvironment, LogFunction } from './types/common.js';
|
|
17
|
+
export type {
|
|
18
|
+
LoginOptions,
|
|
19
|
+
SimplifiedLoginData,
|
|
20
|
+
SimplifiedLoginWidgetLoginOptions,
|
|
21
|
+
SimplifiedLoginWidgetOptions,
|
|
22
|
+
} from './types/login.js';
|
|
23
|
+
export type { HasAccessResult } from './types/monetization.js';
|
|
24
|
+
export type {
|
|
25
|
+
ConnectedSessionResponse,
|
|
26
|
+
ConnectedSessionWithUserIdResponse,
|
|
27
|
+
RedirectSessionResponse,
|
|
28
|
+
SessionFailureResponse,
|
|
29
|
+
SessionResponse,
|
|
30
|
+
SessionSuccessResponse,
|
|
31
|
+
SessionUserId,
|
|
32
|
+
} from './types/session.js';
|
package/src/monetization.ts
CHANGED
|
@@ -4,33 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
import { TinyEmitter } from 'tiny-emitter';
|
|
6
6
|
import Cache from './cache.js';
|
|
7
|
-
import { ENDPOINTS
|
|
7
|
+
import { ENDPOINTS } from './config.js';
|
|
8
8
|
import { registerAndDispatchInGlobal } from './global-registry.js';
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
9
|
+
import { parseHasAccessResult } from './has-access-response.js';
|
|
10
|
+
import type { SessionUserId } from './identity.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 * as spidTalk from './spid-talk.js';
|
|
15
|
+
import type { HasAccessResult, MonetizationOptions } from './types/monetization.js';
|
|
12
16
|
import { urlMapper } from './url.js';
|
|
13
17
|
import { assert, isNonEmptyString, isStr, isUrl } from './validate.js';
|
|
14
18
|
import version from './version.js';
|
|
15
19
|
|
|
16
20
|
const globalWindow = () => window;
|
|
17
21
|
|
|
18
|
-
/**
|
|
19
|
-
* Result returned by the Session Service `/hasAccess` endpoint, describing whether the user
|
|
20
|
-
* is entitled to a requested set of products/features.
|
|
21
|
-
* @internal
|
|
22
|
-
*/
|
|
23
|
-
export interface HasAccessResult {
|
|
24
|
-
/** Whether the user is entitled to at least one of the requested products/features. */
|
|
25
|
-
entitled: boolean;
|
|
26
|
-
/** How long this result stays valid, in seconds, before the cache entry expires. */
|
|
27
|
-
ttl: number;
|
|
28
|
-
allowedFeatures: string[];
|
|
29
|
-
userId: number;
|
|
30
|
-
uuid: string;
|
|
31
|
-
sig: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
22
|
/**
|
|
35
23
|
* Provides features related to monetization
|
|
36
24
|
*/
|
|
@@ -38,8 +26,8 @@ export class Monetization extends TinyEmitter {
|
|
|
38
26
|
cache: Cache;
|
|
39
27
|
clientId: string;
|
|
40
28
|
env: string;
|
|
41
|
-
redirectUri
|
|
42
|
-
_spid
|
|
29
|
+
redirectUri?: string;
|
|
30
|
+
_spid: RESTClient;
|
|
43
31
|
_sessionService?: RESTClient;
|
|
44
32
|
private pendingHasAccessRequests: Record<string, Promise<HasAccessResult>>;
|
|
45
33
|
|
|
@@ -53,63 +41,36 @@ export class Monetization extends TinyEmitter {
|
|
|
53
41
|
env = 'PRE',
|
|
54
42
|
sessionDomain,
|
|
55
43
|
window = globalWindow(),
|
|
56
|
-
}: {
|
|
57
|
-
/** Mandatory client id */
|
|
58
|
-
clientId: string;
|
|
59
|
-
/** Redirect uri */
|
|
60
|
-
redirectUri: string;
|
|
61
|
-
/** Example: `"https://id.site.com"` */
|
|
62
|
-
sessionDomain: string;
|
|
63
|
-
/** Schibsted account environment: `PRE` (default), `PRO`, `PRO_NO` */
|
|
64
|
-
env?: string;
|
|
65
|
-
/** Window object to use (defaults to the global `window`). */
|
|
66
|
-
window?: Window;
|
|
67
|
-
}) {
|
|
44
|
+
}: MonetizationOptions) {
|
|
68
45
|
super();
|
|
69
46
|
spidTalk.emulate(window);
|
|
70
47
|
assert(isNonEmptyString(clientId), 'clientId parameter is required');
|
|
48
|
+
assert(isStr(env), `env parameter is invalid: ${env}`);
|
|
71
49
|
|
|
72
50
|
this.cache = new Cache(() => window && window.sessionStorage);
|
|
73
51
|
this.clientId = clientId;
|
|
74
52
|
this.env = env;
|
|
75
53
|
this.redirectUri = redirectUri;
|
|
76
54
|
this.pendingHasAccessRequests = {};
|
|
77
|
-
this.
|
|
55
|
+
this._spid = createAccountRestClient({
|
|
56
|
+
serverUrl: urlMapper(env, ENDPOINTS.SPiD),
|
|
57
|
+
defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri },
|
|
58
|
+
});
|
|
78
59
|
|
|
79
60
|
if (sessionDomain) {
|
|
80
61
|
assert(isUrl(sessionDomain), 'sessionDomain parameter is not a valid URL');
|
|
81
|
-
this.
|
|
62
|
+
this._sessionService = createAccountRestClient({
|
|
63
|
+
serverUrl: sessionDomain,
|
|
64
|
+
defaultParams: {
|
|
65
|
+
client_sdrn: buildClientSdrn(this.env, this.clientId),
|
|
66
|
+
redirect_uri: this.redirectUri,
|
|
67
|
+
sdk_version: version,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
82
70
|
}
|
|
83
71
|
registerAndDispatchInGlobal(window, 'schMonetization', this);
|
|
84
72
|
}
|
|
85
73
|
|
|
86
|
-
/**
|
|
87
|
-
* Set SPiD server URL
|
|
88
|
-
* @private
|
|
89
|
-
* @param url
|
|
90
|
-
*/
|
|
91
|
-
private _setSpidServerUrl(url: string): void {
|
|
92
|
-
assert(isStr(url), `url parameter is invalid: ${url}`);
|
|
93
|
-
this._spid = new RESTClient({
|
|
94
|
-
serverUrl: urlMapper(url, ENDPOINTS.SPiD),
|
|
95
|
-
defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri },
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Set session-service domain
|
|
101
|
-
* @private
|
|
102
|
-
* @param domain - real URL — (**not** 'PRE' style env key)
|
|
103
|
-
*/
|
|
104
|
-
private _setSessionServiceUrl(domain: string): void {
|
|
105
|
-
assert(isStr(domain), `domain parameter is invalid: ${domain}`);
|
|
106
|
-
const client_sdrn = `sdrn:${NAMESPACE[this.env as keyof typeof NAMESPACE]}:client:${this.clientId}`;
|
|
107
|
-
this._sessionService = new RESTClient({
|
|
108
|
-
serverUrl: domain,
|
|
109
|
-
defaultParams: { client_sdrn, redirect_uri: this.redirectUri, sdk_version: version },
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
74
|
/**
|
|
114
75
|
* Checks if the user has access to a set of products or features.
|
|
115
76
|
* @param productIds - which products/features to check
|
|
@@ -119,7 +80,7 @@ export class Monetization extends TinyEmitter {
|
|
|
119
80
|
* @returns The data object returned from Schibsted account (or `null` if the user
|
|
120
81
|
* doesn't have access to any of the given products/features)
|
|
121
82
|
*/
|
|
122
|
-
async hasAccess(productIds: string[], userId:
|
|
83
|
+
async hasAccess(productIds: string[], userId: SessionUserId): Promise<HasAccessResult | null> {
|
|
123
84
|
if (!this._sessionService) {
|
|
124
85
|
throw new SDKError(`hasAccess can only be called if 'sessionDomain' is configured`);
|
|
125
86
|
}
|
|
@@ -137,6 +98,8 @@ export class Monetization extends TinyEmitter {
|
|
|
137
98
|
if (!this.pendingHasAccessRequests[cacheKey]) {
|
|
138
99
|
this.pendingHasAccessRequests[cacheKey] = this._sessionService.get(
|
|
139
100
|
`/hasAccess/${sortedIds.join(',')}`,
|
|
101
|
+
undefined,
|
|
102
|
+
parseHasAccessResult,
|
|
140
103
|
);
|
|
141
104
|
}
|
|
142
105
|
const promise = this.pendingHasAccessRequests[cacheKey];
|
|
@@ -163,7 +126,7 @@ export class Monetization extends TinyEmitter {
|
|
|
163
126
|
* @param productIds - which products/features to check
|
|
164
127
|
* @param userId - id of currently logged in user
|
|
165
128
|
*/
|
|
166
|
-
clearCachedAccessResult(productIds: string[], userId:
|
|
129
|
+
clearCachedAccessResult(productIds: string[], userId: SessionUserId): void {
|
|
167
130
|
this.cache.delete(this._accessCacheKey(productIds, userId));
|
|
168
131
|
}
|
|
169
132
|
|
|
@@ -173,7 +136,7 @@ export class Monetization extends TinyEmitter {
|
|
|
173
136
|
* @param productIds - which products/features to check
|
|
174
137
|
* @param userId - id of currently logged in user
|
|
175
138
|
*/
|
|
176
|
-
private _accessCacheKey(productIds: string[], userId:
|
|
139
|
+
private _accessCacheKey(productIds: string[], userId: SessionUserId): string {
|
|
177
140
|
return `prd_${[...productIds].sort()}_${userId}`;
|
|
178
141
|
}
|
|
179
142
|
|
|
@@ -182,7 +145,7 @@ export class Monetization extends TinyEmitter {
|
|
|
182
145
|
* @param redirectUri
|
|
183
146
|
* @returns - The url to the subscriptions review page
|
|
184
147
|
*/
|
|
185
|
-
subscriptionsUrl(redirectUri: string = this.redirectUri): string {
|
|
148
|
+
subscriptionsUrl(redirectUri: string = this.redirectUri ?? ''): string {
|
|
186
149
|
assert(isUrl(redirectUri), `subscriptionsUrl(): redirectUri is invalid`);
|
|
187
150
|
return this._spid.makeUrl('account/subscriptions', { redirect_uri: redirectUri });
|
|
188
151
|
}
|
|
@@ -192,7 +155,7 @@ export class Monetization extends TinyEmitter {
|
|
|
192
155
|
* @param redirectUri
|
|
193
156
|
* @returns - The url to the products review page
|
|
194
157
|
*/
|
|
195
|
-
productsUrl(redirectUri: string = this.redirectUri): string {
|
|
158
|
+
productsUrl(redirectUri: string = this.redirectUri ?? ''): string {
|
|
196
159
|
assert(isUrl(redirectUri), `productsUrl(): redirectUri is invalid`);
|
|
197
160
|
return this._spid.makeUrl('account/products', { redirect_uri: redirectUri });
|
|
198
161
|
}
|
package/src/object.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
export const resolveBrowserWindow = (providedWindow?: Window): Window | undefined => {
|
|
6
|
+
if (providedWindow) {
|
|
7
|
+
return providedWindow;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return typeof window === 'undefined' ? undefined : window;
|
|
11
|
+
};
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { cloneDefined } from './object.js';
|
|
6
|
-
import SDKError from './
|
|
6
|
+
import SDKError from './sdk-error.js';
|
|
7
|
+
import type { LogFunction } from './types/common.js';
|
|
7
8
|
import { urlMapper } from './url.js';
|
|
8
9
|
import { assert, isFunction, isNonEmptyString, isObject, isStr } from './validate.js';
|
|
9
10
|
|
|
@@ -15,13 +16,7 @@ import { assert, isFunction, isNonEmptyString, isObject, isStr } from './validat
|
|
|
15
16
|
const logString = (msg: unknown[]): string =>
|
|
16
17
|
msg.map((m) => (isObject(m) ? JSON.stringify(m, null, 2) : m)).join(' ');
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
* Calls a log function passing a string
|
|
20
|
-
* @private
|
|
21
|
-
* @param fn - The log function
|
|
22
|
-
* @param msg - The values to log
|
|
23
|
-
*/
|
|
24
|
-
const logFn = (fn: Function | undefined, ...msg: unknown[]): void => {
|
|
19
|
+
const logFn = (fn: LogFunction | undefined, ...msg: unknown[]): void => {
|
|
25
20
|
if (fn) {
|
|
26
21
|
fn(logString(msg));
|
|
27
22
|
}
|
|
@@ -65,6 +60,20 @@ type QueryParams = Record<string, QueryValue>;
|
|
|
65
60
|
*/
|
|
66
61
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
67
62
|
|
|
63
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
64
|
+
|
|
65
|
+
type ResponseParser<T> = (value: JsonValue) => T;
|
|
66
|
+
|
|
67
|
+
type RESTRequestOptions<T = JsonValue> = {
|
|
68
|
+
method: HttpMethod;
|
|
69
|
+
pathname: string;
|
|
70
|
+
data?: QueryParams;
|
|
71
|
+
headers?: Record<string, string>;
|
|
72
|
+
useDefaultParams?: boolean;
|
|
73
|
+
fetchOptions?: RequestInit;
|
|
74
|
+
parseResponse?: ResponseParser<T>;
|
|
75
|
+
};
|
|
76
|
+
|
|
68
77
|
/**
|
|
69
78
|
* This class can be used for creating a wrapper around a server and all its endpoints.
|
|
70
79
|
* Its functionality is extended by {@link JSONPClient}
|
|
@@ -78,7 +87,7 @@ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
|
78
87
|
export class RESTClient {
|
|
79
88
|
url: URL;
|
|
80
89
|
defaultParams: QueryParams;
|
|
81
|
-
log?:
|
|
90
|
+
log?: LogFunction;
|
|
82
91
|
fetch: FetchFunction;
|
|
83
92
|
|
|
84
93
|
/**
|
|
@@ -103,7 +112,7 @@ export class RESTClient {
|
|
|
103
112
|
serverUrl?: string;
|
|
104
113
|
envDic?: Record<string, string>;
|
|
105
114
|
fetch?: FetchFunction;
|
|
106
|
-
log?:
|
|
115
|
+
log?: LogFunction;
|
|
107
116
|
defaultParams?: QueryParams;
|
|
108
117
|
}) {
|
|
109
118
|
assert(isObject(defaultParams), `defaultParams should be a non-null object`);
|
|
@@ -135,21 +144,23 @@ export class RESTClient {
|
|
|
135
144
|
* @param options.fetchOptions - Additional fetch options
|
|
136
145
|
* @throws {SDKError} - If the call can't be made for whatever reason.
|
|
137
146
|
*/
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
// Overloads describe the two supported modes:
|
|
148
|
+
// - without a parser: return the parsed JSON response as-is;
|
|
149
|
+
// - with a parser: return the parser's typed result.
|
|
150
|
+
async go(options: RESTRequestOptions): Promise<JsonValue>;
|
|
151
|
+
async go<T>(options: RESTRequestOptions<T> & { parseResponse: ResponseParser<T> }): Promise<T>;
|
|
152
|
+
|
|
153
|
+
// TypeScript requires one implementation signature for the overloads above.
|
|
154
|
+
async go<T = JsonValue>(options: RESTRequestOptions<T>): Promise<JsonValue | T> {
|
|
155
|
+
const {
|
|
156
|
+
method,
|
|
157
|
+
headers,
|
|
158
|
+
pathname,
|
|
159
|
+
data = {},
|
|
160
|
+
useDefaultParams = true,
|
|
161
|
+
fetchOptions = { method: options.method, credentials: 'include' },
|
|
162
|
+
parseResponse,
|
|
163
|
+
} = options;
|
|
153
164
|
assert(isNonEmptyString(method), `Method must be a non empty string but it is "${method}"`);
|
|
154
165
|
assert(isNonEmptyString(pathname), `Pathname must be string but it is "${pathname}"`);
|
|
155
166
|
assert(isObject(data), `data must be a non-null object`);
|
|
@@ -167,9 +178,9 @@ export class RESTClient {
|
|
|
167
178
|
// status code not in range 200-299
|
|
168
179
|
throw new SDKError(response.statusText, { code: response.status });
|
|
169
180
|
}
|
|
170
|
-
const responseObject = await response.json();
|
|
181
|
+
const responseObject: JsonValue = await response.json();
|
|
171
182
|
logFn(this.log, 'Response Parsed:', responseObject);
|
|
172
|
-
return responseObject;
|
|
183
|
+
return parseResponse ? parseResponse(responseObject) : responseObject;
|
|
173
184
|
} catch (err: unknown) {
|
|
174
185
|
let msg = isStr(err) ? err : 'Unknown RESTClient error';
|
|
175
186
|
if (isObject(err) && isStr(err.message)) {
|
|
@@ -199,7 +210,23 @@ export class RESTClient {
|
|
|
199
210
|
* @param pathname - WHATWG pathname ie. 'api/2/endpoint-name'
|
|
200
211
|
* @param data - Query parameters
|
|
201
212
|
*/
|
|
202
|
-
|
|
213
|
+
// Mirrors `go()` overloads for the common GET helper.
|
|
214
|
+
get(pathname: string, data?: QueryParams): Promise<JsonValue>;
|
|
215
|
+
get<T>(
|
|
216
|
+
pathname: string,
|
|
217
|
+
data: QueryParams | undefined,
|
|
218
|
+
parseResponse: ResponseParser<T>,
|
|
219
|
+
): Promise<T>;
|
|
220
|
+
|
|
221
|
+
// TypeScript requires one implementation signature for the overloads above.
|
|
222
|
+
get<T>(
|
|
223
|
+
pathname: string,
|
|
224
|
+
data?: QueryParams,
|
|
225
|
+
parseResponse?: ResponseParser<T>,
|
|
226
|
+
): Promise<JsonValue | T> {
|
|
227
|
+
if (parseResponse) {
|
|
228
|
+
return this.go({ method: 'GET', pathname, data, parseResponse });
|
|
229
|
+
}
|
|
203
230
|
return this.go({ method: 'GET', pathname, data });
|
|
204
231
|
}
|
|
205
232
|
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { NAMESPACE } from './config.js';
|
|
6
|
+
import RESTClient from './rest-client.js';
|
|
7
|
+
import type { LogFunction } from './types/common.js';
|
|
8
|
+
import { assert, isNonEmptyString } from './validate.js';
|
|
9
|
+
|
|
10
|
+
type AccountRestClientOptions = {
|
|
11
|
+
serverUrl: string;
|
|
12
|
+
log?: LogFunction;
|
|
13
|
+
defaultParams: Record<string, string | number | boolean | undefined>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const isKnownNamespace = (env: string): env is keyof typeof NAMESPACE =>
|
|
17
|
+
Object.hasOwn(NAMESPACE, env);
|
|
18
|
+
|
|
19
|
+
export const buildClientSdrn = (env: string, clientId: string): string => {
|
|
20
|
+
assert(isNonEmptyString(clientId), 'clientId parameter is required');
|
|
21
|
+
assert(isKnownNamespace(env), `env parameter is invalid: ${env}`);
|
|
22
|
+
|
|
23
|
+
return `sdrn:${NAMESPACE[env]}:client:${clientId}`;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const createAccountRestClient = ({
|
|
27
|
+
serverUrl,
|
|
28
|
+
log,
|
|
29
|
+
defaultParams,
|
|
30
|
+
}: AccountRestClientOptions): RESTClient => new RESTClient({ serverUrl, log, defaultParams });
|
|
@@ -0,0 +1,79 @@
|
|
|
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 type {
|
|
6
|
+
ConnectedSessionResponse,
|
|
7
|
+
ConnectedSessionWithUserIdResponse,
|
|
8
|
+
NormalizedSessionPayload,
|
|
9
|
+
RedirectSessionPayload,
|
|
10
|
+
SessionFailureResponse,
|
|
11
|
+
SessionResponse,
|
|
12
|
+
SessionSuccessResponse,
|
|
13
|
+
} from './types/session.js';
|
|
14
|
+
import { isObject, isStr } from './validate.js';
|
|
15
|
+
|
|
16
|
+
export function isSessionSuccessResponse(value: unknown): value is SessionSuccessResponse {
|
|
17
|
+
return isObject(value) && typeof value.result === 'boolean';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isConnectedSessionResponse(value: unknown): value is ConnectedSessionResponse {
|
|
21
|
+
return isSessionSuccessResponse(value) && value.result === true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isSessionFailureResponse(value: unknown): value is SessionFailureResponse {
|
|
25
|
+
return isObject(value) && Boolean(value.error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isRedirectSessionPayload(value: unknown): value is RedirectSessionPayload {
|
|
29
|
+
return isObject(value) && Object.keys(value).length === 1 && isStr(value.redirectURL);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isConnectedSessionWithUserIdResponse(
|
|
33
|
+
value: SessionResponse,
|
|
34
|
+
): value is ConnectedSessionWithUserIdResponse {
|
|
35
|
+
return (
|
|
36
|
+
isConnectedSessionResponse(value) &&
|
|
37
|
+
(typeof value.userId === 'number' || typeof value.userId === 'string')
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Converts an unknown Session Service `hasSession` payload into one of the shapes the SDK knows how
|
|
43
|
+
* to handle.
|
|
44
|
+
*
|
|
45
|
+
* This function is intentionally tolerant because `hasSession()` historically resolved unknown
|
|
46
|
+
* object payloads instead of rejecting them. The SDK relies on that behavior for backwards
|
|
47
|
+
* compatibility with clients that check the raw response themselves.
|
|
48
|
+
*
|
|
49
|
+
* The normal cases are:
|
|
50
|
+
* - regular success payloads with a boolean `result`;
|
|
51
|
+
* - failure payloads wrapped as `{ error, response? }`;
|
|
52
|
+
* - Safari/session-refresh redirect payloads shaped as `{ redirectURL }`.
|
|
53
|
+
*
|
|
54
|
+
* Any other object is preserved as an unrecognized response so callers can still receive it.
|
|
55
|
+
* Non-object values are normalized to `{}`, matching the old "empty response" fallback used by
|
|
56
|
+
* `hasSession()` and cached-session reads.
|
|
57
|
+
*
|
|
58
|
+
* This is used both for network responses parsed by `RESTClient` and for values restored from the
|
|
59
|
+
* session cache, so the rest of the Identity flow can switch on typed guards instead of repeatedly
|
|
60
|
+
* re-validating unknown data.
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export function normalizeSessionResponse(value: unknown): NormalizedSessionPayload {
|
|
65
|
+
if (isRedirectSessionPayload(value)) {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
if (isSessionFailureResponse(value)) {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
if (isSessionSuccessResponse(value)) {
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
if (isObject(value)) {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AccountBaseConfig, LogFunction } from './common';
|
|
2
|
+
import type { IdentityBeforeRedirectCallback, VarnishCookieOptions } from './identity';
|
|
3
|
+
|
|
4
|
+
export type AccountConfig = AccountBaseConfig & {
|
|
5
|
+
/** Receives debug log information. If not set, no logging will be done. */
|
|
6
|
+
log?: LogFunction;
|
|
7
|
+
/** Callback triggered before a session refresh redirect happens. */
|
|
8
|
+
callbackBeforeRedirect?: IdentityBeforeRedirectCallback;
|
|
9
|
+
/** Optional Varnish cookie setup applied during Account initialization. */
|
|
10
|
+
varnish?: VarnishCookieOptions;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AccountSessionEventName =
|
|
14
|
+
| 'login'
|
|
15
|
+
| 'logout'
|
|
16
|
+
| 'userChange'
|
|
17
|
+
| 'sessionChange'
|
|
18
|
+
| 'notLoggedin'
|
|
19
|
+
| 'sessionInit'
|
|
20
|
+
| 'statusChange'
|
|
21
|
+
| 'error'
|
|
22
|
+
| 'simplifiedLoginOpened'
|
|
23
|
+
| 'simplifiedLoginCancelled';
|
|
24
|
+
|
|
25
|
+
export type AccountEventName = AccountSessionEventName | 'hasAccess';
|
|
26
|
+
|
|
27
|
+
export type AccountEventHandler = (...args: unknown[]) => void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* Common types used across the SDK */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Log callback used by the SDK for debug output.
|
|
5
|
+
* Receives a pre-formatted log line.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export type LogFunction = (message: string) => void;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Supported Schibsted account environment keys.
|
|
12
|
+
*/
|
|
13
|
+
export type AccountEnvironment = 'LOCAL' | 'DEV' | 'PRE' | 'PRO' | 'PRO_NO' | 'PRO_FI' | 'PRO_DK';
|
|
14
|
+
|
|
15
|
+
export type AccountBaseConfig = {
|
|
16
|
+
/** Mandatory client id. Example: "1234567890abcdef12345678" */
|
|
17
|
+
clientId: string;
|
|
18
|
+
/** Redirect uri. Example: "https://site.com" */
|
|
19
|
+
redirectUri: string;
|
|
20
|
+
/** Session Service domain. Example: "https://id.site.com" */
|
|
21
|
+
sessionDomain: string;
|
|
22
|
+
/** Schibsted account environment. Defaults to PRE. */
|
|
23
|
+
env?: AccountEnvironment;
|
|
24
|
+
/** Window object to use. Defaults to the global `window`. */
|
|
25
|
+
window?: Window;
|
|
26
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AccountBaseConfig, LogFunction } from './common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Callback invoked before the SDK performs a full-page session refresh redirect.
|
|
5
|
+
*/
|
|
6
|
+
export type IdentityBeforeRedirectCallback = () => void | Promise<void>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Options accepted by the {@link Identity} constructor.
|
|
10
|
+
*/
|
|
11
|
+
export type IdentityOptions = AccountBaseConfig & {
|
|
12
|
+
/** Receives debug log information. If not set, no logging will be done. */
|
|
13
|
+
log?: LogFunction;
|
|
14
|
+
/** Callback triggered before a session refresh redirect happens. */
|
|
15
|
+
callbackBeforeRedirect?: IdentityBeforeRedirectCallback;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Identity events emitted by the SDK.
|
|
20
|
+
*
|
|
21
|
+
* - `login`: emitted when `hasSession()` resolves with a logged-in user payload.
|
|
22
|
+
* - `logout`: emitted when a previously logged-in user is no longer logged in, or when
|
|
23
|
+
* `logout()` is called.
|
|
24
|
+
* - `userChange`: emitted when the current session user id changes.
|
|
25
|
+
* - `sessionChange`: emitted when either the previous or current `hasSession()` payload contains
|
|
26
|
+
* a logged-in user.
|
|
27
|
+
* - `notLoggedin`: emitted when neither the previous nor current `hasSession()` payload contains
|
|
28
|
+
* a logged-in user.
|
|
29
|
+
* - `sessionInit`: emitted once for the first logged-in session payload.
|
|
30
|
+
* - `statusChange`: emitted when the session `userStatus` changes.
|
|
31
|
+
* - `error`: emitted when `hasSession()` fails.
|
|
32
|
+
* - `simplifiedLoginOpened`: emitted when the simplified login widget is displayed.
|
|
33
|
+
* - `simplifiedLoginCancelled`: emitted when the simplified login widget is closed.
|
|
34
|
+
*/
|
|
35
|
+
export type IdentityEventName =
|
|
36
|
+
| 'login'
|
|
37
|
+
| 'logout'
|
|
38
|
+
| 'userChange'
|
|
39
|
+
| 'sessionChange'
|
|
40
|
+
| 'notLoggedin'
|
|
41
|
+
| 'sessionInit'
|
|
42
|
+
| 'statusChange'
|
|
43
|
+
| 'error'
|
|
44
|
+
| 'simplifiedLoginOpened'
|
|
45
|
+
| 'simplifiedLoginCancelled';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Options for {@link Identity#enableVarnishCookie}.
|
|
49
|
+
*/
|
|
50
|
+
export type VarnishCookieOptions = {
|
|
51
|
+
/** Override number of seconds before the Varnish cookie expires. */
|
|
52
|
+
expiresIn?: number;
|
|
53
|
+
/** Override cookie domain. E.g. "vg.no" instead of "www.vg.no". */
|
|
54
|
+
domain?: string;
|
|
55
|
+
};
|