@openstax/ts-utils 1.20.3 → 1.20.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/dist/cjs/fetch/fetchStatusRetry.d.ts +2 -3
- package/dist/cjs/fetch/fetchStatusRetry.js +10 -18
- package/dist/cjs/misc/helpers.d.ts +4 -2
- package/dist/cjs/misc/helpers.js +17 -5
- package/dist/cjs/services/apiGateway/index.js +1 -1
- package/dist/cjs/services/authProvider/decryption.js +6 -2
- package/dist/cjs/services/authProvider/index.d.ts +2 -0
- package/dist/cjs/services/authProvider/subrequest.js +6 -2
- package/dist/cjs/services/lrsGateway/index.d.ts +5 -1
- package/dist/cjs/services/lrsGateway/index.js +17 -12
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/fetch/fetchStatusRetry.d.ts +2 -3
- package/dist/esm/fetch/fetchStatusRetry.js +10 -18
- package/dist/esm/misc/helpers.d.ts +4 -2
- package/dist/esm/misc/helpers.js +15 -3
- package/dist/esm/services/apiGateway/index.js +1 -1
- package/dist/esm/services/authProvider/decryption.js +6 -2
- package/dist/esm/services/authProvider/index.d.ts +2 -0
- package/dist/esm/services/authProvider/subrequest.js +6 -2
- package/dist/esm/services/lrsGateway/index.d.ts +5 -1
- package/dist/esm/services/lrsGateway/index.js +18 -13
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { RetryOptions } from '../misc/helpers';
|
|
1
2
|
import { GenericFetch } from '.';
|
|
2
|
-
interface Options {
|
|
3
|
+
interface Options extends RetryOptions {
|
|
3
4
|
status?: number[];
|
|
4
|
-
onFail?: boolean;
|
|
5
|
-
retries: number;
|
|
6
5
|
}
|
|
7
6
|
export declare const fetchStatusRetry: (base: GenericFetch, options: Options) => GenericFetch;
|
|
8
7
|
export {};
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fetchStatusRetry = void 0;
|
|
4
|
+
const helpers_1 = require("../misc/helpers");
|
|
4
5
|
const fetchStatusRetry = (base, options) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return fetchTry(retries - 1, ...params);
|
|
15
|
-
}
|
|
16
|
-
else if (shouldHandleStatus) {
|
|
17
|
-
throw new Error(`fetch failed after ${options.retries} retries. ${params[0]} -- ${r.status}: ${await r.text()}`);
|
|
18
|
-
}
|
|
19
|
-
return r;
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
return (...params) => fetchTry(options.retries, ...params);
|
|
6
|
+
return (...params) => (0, helpers_1.retryWithDelay)(() => base(...params).then(r => {
|
|
7
|
+
var _a;
|
|
8
|
+
if ((_a = options.status) === null || _a === void 0 ? void 0 : _a.includes(r.status)) {
|
|
9
|
+
return r.text().then(text => {
|
|
10
|
+
throw new Error(`fetch failed ${params[0]} -- ${r.status}: ${text}`);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return r;
|
|
14
|
+
}), options);
|
|
23
15
|
};
|
|
24
16
|
exports.fetchStatusRetry = fetchStatusRetry;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Logger } from '../services/logger';
|
|
1
2
|
/**
|
|
2
3
|
* Returns a function that gets the value of the given key from an object
|
|
3
4
|
*
|
|
@@ -44,7 +45,8 @@ declare type FlowChainArg<C> = C extends [infer F1, ...infer _] ? F1 extends Flo
|
|
|
44
45
|
* this is like lodash/flow but it uses a recursive type instead of hard-coding parameters
|
|
45
46
|
*/
|
|
46
47
|
export declare const flow: <C extends AnyFlowFn[]>(...chain: C) => (param: FlowChainArg<C>) => FlowResult<C, FlowChainArg<C>>;
|
|
47
|
-
declare type RetryOptions = {
|
|
48
|
+
export declare type RetryOptions = {
|
|
49
|
+
logger?: Logger;
|
|
48
50
|
wait?: number;
|
|
49
51
|
splay?: number;
|
|
50
52
|
retries?: number;
|
|
@@ -64,7 +66,7 @@ declare type RetryOptions = {
|
|
|
64
66
|
* initial wait cliff with incremental increases, then maybe you'd set this. default: 0
|
|
65
67
|
* @returns A promise that resolves with the result of the function, or rejects with the last error.
|
|
66
68
|
*/
|
|
67
|
-
export declare const retryWithDelay: <R>(fn: () => Promise<R>, options?: RetryOptions | undefined) => Promise<R>;
|
|
69
|
+
export declare const retryWithDelay: <R>(fn: (abort?: ((e: Error) => never) | undefined) => Promise<R>, options?: RetryOptions | undefined) => Promise<R>;
|
|
68
70
|
/**
|
|
69
71
|
* a shameful helper to avoid needing to test code coverage of branches
|
|
70
72
|
*/
|
package/dist/cjs/misc/helpers.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tuple = exports.roundToPrecision = exports.memoize = exports.once = exports.mapFind = exports.fnIf = exports.retryWithDelay = exports.flow = exports.coerceArray = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
|
|
2
4
|
/*
|
|
3
5
|
* there was a reason i made these instead of using lodash/fp but i forget what it was. i think maybe
|
|
4
6
|
* these do more validation that the second function gets a compatible object.
|
|
5
7
|
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.tuple = exports.roundToPrecision = exports.memoize = exports.once = exports.mapFind = exports.fnIf = exports.retryWithDelay = exports.flow = exports.coerceArray = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
|
|
8
8
|
/**
|
|
9
9
|
* Returns a function that gets the value of the given key from an object
|
|
10
10
|
*
|
|
@@ -76,11 +76,23 @@ const retryWithDelay = (fn, options) => {
|
|
|
76
76
|
if (n >= retries) {
|
|
77
77
|
return fn();
|
|
78
78
|
}
|
|
79
|
+
let aborted = false;
|
|
79
80
|
const timeout = (n + 1) * wait + (Math.random() * 2 - 1) * splay * wait;
|
|
80
|
-
const retry = () => new Promise((resolve, reject) => {
|
|
81
|
-
|
|
81
|
+
const retry = (e) => new Promise((resolve, reject) => {
|
|
82
|
+
var _a;
|
|
83
|
+
if (aborted) {
|
|
84
|
+
reject(e);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
(_a = options === null || options === void 0 ? void 0 : options.logger) === null || _a === void 0 ? void 0 : _a.log(`failed try ${n} of ${retries}. ${e.message}`);
|
|
88
|
+
setTimeout(() => (0, exports.retryWithDelay)(fn, { ...options, n: n + 1 }).then(resolve, reject), timeout);
|
|
89
|
+
}
|
|
82
90
|
});
|
|
83
|
-
|
|
91
|
+
const abort = (e) => {
|
|
92
|
+
aborted = true;
|
|
93
|
+
throw e;
|
|
94
|
+
};
|
|
95
|
+
return fn(abort).catch(retry);
|
|
84
96
|
};
|
|
85
97
|
exports.retryWithDelay = retryWithDelay;
|
|
86
98
|
/**
|
|
@@ -58,7 +58,7 @@ const makeRouteClient = (initializer, config, route, authProvider) => {
|
|
|
58
58
|
const url = await renderUrl({ params, query });
|
|
59
59
|
const body = payload ? JSON.stringify(payload) : undefined;
|
|
60
60
|
const baseOptions = (0, __1.merge)((await (authProvider === null || authProvider === void 0 ? void 0 : authProvider.getAuthorizedFetchConfig())) || {}, fetchConfig || {});
|
|
61
|
-
const fetcher = (0, fetchStatusRetry_1.fetchStatusRetry)(initializer.fetch, { retries: 1, status: [502]
|
|
61
|
+
const fetcher = (0, fetchStatusRetry_1.fetchStatusRetry)(initializer.fetch, { retries: 1, status: [502] });
|
|
62
62
|
return fetcher(url, (0, __1.merge)(baseOptions, {
|
|
63
63
|
method: route.method,
|
|
64
64
|
body,
|
|
@@ -12,7 +12,7 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
12
12
|
const cookieName = (0, helpers_1.once)(() => (0, resolveConfigValue_1.resolveConfigValue)(config.cookieName));
|
|
13
13
|
const encryptionPrivateKey = (0, helpers_1.once)(() => (0, resolveConfigValue_1.resolveConfigValue)(config.encryptionPrivateKey));
|
|
14
14
|
const signaturePublicKey = (0, helpers_1.once)(() => (0, resolveConfigValue_1.resolveConfigValue)(config.signaturePublicKey));
|
|
15
|
-
return ({ request }) => {
|
|
15
|
+
return ({ request, logger }) => {
|
|
16
16
|
let user;
|
|
17
17
|
const getAuthorizedFetchConfig = async () => {
|
|
18
18
|
const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName());
|
|
@@ -36,7 +36,11 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
36
36
|
if ('error' in result && result.error == 'expired token') {
|
|
37
37
|
throw new errors_1.SessionExpiredError();
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
if ('user' in result) {
|
|
40
|
+
logger.setContext({ user: result.user.uuid });
|
|
41
|
+
return result.user;
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
40
44
|
};
|
|
41
45
|
return {
|
|
42
46
|
getAuthorizedFetchConfig,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FetchConfig } from '../../fetch';
|
|
2
2
|
import type { HttpHeaders, QueryParams } from '../../routing';
|
|
3
|
+
import type { Logger } from '../logger';
|
|
3
4
|
export declare type ConsentPreferences = {
|
|
4
5
|
consent_preferences: {
|
|
5
6
|
accepted: string[];
|
|
@@ -49,6 +50,7 @@ export declare type CookieAuthProviderRequest = {
|
|
|
49
50
|
};
|
|
50
51
|
export declare type CookieAuthProvider<T extends AuthProvider = AuthProvider> = (inputs: {
|
|
51
52
|
request: CookieAuthProviderRequest;
|
|
53
|
+
logger: Logger;
|
|
52
54
|
}) => T;
|
|
53
55
|
export declare type StubAuthProvider = (user: User | undefined) => AuthProvider;
|
|
54
56
|
export declare const stubAuthProvider: (user?: User | undefined) => AuthProvider;
|
|
@@ -13,7 +13,7 @@ const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
|
13
13
|
const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'subrequest')];
|
|
14
14
|
const cookieName = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.cookieName));
|
|
15
15
|
const accountsBase = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.accountsBase));
|
|
16
|
-
return ({ request }) => {
|
|
16
|
+
return ({ request, logger }) => {
|
|
17
17
|
let user;
|
|
18
18
|
const getAuthorizedFetchConfig = async () => {
|
|
19
19
|
const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName());
|
|
@@ -29,8 +29,12 @@ const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
31
|
const headers = { cookie: cookie_1.default.serialize(resolvedCookieName, token) };
|
|
32
|
-
|
|
32
|
+
const user = await initializer.fetch((await accountsBase()).replace(/\/+$/, '') + '/api/user', { headers })
|
|
33
33
|
.then(response => response.json());
|
|
34
|
+
if (user) {
|
|
35
|
+
logger.setContext({ user: user.uuid });
|
|
36
|
+
}
|
|
37
|
+
return user;
|
|
34
38
|
};
|
|
35
39
|
return {
|
|
36
40
|
getAuthorizedFetchConfig,
|
|
@@ -2,6 +2,7 @@ import { ConfigProviderForConfig } from '../../config';
|
|
|
2
2
|
import { GenericFetch } from '../../fetch';
|
|
3
3
|
import { WithRequired } from '../../types';
|
|
4
4
|
import { AuthProvider } from '../authProvider';
|
|
5
|
+
import { Logger } from '../logger';
|
|
5
6
|
declare type Config = {
|
|
6
7
|
lrsHost: string;
|
|
7
8
|
lrsAuthorization: string;
|
|
@@ -80,7 +81,10 @@ export declare type LrsProvider = ReturnType<ReturnType<typeof lrsGateway>>;
|
|
|
80
81
|
export declare const lrsGateway: <C extends string = "lrs">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
|
|
81
82
|
lrsHost: import("../../config").ConfigValueProvider<string>;
|
|
82
83
|
lrsAuthorization: import("../../config").ConfigValueProvider<string>;
|
|
83
|
-
}; }) => (authProvider
|
|
84
|
+
}; }) => ({ authProvider, logger }: {
|
|
85
|
+
authProvider: AuthProvider;
|
|
86
|
+
logger: Logger;
|
|
87
|
+
}) => {
|
|
84
88
|
putXapiStatements: (statements: Array<(Pick<XapiStatement, 'object' | 'verb' | 'context' | 'result'> & {
|
|
85
89
|
id?: string;
|
|
86
90
|
}) | UXapiStatement>) => Promise<EagerXapiStatement[]>;
|
|
@@ -31,14 +31,20 @@ const config_1 = require("../../config");
|
|
|
31
31
|
const errors_1 = require("../../errors");
|
|
32
32
|
const fetchStatusRetry_1 = require("../../fetch/fetchStatusRetry");
|
|
33
33
|
const guards_1 = require("../../guards");
|
|
34
|
+
const helpers_1 = require("../../misc/helpers");
|
|
34
35
|
const routing_1 = require("../../routing");
|
|
35
36
|
const addStatementDefaultFields_1 = require("./addStatementDefaultFields");
|
|
36
37
|
const lrsGateway = (initializer) => (configProvider) => {
|
|
37
38
|
const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'lrs')];
|
|
38
39
|
const lrsHost = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.lrsHost));
|
|
39
40
|
const lrsAuthorization = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.lrsAuthorization));
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
return ({ authProvider, logger }) => {
|
|
42
|
+
const fetcher = (0, fetchStatusRetry_1.fetchStatusRetry)(initializer.fetch, {
|
|
43
|
+
logger,
|
|
44
|
+
retries: 2,
|
|
45
|
+
wait: 1000,
|
|
46
|
+
status: [502]
|
|
47
|
+
});
|
|
42
48
|
// Note: This method actually uses POST
|
|
43
49
|
const putXapiStatements = async (statements) => {
|
|
44
50
|
const user = (0, assertions_1.assertDefined)(await authProvider.getUser(), new errors_1.UnauthorizedError);
|
|
@@ -62,8 +68,7 @@ ${await response.text()}`);
|
|
|
62
68
|
};
|
|
63
69
|
// Note: This code does not currently handle a single statement response,
|
|
64
70
|
// which can return 404 if the statement is not found
|
|
65
|
-
const formatGetXapiStatementsResponse = async (
|
|
66
|
-
const response = await responsePromise;
|
|
71
|
+
const formatGetXapiStatementsResponse = async (response) => {
|
|
67
72
|
if (response.status !== 200) {
|
|
68
73
|
throw new Error(`Unexpected LRS GET statements response code ${response.status} with body:
|
|
69
74
|
|
|
@@ -71,12 +76,12 @@ ${await response.text()}`);
|
|
|
71
76
|
}
|
|
72
77
|
return response.json();
|
|
73
78
|
};
|
|
74
|
-
const getMoreXapiStatements = async (more) =>
|
|
79
|
+
const getMoreXapiStatements = async (more) => fetcher((await lrsHost()).replace(/\/+$/, '') + more, {
|
|
75
80
|
headers: {
|
|
76
81
|
Authorization: await lrsAuthorization(),
|
|
77
82
|
'X-Experience-API-Version': '1.0.0',
|
|
78
83
|
},
|
|
79
|
-
}));
|
|
84
|
+
}).then(formatGetXapiStatementsResponse);
|
|
80
85
|
const fetchXapiStatements = async ({ user, anyUser, ...options }) => fetcher((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements?' + queryString.stringify({
|
|
81
86
|
...options,
|
|
82
87
|
...(anyUser === true ? {} : {
|
|
@@ -98,18 +103,18 @@ ${await response.text()}`);
|
|
|
98
103
|
const { ensureSync, ...fetchParams } = params;
|
|
99
104
|
if (ensureSync) {
|
|
100
105
|
const date = new Date();
|
|
101
|
-
return (0,
|
|
102
|
-
|
|
103
|
-
const response = await
|
|
106
|
+
return (0, helpers_1.retryWithDelay)(async (abort) => {
|
|
107
|
+
// this function retries for the consistency, if the fetch errors just abort
|
|
108
|
+
const response = await fetchXapiStatements(fetchParams).catch(abort);
|
|
104
109
|
const consistentThrough = response.headers.get('X-Experience-API-Consistent-Through');
|
|
105
110
|
if (!consistentThrough || new Date(consistentThrough) < date) {
|
|
106
111
|
throw new Error(`xAPI consistent through ${consistentThrough}; not in sync with current date ${date}.`);
|
|
107
112
|
}
|
|
108
|
-
return formatGetXapiStatementsResponse(
|
|
109
|
-
}, { retries: 4 });
|
|
113
|
+
return formatGetXapiStatementsResponse(response);
|
|
114
|
+
}, { retries: 4, logger });
|
|
110
115
|
}
|
|
111
116
|
else {
|
|
112
|
-
return
|
|
117
|
+
return fetchXapiStatements(fetchParams).then(formatGetXapiStatementsResponse);
|
|
113
118
|
}
|
|
114
119
|
};
|
|
115
120
|
const getAllXapiStatements = async ({ fetchUntil, ...params }) => {
|