@schibsted/account-sdk-browser 6.0.0-alpha.1 → 6.0.0-alpha.2
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 +8 -33
- package/dist/RESTClient.d.ts +91 -0
- package/dist/SDKError.d.ts +27 -0
- package/dist/cache.d.ts +65 -0
- package/dist/config.d.ts +86 -0
- package/dist/global-registry.d.ts +23 -0
- package/dist/globals.d.ts +13 -0
- package/dist/identity-s4nofYmB.js +370 -0
- package/dist/identity-s4nofYmB.js.map +1 -0
- package/dist/identity.d.ts +523 -0
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/monetization.d.ts +94 -0
- package/dist/monetization.js +72 -0
- package/dist/monetization.js.map +1 -0
- package/{src → dist}/object.d.ts +4 -9
- package/dist/popup.d.ts +9 -0
- package/{src → dist}/spidTalk.d.ts +4 -6
- package/dist/url.d.ts +8 -0
- package/dist/validate.d.ts +50 -0
- package/dist/version-spE-k97g.js +289 -0
- package/dist/version-spE-k97g.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/package.json +38 -22
- package/src/RESTClient.ts +226 -0
- package/src/SDKError.ts +59 -0
- package/src/{cache.js → cache.ts} +52 -37
- package/src/{config.js → config.ts} +7 -32
- package/src/global-registry.ts +39 -0
- package/src/globals.ts +10 -0
- package/src/{identity.js → identity.ts} +539 -437
- package/{index.js → src/index.ts} +1 -3
- package/src/{monetization.js → monetization.ts} +77 -48
- package/src/{object.js → object.ts} +8 -15
- package/src/popup.ts +74 -0
- package/src/{spidTalk.js → spidTalk.ts} +10 -12
- package/src/{url.js → url.ts} +6 -10
- package/src/{validate.js → validate.ts} +26 -42
- package/src/{version.js → version.ts} +1 -2
- package/identity.d.ts +0 -1
- package/identity.js +0 -5
- package/index.d.ts +0 -4
- package/monetization.d.ts +0 -1
- package/monetization.js +0 -5
- package/payment.d.ts +0 -1
- package/payment.js +0 -5
- package/src/RESTClient.d.ts +0 -89
- package/src/RESTClient.js +0 -193
- package/src/SDKError.d.ts +0 -16
- package/src/SDKError.js +0 -55
- package/src/cache.d.ts +0 -64
- package/src/config.d.ts +0 -34
- package/src/global-registry.js +0 -20
- package/src/identity.d.ts +0 -679
- package/src/monetization.d.ts +0 -80
- package/src/payment.d.ts +0 -115
- package/src/payment.js +0 -211
- package/src/popup.d.ts +0 -10
- package/src/popup.js +0 -59
- package/src/url.d.ts +0 -10
- package/src/validate.d.ts +0 -64
- package/src/version.d.ts +0 -2
package/package.json
CHANGED
|
@@ -1,23 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schibsted/account-sdk-browser",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.2",
|
|
4
4
|
"description": "Schibsted account SDK for browsers",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"default": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"./identity": {
|
|
13
|
+
"default": "./dist/identity.js",
|
|
14
|
+
"types": "./dist/identity.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./monetization": {
|
|
17
|
+
"default": "./dist/monetization.js",
|
|
18
|
+
"types": "./dist/monetization.d.ts"
|
|
19
|
+
}
|
|
12
20
|
},
|
|
13
21
|
"scripts": {
|
|
14
|
-
"clean": "rimraf .cache coverage docs && npm run clean:legacy",
|
|
22
|
+
"clean": "rimraf .cache coverage docs dist && npm run clean:legacy",
|
|
15
23
|
"clean:legacy": "rimraf es5",
|
|
16
|
-
"docs": "
|
|
17
|
-
"
|
|
18
|
-
"
|
|
24
|
+
"docs:build": "typedoc",
|
|
25
|
+
"docs:watch": "typedoc --watch",
|
|
26
|
+
"docs:open": "node scripts/open-docs.mjs",
|
|
27
|
+
"lint": "biome check",
|
|
28
|
+
"lint:fix": "biome check --fix",
|
|
29
|
+
"build": "vite build",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"pretest": "npm run build",
|
|
19
32
|
"test": "vitest run",
|
|
20
|
-
"
|
|
33
|
+
"precover": "npm run build",
|
|
34
|
+
"cover": "vitest run --coverage",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
21
36
|
},
|
|
22
37
|
"author": "",
|
|
23
38
|
"license": "MIT",
|
|
@@ -25,19 +40,20 @@
|
|
|
25
40
|
"tiny-emitter": "^2.1.0"
|
|
26
41
|
},
|
|
27
42
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
43
|
+
"@biomejs/biome": "2.4.15",
|
|
44
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
45
|
+
"jsdom": "^29.1.1",
|
|
46
|
+
"open": "^11.0.0",
|
|
47
|
+
"rimraf": "^6.1.3",
|
|
48
|
+
"typedoc": "^0.28.19",
|
|
49
|
+
"typedoc-plugin-mdn-links": "^5.1.1",
|
|
50
|
+
"typescript": "^6.0.3",
|
|
51
|
+
"unplugin-dts": "^1.0.1",
|
|
52
|
+
"vite": "^8.0.14",
|
|
53
|
+
"vitest": "^4.1.7"
|
|
37
54
|
},
|
|
38
55
|
"repository": {
|
|
39
56
|
"type": "git",
|
|
40
57
|
"url": "git://schibsted.ghe.com/user-identity/account-sdk-browser.git"
|
|
41
|
-
}
|
|
42
|
-
"typings": "index.d.ts"
|
|
58
|
+
}
|
|
43
59
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/* Copyright 2024 Schibsted Products & Technology AS. Licensed under the terms of the MIT license.
|
|
2
|
+
* See LICENSE.md in the project root.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { cloneDefined } from './object.js';
|
|
6
|
+
import SDKError from './SDKError.js';
|
|
7
|
+
import { urlMapper } from './url.js';
|
|
8
|
+
import { assert, isFunction, isNonEmptyString, isObject, isStr } from './validate.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Converts a series of parameters of various types to a string that's suitable for logging.
|
|
12
|
+
* @private
|
|
13
|
+
* @param msg - The values to format; objects are stringified to JSON
|
|
14
|
+
*/
|
|
15
|
+
const logString = (msg: unknown[]): string =>
|
|
16
|
+
msg.map((m) => (isObject(m) ? JSON.stringify(m, null, 2) : m)).join(' ');
|
|
17
|
+
|
|
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 => {
|
|
25
|
+
if (fn) {
|
|
26
|
+
fn(logString(msg));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Encode a string like URLSearchParams would do
|
|
32
|
+
* @private
|
|
33
|
+
* @param str - The input
|
|
34
|
+
*/
|
|
35
|
+
function encode(str: string): string {
|
|
36
|
+
const replace: Record<string, string> = {
|
|
37
|
+
'!': '%21',
|
|
38
|
+
"'": '%27',
|
|
39
|
+
'(': '%28',
|
|
40
|
+
')': '%29',
|
|
41
|
+
'~': '%7E',
|
|
42
|
+
'%20': '+',
|
|
43
|
+
'%00': '\x00',
|
|
44
|
+
};
|
|
45
|
+
return encodeURIComponent(str).replace(/[!'()~]|%20|%00/g, (match) => replace[match]);
|
|
46
|
+
}
|
|
47
|
+
type FetchFunction = (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
48
|
+
const globalFetch = () => window.fetch && window.fetch.bind(window);
|
|
49
|
+
const noFetch: FetchFunction = () => {
|
|
50
|
+
throw new SDKError('fetch is not available in this environment');
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The value of a single query parameter; `undefined` entries are skipped.
|
|
55
|
+
*/
|
|
56
|
+
type QueryValue = string | number | boolean | undefined;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Query parameters appended to a request URL.
|
|
60
|
+
*/
|
|
61
|
+
type QueryParams = Record<string, QueryValue>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* HTTP method used for a request.
|
|
65
|
+
*/
|
|
66
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* This class can be used for creating a wrapper around a server and all its endpoints.
|
|
70
|
+
* Its functionality is extended by {@link JSONPClient}
|
|
71
|
+
* Creates a client to a REST server. While useful stand-alone, it's also used for some other
|
|
72
|
+
* types of client that change some functionalities.
|
|
73
|
+
* @throws {SDKError} - If any of options are invalid
|
|
74
|
+
* @summary The simplest way to communicate to a REST endpoint without any
|
|
75
|
+
* special authentication
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
export class RESTClient {
|
|
79
|
+
url: URL;
|
|
80
|
+
defaultParams: QueryParams;
|
|
81
|
+
log?: Function;
|
|
82
|
+
fetch: FetchFunction;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param options
|
|
86
|
+
* @param options.serverUrl - The URL to the server eg.
|
|
87
|
+
* https://login.schibsted.com or a URL key like 'DEV' in combination with {@link envDic}.
|
|
88
|
+
* @param options.envDic - A dictionary that will be used for looking up
|
|
89
|
+
* {@link serverUrl} keys. If serverUrl is always a URL, you don't need this.
|
|
90
|
+
* @param options.fetch - The fetch function to use. It can be native
|
|
91
|
+
* or a polyfill
|
|
92
|
+
* @param options.log - A function that will be called with log messages about
|
|
93
|
+
* request and response
|
|
94
|
+
* @param options.defaultParams - Query parameters added to every request
|
|
95
|
+
*/
|
|
96
|
+
constructor({
|
|
97
|
+
serverUrl = 'PRE',
|
|
98
|
+
envDic,
|
|
99
|
+
fetch = globalFetch() ?? noFetch,
|
|
100
|
+
log,
|
|
101
|
+
defaultParams = {},
|
|
102
|
+
}: {
|
|
103
|
+
serverUrl?: string;
|
|
104
|
+
envDic?: Record<string, string>;
|
|
105
|
+
fetch?: FetchFunction;
|
|
106
|
+
log?: Function;
|
|
107
|
+
defaultParams?: QueryParams;
|
|
108
|
+
}) {
|
|
109
|
+
assert(isObject(defaultParams), `defaultParams should be a non-null object`);
|
|
110
|
+
|
|
111
|
+
const mappedUrl = urlMapper(serverUrl, envDic ?? {});
|
|
112
|
+
const handledServerUrl = mappedUrl.endsWith('/') ? mappedUrl : `${mappedUrl}/`;
|
|
113
|
+
this.url = new URL(handledServerUrl);
|
|
114
|
+
|
|
115
|
+
this.defaultParams = defaultParams;
|
|
116
|
+
|
|
117
|
+
if (log) {
|
|
118
|
+
assert(isFunction(log), `log must be a function but it is ${log}`);
|
|
119
|
+
this.log = log;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.fetch = fetch;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Makes the actual call to the server and deals with headers, data objects and the edge cases.
|
|
127
|
+
* Please note that this method expects the response to be in JSON format. However, it'll not
|
|
128
|
+
* parse the response if its code is not in the 200 range.
|
|
129
|
+
* @param options - An obligatory options object
|
|
130
|
+
* @param options.method - The HTTP request method, e.g. 'GET' or 'POST'
|
|
131
|
+
* @param options.pathname - The path to the endpoint like 'api/2/endpoint-name'
|
|
132
|
+
* @param options.data - Query parameters added to the request URL
|
|
133
|
+
* @param options.headers - Request headers as a plain key/value map
|
|
134
|
+
* @param options.useDefaultParams - Should we add the defaultParams to the query?
|
|
135
|
+
* @param options.fetchOptions - Additional fetch options
|
|
136
|
+
* @throws {SDKError} - If the call can't be made for whatever reason.
|
|
137
|
+
*/
|
|
138
|
+
async go({
|
|
139
|
+
method,
|
|
140
|
+
headers,
|
|
141
|
+
pathname,
|
|
142
|
+
data = {},
|
|
143
|
+
useDefaultParams = true,
|
|
144
|
+
fetchOptions = { method, credentials: 'include' },
|
|
145
|
+
}: {
|
|
146
|
+
method: HttpMethod;
|
|
147
|
+
pathname: string;
|
|
148
|
+
data?: QueryParams;
|
|
149
|
+
headers?: Record<string, string>;
|
|
150
|
+
useDefaultParams?: boolean;
|
|
151
|
+
fetchOptions?: RequestInit;
|
|
152
|
+
}): Promise<any> {
|
|
153
|
+
assert(isNonEmptyString(method), `Method must be a non empty string but it is "${method}"`);
|
|
154
|
+
assert(isNonEmptyString(pathname), `Pathname must be string but it is "${pathname}"`);
|
|
155
|
+
assert(isObject(data), `data must be a non-null object`);
|
|
156
|
+
|
|
157
|
+
fetchOptions.headers = isObject(headers) ? headers : {};
|
|
158
|
+
const fullUrl = this.makeUrl(pathname, data, useDefaultParams);
|
|
159
|
+
|
|
160
|
+
logFn(this.log, 'Request:', method.toUpperCase(), fullUrl);
|
|
161
|
+
logFn(this.log, 'Request Headers:', fetchOptions.headers);
|
|
162
|
+
logFn(this.log, 'Request Body:', fetchOptions.body);
|
|
163
|
+
try {
|
|
164
|
+
const response = await this.fetch(fullUrl, fetchOptions);
|
|
165
|
+
logFn(this.log, 'Response Code:', response.status, response.statusText);
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
// status code not in range 200-299
|
|
168
|
+
throw new SDKError(response.statusText, { code: response.status });
|
|
169
|
+
}
|
|
170
|
+
const responseObject = await response.json();
|
|
171
|
+
logFn(this.log, 'Response Parsed:', responseObject);
|
|
172
|
+
return responseObject;
|
|
173
|
+
} catch (err: unknown) {
|
|
174
|
+
let msg = isStr(err) ? err : 'Unknown RESTClient error';
|
|
175
|
+
if (isObject(err) && isStr(err.message)) {
|
|
176
|
+
msg = err.message;
|
|
177
|
+
}
|
|
178
|
+
const errorObject = isObject(err) ? err : undefined;
|
|
179
|
+
throw new SDKError(`Failed to '${method}' '${fullUrl}': '${msg}'`, errorObject);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a url that points to an endpoint in the server
|
|
185
|
+
* @param pathname - WHATWG pathname ie. 'api/2/endpoint-name'
|
|
186
|
+
* @param query - Query parameters to serialize into the query string
|
|
187
|
+
* @param useDefaultParams - Should we add the defaultParams to the query?
|
|
188
|
+
*/
|
|
189
|
+
makeUrl(pathname = '', query: QueryParams = {}, useDefaultParams = true): string {
|
|
190
|
+
const handledPathname = pathname.startsWith('/') ? pathname.slice(1) : pathname;
|
|
191
|
+
|
|
192
|
+
const url = new URL(handledPathname, this.url);
|
|
193
|
+
url.search = RESTClient.search(query, useDefaultParams, this.defaultParams);
|
|
194
|
+
return url.href;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Make a GET request
|
|
199
|
+
* @param pathname - WHATWG pathname ie. 'api/2/endpoint-name'
|
|
200
|
+
* @param data - Query parameters
|
|
201
|
+
*/
|
|
202
|
+
get(pathname: string, data?: QueryParams): Promise<any> {
|
|
203
|
+
return this.go({ method: 'GET', pathname, data });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Construct query string for WHATWG urls
|
|
208
|
+
* @private
|
|
209
|
+
* @param query - Query parameters to generate the query string from
|
|
210
|
+
* @param useDefaultParams - Use defaultParams or not
|
|
211
|
+
* @param defaultParams - Default params
|
|
212
|
+
*/
|
|
213
|
+
private static search(
|
|
214
|
+
query: QueryParams,
|
|
215
|
+
useDefaultParams: boolean,
|
|
216
|
+
defaultParams: QueryParams,
|
|
217
|
+
): string {
|
|
218
|
+
const params = useDefaultParams ? cloneDefined(defaultParams, query) : cloneDefined(query);
|
|
219
|
+
return Object.keys(params)
|
|
220
|
+
.filter((p) => params[p] !== '')
|
|
221
|
+
.map((p) => `${encode(p)}=${encode(String(params[p]))}`)
|
|
222
|
+
.join('&');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export default RESTClient;
|
package/src/SDKError.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Copyright 2024 Schibsted Products & Technology AS. Licensed under the terms of the MIT license.
|
|
2
|
+
* See LICENSE.md in the project root.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Note: this module can't have any internal dependencies because it's used in ./validate which
|
|
7
|
+
* in turn is used as a dependency to a lot of other modules. Doing so may create a circular
|
|
8
|
+
* dependency that's hard to debug in Node.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const STRINGIFY_TYPES = ['boolean', 'number', 'string'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Custom error class thrown by all SDK methods on failure.
|
|
15
|
+
*
|
|
16
|
+
* All rejected promises from API calls reject with an instance of this class.
|
|
17
|
+
*/
|
|
18
|
+
export default class SDKError extends Error {
|
|
19
|
+
/** HTTP status code from the server error payload;
|
|
20
|
+
* declared explicitly to allow numeric comparisons without type-casting.
|
|
21
|
+
*/
|
|
22
|
+
code?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Properties copied from the server error object.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param message - Human-readable error message
|
|
31
|
+
* @param errorObject - Optional server error payload. All enumerable properties are
|
|
32
|
+
* shallow-copied onto this instance.
|
|
33
|
+
*/
|
|
34
|
+
constructor(message: string, errorObject?: Record<string, unknown>) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'SDKError';
|
|
37
|
+
|
|
38
|
+
if (errorObject) {
|
|
39
|
+
try {
|
|
40
|
+
Object.assign(this, errorObject);
|
|
41
|
+
} catch {
|
|
42
|
+
// silent
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns a multi-line string with the error name, message, and any other properties copied
|
|
49
|
+
* from the server error payload.
|
|
50
|
+
*/
|
|
51
|
+
toString(): string {
|
|
52
|
+
const ret = `${this.name}: ${this.message}`;
|
|
53
|
+
const additionalInfo = Object.keys(this)
|
|
54
|
+
.filter((key) => key !== 'name' && STRINGIFY_TYPES.includes(typeof this[key]))
|
|
55
|
+
.map((key) => ` ${key}: ${this[key]}`)
|
|
56
|
+
.join('\n');
|
|
57
|
+
return additionalInfo ? `${ret}\n${additionalInfo}` : ret;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -2,45 +2,47 @@
|
|
|
2
2
|
* See LICENSE.md in the project root.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
5
|
import SDKError from './SDKError.js';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* Check whether we are able to use web storage
|
|
11
|
-
* @param
|
|
9
|
+
* @param storeProvider - A function to return a WebStorage instance (either
|
|
12
10
|
* `sessionStorage` or `localStorage` from a `Window` object)
|
|
13
11
|
* @private
|
|
14
|
-
* @returns {boolean}
|
|
15
12
|
*/
|
|
16
|
-
function webStorageWorks(storeProvider) {
|
|
13
|
+
function webStorageWorks(storeProvider: (() => Storage) | undefined): boolean {
|
|
17
14
|
if (!storeProvider) {
|
|
18
15
|
return false;
|
|
19
16
|
}
|
|
20
17
|
try {
|
|
21
18
|
const store = storeProvider();
|
|
22
|
-
const randomKey = 'x-x-x-x'.replace(/x/g, () => Math.random());
|
|
19
|
+
const randomKey = 'x-x-x-x'.replace(/x/g, () => String(Math.random()));
|
|
23
20
|
const testValue = 'TEST-VALUE';
|
|
24
21
|
store.setItem(randomKey, testValue);
|
|
25
22
|
const val = store.getItem(randomKey);
|
|
26
23
|
store.removeItem(randomKey);
|
|
27
|
-
return
|
|
28
|
-
} catch
|
|
24
|
+
return val === testValue;
|
|
25
|
+
} catch {
|
|
29
26
|
return false;
|
|
30
27
|
}
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
/**
|
|
34
|
-
*
|
|
31
|
+
* Cache implementation used when web storage is available. Wraps a `Storage` instance.
|
|
35
32
|
* @private
|
|
36
33
|
*/
|
|
37
34
|
class WebStorageCache {
|
|
35
|
+
store: Storage;
|
|
36
|
+
get: (key: string) => string | null;
|
|
37
|
+
set: (key: string, value: string) => void;
|
|
38
|
+
delete: (key: string) => void;
|
|
39
|
+
|
|
38
40
|
/**
|
|
39
41
|
* Create web storage cache object
|
|
40
|
-
* @param
|
|
42
|
+
* @param store - A reference to either `sessionStorage` or `localStorage` from a
|
|
41
43
|
* `Window` object
|
|
42
44
|
*/
|
|
43
|
-
constructor(store) {
|
|
45
|
+
constructor(store: Storage) {
|
|
44
46
|
this.store = store;
|
|
45
47
|
this.get = (key) => this.store.getItem(key);
|
|
46
48
|
this.set = (key, value) => this.store.setItem(key, value);
|
|
@@ -49,35 +51,49 @@ class WebStorageCache {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
|
-
*
|
|
54
|
+
* Cache implementation used when web storage is not available. Stores entries in an in-memory object.
|
|
53
55
|
* @private
|
|
54
56
|
*/
|
|
55
57
|
class LiteralCache {
|
|
58
|
+
store: Record<string, string>;
|
|
59
|
+
get: (key: string) => string | undefined;
|
|
60
|
+
set: (key: string, value: string) => void;
|
|
61
|
+
delete: (key: string) => void;
|
|
62
|
+
|
|
56
63
|
/**
|
|
57
64
|
* Create JS object literal cache object
|
|
58
65
|
*/
|
|
59
66
|
constructor() {
|
|
60
67
|
this.store = {};
|
|
61
68
|
this.get = (key) => this.store[key];
|
|
62
|
-
this.set = (key, value) =>
|
|
63
|
-
|
|
69
|
+
this.set = (key, value) => {
|
|
70
|
+
this.store[key] = value;
|
|
71
|
+
};
|
|
72
|
+
this.delete = (key) => {
|
|
73
|
+
delete this.store[key];
|
|
74
|
+
};
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
|
|
78
|
+
type CacheEntry = { expiresOn: number; value: unknown };
|
|
79
|
+
|
|
80
|
+
const maxExpiresIn = 2 ** 31 - 1;
|
|
68
81
|
|
|
69
82
|
/**
|
|
70
83
|
* Cache class that attempts WebStorage (session/local storage), and falls back to JS object literal
|
|
71
84
|
* @private
|
|
72
85
|
*/
|
|
73
86
|
export default class Cache {
|
|
87
|
+
cache: WebStorageCache | LiteralCache;
|
|
88
|
+
type: string;
|
|
89
|
+
|
|
74
90
|
/**
|
|
75
|
-
* @param
|
|
91
|
+
* @param storeProvider - A function to return a WebStorage instance (either
|
|
76
92
|
* `sessionStorage` or `localStorage` from a `Window` object)
|
|
77
93
|
* @throws {SDKError} - If sessionStorage or localStorage are not accessible
|
|
78
94
|
*/
|
|
79
|
-
constructor(storeProvider) {
|
|
80
|
-
if (webStorageWorks(storeProvider)) {
|
|
95
|
+
constructor(storeProvider?: () => Storage) {
|
|
96
|
+
if (storeProvider && webStorageWorks(storeProvider)) {
|
|
81
97
|
this.cache = new WebStorageCache(storeProvider());
|
|
82
98
|
this.type = 'WebStorage';
|
|
83
99
|
} else {
|
|
@@ -88,46 +104,46 @@ export default class Cache {
|
|
|
88
104
|
|
|
89
105
|
/**
|
|
90
106
|
* Get a value from cache (checks that the object has not expired)
|
|
91
|
-
* @param
|
|
107
|
+
* @param key
|
|
92
108
|
* @private
|
|
93
|
-
* @returns {*} - The value if it exists, otherwise null
|
|
94
109
|
*/
|
|
95
|
-
get(key) {
|
|
110
|
+
get(key: string): unknown {
|
|
96
111
|
/**
|
|
97
112
|
* JSON.parse safe wrapper
|
|
98
|
-
* @param
|
|
99
|
-
* @returns {*} parsed value or null if failed to parse
|
|
113
|
+
* @param raw
|
|
100
114
|
*/
|
|
101
|
-
function getObj(raw) {
|
|
115
|
+
function getObj(raw: string | null | undefined): CacheEntry | null {
|
|
116
|
+
if (raw == null) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
102
119
|
try {
|
|
103
120
|
return JSON.parse(raw);
|
|
104
|
-
} catch
|
|
121
|
+
} catch {
|
|
105
122
|
return null;
|
|
106
123
|
}
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
try {
|
|
110
127
|
const raw = this.cache.get(key);
|
|
111
|
-
|
|
128
|
+
const obj = getObj(raw);
|
|
112
129
|
if (obj && Number.isInteger(obj.expiresOn) && obj.expiresOn > Date.now()) {
|
|
113
130
|
return obj.value;
|
|
114
131
|
}
|
|
115
132
|
this.delete(key);
|
|
116
133
|
return null;
|
|
117
134
|
} catch (e) {
|
|
118
|
-
throw new SDKError(e);
|
|
135
|
+
throw new SDKError(String(e));
|
|
119
136
|
}
|
|
120
137
|
}
|
|
121
138
|
|
|
122
139
|
/**
|
|
123
140
|
* Set a cache entry
|
|
124
|
-
* @param
|
|
125
|
-
* @param
|
|
126
|
-
* @param
|
|
141
|
+
* @param key
|
|
142
|
+
* @param value
|
|
143
|
+
* @param expiresIn - Value in milliseconds until the entry expires
|
|
127
144
|
* @private
|
|
128
|
-
* @returns {void}
|
|
129
145
|
*/
|
|
130
|
-
set(key, value, expiresIn = 0) {
|
|
146
|
+
set(key: string, value: unknown, expiresIn = 0): void {
|
|
131
147
|
if (expiresIn <= 0) {
|
|
132
148
|
return;
|
|
133
149
|
}
|
|
@@ -138,21 +154,20 @@ export default class Cache {
|
|
|
138
154
|
this.cache.set(key, JSON.stringify({ expiresOn, value }));
|
|
139
155
|
setTimeout(() => this.delete(key), expiresIn);
|
|
140
156
|
} catch (e) {
|
|
141
|
-
throw new SDKError(e);
|
|
157
|
+
throw new SDKError(String(e));
|
|
142
158
|
}
|
|
143
159
|
}
|
|
144
160
|
|
|
145
161
|
/**
|
|
146
162
|
* Delete a cache entry
|
|
147
|
-
* @param
|
|
163
|
+
* @param key
|
|
148
164
|
* @private
|
|
149
|
-
* @returns {void}
|
|
150
165
|
*/
|
|
151
|
-
delete(key) {
|
|
166
|
+
delete(key: string): void {
|
|
152
167
|
try {
|
|
153
168
|
this.cache.delete(key);
|
|
154
169
|
} catch (e) {
|
|
155
|
-
throw new SDKError(e);
|
|
170
|
+
throw new SDKError(String(e));
|
|
156
171
|
}
|
|
157
172
|
}
|
|
158
173
|
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* See LICENSE.md in the project root.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
5
|
/*
|
|
8
6
|
* This file declares configs that are essentially part of how the SDK works and interacts with our
|
|
9
7
|
* backend servers.
|
|
@@ -18,34 +16,11 @@
|
|
|
18
16
|
*/
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
|
-
* Core configuration used by the SDK
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* @prop {string} ENDPOINTS.SPiD.LOCAL - Local endpoint (for Identity team)
|
|
27
|
-
* @prop {string} ENDPOINTS.SPiD.DEV - Dev environment (for Identity team)
|
|
28
|
-
* @prop {string} ENDPOINTS.SPiD.PRE - Staging environment
|
|
29
|
-
* @prop {string} ENDPOINTS.SPiD.PRO - Production environment Sweden
|
|
30
|
-
* @prop {string} ENDPOINTS.SPiD.PRO_NO - Production environment Norway
|
|
31
|
-
* @prop {string} ENDPOINTS.SPiD.PRO_FI - Production environment Finland
|
|
32
|
-
* @prop {string} ENDPOINTS.SPiD.PRO_DK - Production environment Denmark
|
|
33
|
-
* @prop {object} ENDPOINTS.BFF - Endpoints used with new GDPR-compliant web flows
|
|
34
|
-
* @prop {string} ENDPOINTS.BFF.LOCAL - Local endpoint (for Identity team)
|
|
35
|
-
* @prop {string} ENDPOINTS.BFF.DEV - Dev environment (for Identity team)
|
|
36
|
-
* @prop {string} ENDPOINTS.BFF.PRE - Staging environment
|
|
37
|
-
* @prop {string} ENDPOINTS.BFF.PRO - Production environment Sweden
|
|
38
|
-
* @prop {string} ENDPOINTS.BFF.PRO_NO - Production environment Norway
|
|
39
|
-
* @prop {string} ENDPOINTS.BFF.PRO_FI - Production environment Finland
|
|
40
|
-
* @prop {string} ENDPOINTS.BFF.PRO_DK - Production environment Denmark
|
|
41
|
-
* @prop {object} ENDPOINTS.SESSION_SERVICE - Endpoints to check global user session data
|
|
42
|
-
* @prop {string} ENDPOINTS.SESSION_SERVICE.LOCAL - Local endpoint (for Identity team)
|
|
43
|
-
* @prop {string} ENDPOINTS.SESSION_SERVICE.DEV - Dev environment (for Identity team)
|
|
44
|
-
* @prop {string} ENDPOINTS.SESSION_SERVICE.PRE - Staging environment
|
|
45
|
-
* @prop {string} ENDPOINTS.SESSION_SERVICE.PRO - Production environment Sweden
|
|
46
|
-
* @prop {string} ENDPOINTS.SESSION_SERVICE.PRO_NO - Production environment Norway
|
|
47
|
-
* @prop {string} ENDPOINTS.SESSION_SERVICE.PRO_FI - Production environment Finland
|
|
48
|
-
* @prop {string} ENDPOINTS.SESSION_SERVICE.PRO_DK - Production environment Denmark
|
|
19
|
+
* Core configuration used by the SDK.
|
|
20
|
+
* - `ENDPOINTS.SPiD` — SPiD endpoints per environment
|
|
21
|
+
* - `ENDPOINTS.BFF` — Endpoints used with new GDPR-compliant web flows
|
|
22
|
+
* - `ENDPOINTS.SESSION_SERVICE` — Endpoints to check global user session data
|
|
23
|
+
*
|
|
49
24
|
*/
|
|
50
25
|
const config = {
|
|
51
26
|
ENDPOINTS: {
|
|
@@ -85,8 +60,8 @@ const config = {
|
|
|
85
60
|
PRO_NO: 'spid.no',
|
|
86
61
|
PRO_FI: 'schibsted.fi',
|
|
87
62
|
PRO_DK: 'schibsted.dk',
|
|
88
|
-
}
|
|
89
|
-
};
|
|
63
|
+
},
|
|
64
|
+
} as const;
|
|
90
65
|
|
|
91
66
|
export default config;
|
|
92
67
|
export const ENDPOINTS = config.ENDPOINTS;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import './globals.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Names of the SDK instance properties that can be registered on the global {@link Window} object.
|
|
5
|
+
*/
|
|
6
|
+
type Property = 'schIdentity' | 'schMonetization';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Registers an SDK instance on the global {@link Window} object's given property and dispatches a ready event.
|
|
10
|
+
*
|
|
11
|
+
* The dispatched event is a {@link CustomEvent} fired on `global` (i.e. `window`) with:
|
|
12
|
+
* - name: `${property}:ready` (e.g. `"schIdentity:ready"` or `"schMonetization:ready"`)
|
|
13
|
+
* - `detail.instance`: the registered SDK instance
|
|
14
|
+
*
|
|
15
|
+
* @typeParam T - Constrained to {@link Property}; inferred from the `property` argument.
|
|
16
|
+
* @param global - The `Window` object to register on.
|
|
17
|
+
* @param property - The window property name to assign the instance to, 'schIdentity' or 'schMonetization'.
|
|
18
|
+
* @param instance - The SDK instance to register. Must be non-nullable.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* registerAndDispatchInGlobal(window, 'schIdentity', identityInstance);
|
|
22
|
+
* / window.schIdentity === identityInstance
|
|
23
|
+
* / CustomEvent 'schIdentity:ready' has been dispatched on window
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export const registerAndDispatchInGlobal = <T extends Property>(
|
|
27
|
+
global: Window,
|
|
28
|
+
property: T,
|
|
29
|
+
instance: NonNullable<Window[T]>,
|
|
30
|
+
): void => {
|
|
31
|
+
if (!global[property]) {
|
|
32
|
+
global[property] = instance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
global.dispatchEvent(
|
|
36
|
+
//TODO -> https://schibsted.ghe.com/connect/team/issues/645
|
|
37
|
+
new CustomEvent(`${property}:ready`, { detail: { instance: global[property] } }),
|
|
38
|
+
);
|
|
39
|
+
};
|
package/src/globals.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Identity } from './identity.js';
|
|
2
|
+
import type { Monetization } from './monetization.js';
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
schIdentity?: Identity;
|
|
7
|
+
schMonetization?: Monetization;
|
|
8
|
+
SPiD?: { Talk?: { response?: (callbackName: string, data: unknown) => unknown } };
|
|
9
|
+
}
|
|
10
|
+
}
|