@openstax/ts-utils 1.1.27 → 1.1.29
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/{assertions.d.ts → cjs/assertions.d.ts} +0 -0
- package/dist/{assertions.js → cjs/assertions.js} +0 -0
- package/dist/{aws → cjs/aws}/securityTokenService.d.ts +0 -0
- package/dist/{aws → cjs/aws}/securityTokenService.js +0 -0
- package/dist/{aws → cjs/aws}/ssmService.d.ts +0 -0
- package/dist/{aws → cjs/aws}/ssmService.js +0 -0
- package/dist/cjs/config/awsAccountConfig.d.ts +5 -0
- package/dist/cjs/config/awsAccountConfig.js +35 -0
- package/dist/cjs/config/awsParameterConfig.d.ts +2 -0
- package/dist/cjs/config/awsParameterConfig.js +18 -0
- package/dist/cjs/config/envConfig.d.ts +3 -0
- package/dist/cjs/config/envConfig.js +35 -0
- package/dist/cjs/config/index.d.ts +21 -0
- package/dist/cjs/config/index.js +39 -0
- package/dist/cjs/config/lambdaParameterConfig.d.ts +2 -0
- package/dist/cjs/config/lambdaParameterConfig.js +36 -0
- package/dist/cjs/config/replaceConfig.d.ts +4 -0
- package/dist/cjs/config/replaceConfig.js +12 -0
- package/dist/cjs/config/resolveConfigValue.d.ts +2 -0
- package/dist/cjs/config/resolveConfigValue.js +12 -0
- package/dist/{config.d.ts → cjs/config.d.ts} +0 -0
- package/dist/{config.js → cjs/config.js} +0 -0
- package/dist/{errors.d.ts → cjs/errors.d.ts} +0 -0
- package/dist/{errors.js → cjs/errors.js} +0 -0
- package/dist/{fetch.d.ts → cjs/fetch.d.ts} +0 -0
- package/dist/{fetch.js → cjs/fetch.js} +0 -0
- package/dist/{guards.d.ts → cjs/guards.d.ts} +0 -0
- package/dist/{guards.js → cjs/guards.js} +0 -0
- package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
- package/dist/{index.js → cjs/index.js} +0 -0
- package/dist/{middleware.d.ts → cjs/middleware.d.ts} +0 -0
- package/dist/{middleware.js → cjs/middleware.js} +0 -0
- package/dist/{pagination.d.ts → cjs/pagination.d.ts} +0 -0
- package/dist/{pagination.js → cjs/pagination.js} +0 -0
- package/dist/{profile.d.ts → cjs/profile.d.ts} +0 -0
- package/dist/{profile.js → cjs/profile.js} +0 -0
- package/dist/{routing.d.ts → cjs/routing.d.ts} +0 -0
- package/dist/{routing.js → cjs/routing.js} +0 -0
- package/dist/{services → cjs/services}/apiGateway/index.d.ts +0 -0
- package/dist/{services → cjs/services}/apiGateway/index.js +0 -0
- package/dist/{services → cjs/services}/authProvider/browser.d.ts +0 -0
- package/dist/{services → cjs/services}/authProvider/browser.js +0 -0
- package/dist/{services → cjs/services}/authProvider/decryption.d.ts +0 -0
- package/dist/{services → cjs/services}/authProvider/decryption.js +0 -0
- package/dist/{services → cjs/services}/authProvider/index.d.ts +0 -0
- package/dist/{services → cjs/services}/authProvider/index.js +0 -0
- package/dist/{services → cjs/services}/authProvider/subrequest.d.ts +0 -0
- package/dist/{services → cjs/services}/authProvider/subrequest.js +0 -0
- package/dist/{services → cjs/services}/authProvider/utils/embeddedAuthProvider.d.ts +0 -0
- package/dist/{services → cjs/services}/authProvider/utils/embeddedAuthProvider.js +0 -0
- package/dist/{services → cjs/services}/exercisesGateway/index.d.ts +0 -0
- package/dist/{services → cjs/services}/exercisesGateway/index.js +0 -0
- package/dist/{services → cjs/services}/lrsGateway/attempt-utils.d.ts +0 -0
- package/dist/{services → cjs/services}/lrsGateway/attempt-utils.js +0 -0
- package/dist/{services → cjs/services}/lrsGateway/file-system.d.ts +0 -0
- package/dist/{services → cjs/services}/lrsGateway/file-system.js +0 -0
- package/dist/{services → cjs/services}/lrsGateway/index.d.ts +0 -0
- package/dist/{services → cjs/services}/lrsGateway/index.js +0 -0
- package/dist/{services → cjs/services}/searchProvider/index.d.ts +0 -0
- package/dist/{services → cjs/services}/searchProvider/index.js +0 -0
- package/dist/{services → cjs/services}/searchProvider/memorySearchTheBadWay.d.ts +0 -0
- package/dist/{services → cjs/services}/searchProvider/memorySearchTheBadWay.js +0 -0
- package/dist/{services → cjs/services}/versionedDocumentStore/dynamodb.d.ts +0 -0
- package/dist/{services → cjs/services}/versionedDocumentStore/dynamodb.js +0 -0
- package/dist/{services → cjs/services}/versionedDocumentStore/file-system.d.ts +0 -0
- package/dist/{services → cjs/services}/versionedDocumentStore/file-system.js +0 -0
- package/dist/{services → cjs/services}/versionedDocumentStore/index.d.ts +0 -0
- package/dist/{services → cjs/services}/versionedDocumentStore/index.js +0 -0
- package/dist/cjs/tsconfig.withoutspecs.cjs.tsbuildinfo +1 -0
- package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
- package/dist/{types.js → cjs/types.js} +0 -0
- package/dist/esm/assertions.d.ts +9 -0
- package/dist/esm/assertions.js +90 -0
- package/dist/esm/aws/securityTokenService.d.ts +2 -0
- package/dist/esm/aws/securityTokenService.js +3 -0
- package/dist/esm/aws/ssmService.d.ts +2 -0
- package/dist/esm/aws/ssmService.js +3 -0
- package/dist/esm/config/awsAccountConfig.d.ts +5 -0
- package/dist/esm/config/awsAccountConfig.js +31 -0
- package/dist/esm/config/awsParameterConfig.d.ts +2 -0
- package/dist/esm/config/awsParameterConfig.js +14 -0
- package/dist/esm/config/envConfig.d.ts +3 -0
- package/dist/esm/config/envConfig.js +31 -0
- package/dist/esm/config/index.d.ts +21 -0
- package/dist/esm/config/index.js +21 -0
- package/dist/esm/config/lambdaParameterConfig.d.ts +2 -0
- package/dist/esm/config/lambdaParameterConfig.js +29 -0
- package/dist/esm/config/replaceConfig.d.ts +4 -0
- package/dist/esm/config/replaceConfig.js +8 -0
- package/dist/esm/config/resolveConfigValue.d.ts +2 -0
- package/dist/esm/config/resolveConfigValue.js +8 -0
- package/dist/esm/config.d.ts +27 -0
- package/dist/esm/config.js +127 -0
- package/dist/esm/errors.d.ts +12 -0
- package/dist/esm/errors.js +26 -0
- package/dist/esm/fetch.d.ts +64 -0
- package/dist/esm/fetch.js +46 -0
- package/dist/esm/guards.d.ts +6 -0
- package/dist/esm/guards.js +29 -0
- package/dist/esm/index.d.ts +29 -0
- package/dist/esm/index.js +210 -0
- package/dist/esm/middleware.d.ts +9 -0
- package/dist/esm/middleware.js +34 -0
- package/dist/esm/pagination.d.ts +63 -0
- package/dist/esm/pagination.js +77 -0
- package/dist/esm/profile.d.ts +59 -0
- package/dist/esm/profile.js +191 -0
- package/dist/esm/routing.d.ts +107 -0
- package/dist/esm/routing.js +208 -0
- package/dist/esm/services/apiGateway/index.d.ts +55 -0
- package/dist/esm/services/apiGateway/index.js +51 -0
- package/dist/esm/services/authProvider/browser.d.ts +61 -0
- package/dist/esm/services/authProvider/browser.js +119 -0
- package/dist/esm/services/authProvider/decryption.d.ts +16 -0
- package/dist/esm/services/authProvider/decryption.js +61 -0
- package/dist/esm/services/authProvider/index.d.ts +42 -0
- package/dist/esm/services/authProvider/index.js +15 -0
- package/dist/esm/services/authProvider/subrequest.d.ts +16 -0
- package/dist/esm/services/authProvider/subrequest.js +36 -0
- package/dist/esm/services/authProvider/utils/embeddedAuthProvider.d.ts +20 -0
- package/dist/esm/services/authProvider/utils/embeddedAuthProvider.js +30 -0
- package/dist/esm/services/exercisesGateway/index.d.ts +74 -0
- package/dist/esm/services/exercisesGateway/index.js +69 -0
- package/dist/esm/services/lrsGateway/attempt-utils.d.ts +62 -0
- package/dist/esm/services/lrsGateway/attempt-utils.js +251 -0
- package/dist/esm/services/lrsGateway/file-system.d.ts +15 -0
- package/dist/esm/services/lrsGateway/file-system.js +96 -0
- package/dist/esm/services/lrsGateway/index.d.ts +110 -0
- package/dist/esm/services/lrsGateway/index.js +87 -0
- package/dist/esm/services/searchProvider/index.d.ts +19 -0
- package/dist/esm/services/searchProvider/index.js +1 -0
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +12 -0
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +53 -0
- package/dist/esm/services/versionedDocumentStore/dynamodb.d.ts +23 -0
- package/dist/esm/services/versionedDocumentStore/dynamodb.js +147 -0
- package/dist/esm/services/versionedDocumentStore/file-system.d.ts +25 -0
- package/dist/esm/services/versionedDocumentStore/file-system.js +81 -0
- package/dist/esm/services/versionedDocumentStore/index.d.ts +23 -0
- package/dist/esm/services/versionedDocumentStore/index.js +1 -0
- package/dist/esm/tsconfig.withoutspecs.esm.tsbuildinfo +1 -0
- package/dist/esm/types.d.ts +6 -0
- package/dist/esm/types.js +1 -0
- package/package.json +24 -8
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/tsconfig.withoutspecs.tsbuildinfo +0 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { compactDecrypt, compactVerify, importSPKI } from 'jose';
|
|
2
|
+
import { once } from '../..';
|
|
3
|
+
import { resolveConfigValue } from '../../config';
|
|
4
|
+
import { ifDefined } from '../../guards';
|
|
5
|
+
import { getAuthTokenOrCookie } from '.';
|
|
6
|
+
export const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
7
|
+
const config = configProvider[ifDefined(initializer.configSpace, 'decryption')];
|
|
8
|
+
const cookieName = once(() => resolveConfigValue(config.cookieName));
|
|
9
|
+
const encryptionPrivateKey = once(() => resolveConfigValue(config.encryptionPrivateKey));
|
|
10
|
+
const signaturePublicKey = once(() => resolveConfigValue(config.signaturePublicKey));
|
|
11
|
+
const decryptAndVerify = async (jwt) => {
|
|
12
|
+
try {
|
|
13
|
+
// Decrypt SSO cookie
|
|
14
|
+
const { plaintext } = await compactDecrypt(jwt, Buffer.from(await encryptionPrivateKey()), // Note: Buffer.from() is node-js only
|
|
15
|
+
{ contentEncryptionAlgorithms: ['A256GCM'], keyManagementAlgorithms: ['dir'] });
|
|
16
|
+
// Verify SSO cookie signature
|
|
17
|
+
const { payload } = await compactVerify(plaintext, await importSPKI(await signaturePublicKey(), 'RS256'), { algorithms: ['RS256'] });
|
|
18
|
+
return payload;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return ({ request, profile }) => {
|
|
25
|
+
let user;
|
|
26
|
+
const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
|
|
27
|
+
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
28
|
+
if (!token) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
return { headers };
|
|
32
|
+
});
|
|
33
|
+
const loadUser = profile.track('loadUser', () => async () => {
|
|
34
|
+
const [token] = getAuthTokenOrCookie(request, await cookieName());
|
|
35
|
+
if (!token) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const payload = await decryptAndVerify(token);
|
|
39
|
+
if (!payload) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
// Note: Uint8Array.toString() returns text in node-js only
|
|
43
|
+
// The browser version is new TextDecoder().decode(payload)
|
|
44
|
+
const jwt = JSON.parse(payload.toString());
|
|
45
|
+
// Allow clock skew up to 5 minutes
|
|
46
|
+
if (!jwt.sub || !jwt.sub.uuid || (jwt.exp && jwt.exp < Math.floor(Date.now() / 1000) - 300)) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return jwt.sub;
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
getAuthorizedFetchConfig,
|
|
53
|
+
getUser: async () => {
|
|
54
|
+
if (!user) {
|
|
55
|
+
user = await loadUser();
|
|
56
|
+
}
|
|
57
|
+
return user;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FetchConfig } from '../../fetch';
|
|
2
|
+
import { Track } from '../../profile';
|
|
3
|
+
import { HttpHeaders } from '../../routing';
|
|
4
|
+
export interface User {
|
|
5
|
+
id: number;
|
|
6
|
+
name: string;
|
|
7
|
+
first_name: string;
|
|
8
|
+
last_name: string;
|
|
9
|
+
full_name: string;
|
|
10
|
+
uuid: string;
|
|
11
|
+
faculty_status: string;
|
|
12
|
+
is_administrator: boolean;
|
|
13
|
+
is_not_gdpr_location: boolean;
|
|
14
|
+
contact_infos: Array<{
|
|
15
|
+
type: string;
|
|
16
|
+
value: string;
|
|
17
|
+
is_verified: boolean;
|
|
18
|
+
is_guessed_preferred: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare type AuthProvider = {
|
|
22
|
+
getUser: () => Promise<User | undefined>;
|
|
23
|
+
/**
|
|
24
|
+
* gets second argument for `fetch` that has authentication token or cookie
|
|
25
|
+
*/
|
|
26
|
+
getAuthorizedFetchConfig: () => Promise<FetchConfig>;
|
|
27
|
+
};
|
|
28
|
+
export declare type CookieAuthProviderRequest = {
|
|
29
|
+
headers: HttpHeaders;
|
|
30
|
+
cookies?: string[];
|
|
31
|
+
};
|
|
32
|
+
export declare type CookieAuthProvider = (inputs: {
|
|
33
|
+
request: CookieAuthProviderRequest;
|
|
34
|
+
profile: Track;
|
|
35
|
+
}) => AuthProvider;
|
|
36
|
+
export declare type StubAuthProvider = (user: User | undefined) => AuthProvider;
|
|
37
|
+
export declare const stubAuthProvider: (user?: User | undefined) => AuthProvider;
|
|
38
|
+
export declare const getAuthTokenOrCookie: (request: CookieAuthProviderRequest, cookieName: string) => [string, {
|
|
39
|
+
Authorization: string;
|
|
40
|
+
}] | [string, {
|
|
41
|
+
cookie: string;
|
|
42
|
+
}];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import cookie from 'cookie';
|
|
2
|
+
import { tuple } from '../..';
|
|
3
|
+
import { getHeader } from '../../routing';
|
|
4
|
+
export const stubAuthProvider = (user) => ({
|
|
5
|
+
getUser: () => Promise.resolve(user),
|
|
6
|
+
getAuthorizedFetchConfig: () => Promise.resolve(user ? { headers: { Authorization: user.uuid } } : {})
|
|
7
|
+
});
|
|
8
|
+
export const getAuthTokenOrCookie = (request, cookieName) => {
|
|
9
|
+
var _a;
|
|
10
|
+
const authHeader = getHeader(request.headers, 'authorization');
|
|
11
|
+
const cookieValue = cookie.parse(((_a = request.cookies) === null || _a === void 0 ? void 0 : _a.join('; ')) || '')[cookieName];
|
|
12
|
+
return authHeader && authHeader.length >= 8 && authHeader.startsWith('Bearer ')
|
|
13
|
+
? tuple(authHeader.slice(7), { Authorization: authHeader })
|
|
14
|
+
: tuple(cookieValue, { cookie: cookie.serialize(cookieName, cookieValue) });
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ConfigProviderForConfig } from '../../config';
|
|
2
|
+
import { GenericFetch } from '../../fetch';
|
|
3
|
+
import { CookieAuthProvider } from '.';
|
|
4
|
+
declare type Config = {
|
|
5
|
+
cookieName: string;
|
|
6
|
+
accountsUrl: string;
|
|
7
|
+
};
|
|
8
|
+
interface Initializer<C> {
|
|
9
|
+
configSpace?: C;
|
|
10
|
+
fetch: GenericFetch;
|
|
11
|
+
}
|
|
12
|
+
export declare const subrequestAuthProvider: <C extends string = "subrequest">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
|
|
13
|
+
cookieName: import("../../config").ConfigValueProvider<string>;
|
|
14
|
+
accountsUrl: import("../../config").ConfigValueProvider<string>;
|
|
15
|
+
}; }) => CookieAuthProvider;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { once } from '../..';
|
|
2
|
+
import { resolveConfigValue } from '../../config';
|
|
3
|
+
import { ifDefined } from '../../guards';
|
|
4
|
+
import { getAuthTokenOrCookie } from '.';
|
|
5
|
+
export const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
6
|
+
const config = configProvider[ifDefined(initializer.configSpace, 'subrequest')];
|
|
7
|
+
const cookieName = once(() => resolveConfigValue(config.cookieName));
|
|
8
|
+
const accountsUrl = once(() => resolveConfigValue(config.accountsUrl));
|
|
9
|
+
return ({ request, profile }) => {
|
|
10
|
+
let user;
|
|
11
|
+
const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
|
|
12
|
+
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
13
|
+
if (!token) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
return { headers };
|
|
17
|
+
});
|
|
18
|
+
const loadUser = profile.track('loadUser', p => async () => {
|
|
19
|
+
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
20
|
+
if (!token) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return p.trackFetch(initializer.fetch)(await accountsUrl(), { headers })
|
|
24
|
+
.then(response => response.json());
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
getAuthorizedFetchConfig,
|
|
28
|
+
getUser: async () => {
|
|
29
|
+
if (!user) {
|
|
30
|
+
user = await loadUser();
|
|
31
|
+
}
|
|
32
|
+
return user;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { User } from '..';
|
|
2
|
+
import { Window } from '../browser';
|
|
3
|
+
export declare type UserData = {
|
|
4
|
+
user?: User;
|
|
5
|
+
token: string | null;
|
|
6
|
+
};
|
|
7
|
+
declare type UserDataLoader = () => Promise<UserData>;
|
|
8
|
+
export declare enum PostMessageTypes {
|
|
9
|
+
ReceiveUser = "receive-user",
|
|
10
|
+
RequestUser = "request-user"
|
|
11
|
+
}
|
|
12
|
+
export declare const embeddedAuthProvider: (getUserData: UserDataLoader, { queryKey, window }: {
|
|
13
|
+
queryKey?: string | undefined;
|
|
14
|
+
window: Window;
|
|
15
|
+
}) => {
|
|
16
|
+
embeddedQueryValue: string;
|
|
17
|
+
getAuthorizedEmbedUrl: (urlString: string) => string;
|
|
18
|
+
unmount: () => void;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export var PostMessageTypes;
|
|
2
|
+
(function (PostMessageTypes) {
|
|
3
|
+
PostMessageTypes["ReceiveUser"] = "receive-user";
|
|
4
|
+
PostMessageTypes["RequestUser"] = "request-user";
|
|
5
|
+
})(PostMessageTypes || (PostMessageTypes = {}));
|
|
6
|
+
export const embeddedAuthProvider = (getUserData, { queryKey = 'auth', window }) => {
|
|
7
|
+
const trustedEmbeds = new Set();
|
|
8
|
+
const embeddedQueryValue = 'embedded';
|
|
9
|
+
const messageHandler = event => {
|
|
10
|
+
if (event.data.type === PostMessageTypes.RequestUser && trustedEmbeds.has(event.origin)) {
|
|
11
|
+
getUserData().then(data => {
|
|
12
|
+
event.source.postMessage({ type: PostMessageTypes.ReceiveUser, userData: data }, event.origin);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
window.addEventListener('message', messageHandler);
|
|
17
|
+
const getAuthorizedEmbedUrl = (urlString) => {
|
|
18
|
+
const url = new URL(urlString);
|
|
19
|
+
trustedEmbeds.add(url.origin);
|
|
20
|
+
url.searchParams.set(queryKey, embeddedQueryValue);
|
|
21
|
+
return url.href;
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
embeddedQueryValue,
|
|
25
|
+
getAuthorizedEmbedUrl,
|
|
26
|
+
unmount: () => {
|
|
27
|
+
window.removeEventListener('message', messageHandler);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ConfigProviderForConfig } from '../../config';
|
|
2
|
+
import { GenericFetch } from '../../fetch';
|
|
3
|
+
import { Track } from '../../profile';
|
|
4
|
+
export declare type Config = {
|
|
5
|
+
defaultCorrectness?: string;
|
|
6
|
+
exercisesHost: string;
|
|
7
|
+
exercisesAuthToken: string;
|
|
8
|
+
};
|
|
9
|
+
interface Initializer<C> {
|
|
10
|
+
configSpace?: C;
|
|
11
|
+
fetch: GenericFetch;
|
|
12
|
+
}
|
|
13
|
+
export declare type Answer = {
|
|
14
|
+
id: number;
|
|
15
|
+
content_html: string;
|
|
16
|
+
correctness?: string;
|
|
17
|
+
feedback_html?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare type Solution = {
|
|
20
|
+
images: any[];
|
|
21
|
+
solution_type: string;
|
|
22
|
+
content_html: string;
|
|
23
|
+
};
|
|
24
|
+
export declare type Question = {
|
|
25
|
+
id: number;
|
|
26
|
+
is_answer_order_important: boolean;
|
|
27
|
+
stimulus_html: string;
|
|
28
|
+
stem_html: string;
|
|
29
|
+
answers: Answer[];
|
|
30
|
+
hints: string[];
|
|
31
|
+
formats: string[];
|
|
32
|
+
combo_choices: any[];
|
|
33
|
+
collaborator_solutions?: Solution[];
|
|
34
|
+
community_solutions?: Solution[];
|
|
35
|
+
};
|
|
36
|
+
export declare type Exercise = {
|
|
37
|
+
images: any[];
|
|
38
|
+
tags: string[];
|
|
39
|
+
uuid: string;
|
|
40
|
+
group_uuid: string;
|
|
41
|
+
number: number;
|
|
42
|
+
version: number;
|
|
43
|
+
uid: string;
|
|
44
|
+
published_at: string;
|
|
45
|
+
solutions_are_public: boolean;
|
|
46
|
+
authors: any[];
|
|
47
|
+
copyright_holders: any[];
|
|
48
|
+
derived_from: any[];
|
|
49
|
+
is_vocab: boolean;
|
|
50
|
+
questions: Question[];
|
|
51
|
+
delegations: any[];
|
|
52
|
+
versions: number[];
|
|
53
|
+
stimulus_html: string;
|
|
54
|
+
};
|
|
55
|
+
export declare type ExercisesSearchResults = {
|
|
56
|
+
total_count: number;
|
|
57
|
+
items: Exercise[];
|
|
58
|
+
};
|
|
59
|
+
export declare type ExercisesSearchResultsWithDigest = ExercisesSearchResults & {
|
|
60
|
+
digest: string;
|
|
61
|
+
};
|
|
62
|
+
export declare const exercisesGateway: <C extends string = "exercises">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
|
|
63
|
+
defaultCorrectness?: import("../../config").ConfigValueProvider<string> | undefined;
|
|
64
|
+
exercisesHost: import("../../config").ConfigValueProvider<string>;
|
|
65
|
+
exercisesAuthToken: import("../../config").ConfigValueProvider<string>;
|
|
66
|
+
}; }) => ({ profile }: {
|
|
67
|
+
profile: Track;
|
|
68
|
+
}) => {
|
|
69
|
+
searchDigest: (query: string, page?: any, per_page?: any) => Promise<string>;
|
|
70
|
+
get: (uuid: string) => Promise<Exercise | undefined>;
|
|
71
|
+
search: (query: string, page?: any, per_page?: any) => Promise<ExercisesSearchResultsWithDigest>;
|
|
72
|
+
};
|
|
73
|
+
export declare type ExercisesGateway = ReturnType<ReturnType<ReturnType<typeof exercisesGateway>>>;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as queryString from 'query-string';
|
|
2
|
+
import { once } from '../..';
|
|
3
|
+
import { assertString } from '../../assertions';
|
|
4
|
+
import { resolveConfigValue } from '../../config';
|
|
5
|
+
import { ifDefined } from '../../guards';
|
|
6
|
+
import { METHOD } from '../../routing';
|
|
7
|
+
export const exercisesGateway = (initializer) => (configProvider) => {
|
|
8
|
+
const config = configProvider[ifDefined(initializer.configSpace, 'exercises')];
|
|
9
|
+
const exercisesHost = once(() => resolveConfigValue(config.exercisesHost));
|
|
10
|
+
const exercisesAuthToken = once(() => resolveConfigValue(config.exercisesAuthToken));
|
|
11
|
+
const defaultCorrectness = once(() => resolveConfigValue(config.defaultCorrectness || ''));
|
|
12
|
+
const doDefaultCorrectness = async (exercise) => {
|
|
13
|
+
if (await defaultCorrectness() !== 'true') {
|
|
14
|
+
return exercise;
|
|
15
|
+
}
|
|
16
|
+
for (const question of exercise.questions) {
|
|
17
|
+
const existingCorrect = question.answers.find(answer => answer.correctness !== undefined);
|
|
18
|
+
if (question.answers.length < 1 || existingCorrect) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const defaultCorrectIndex = question.id % question.answers.length;
|
|
22
|
+
const defaultCorrect = question.answers[defaultCorrectIndex];
|
|
23
|
+
const defaultHint = `<em>random default: the correct answer is ${defaultCorrect.id}: ${defaultCorrect.content_html.slice(0, 20)}</em>`;
|
|
24
|
+
question.stem_html += `\n<br>${defaultHint}`;
|
|
25
|
+
question.collaborator_solutions = [
|
|
26
|
+
{ solution_type: 'detailed', images: [], content_html: defaultHint }
|
|
27
|
+
];
|
|
28
|
+
for (const [index, answer] of question.answers.entries()) {
|
|
29
|
+
answer.correctness = defaultCorrectIndex === index ? '1.0' : '0.0';
|
|
30
|
+
answer.feedback_html = defaultCorrectIndex === index ? 'This is the good one!' : defaultHint;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return exercise;
|
|
34
|
+
};
|
|
35
|
+
return ({ profile }) => {
|
|
36
|
+
const request = async (method, path, query = undefined) => {
|
|
37
|
+
const host = (await exercisesHost()).replace(/\/+$/, '');
|
|
38
|
+
const baseUrl = `${host}/api/${path}`;
|
|
39
|
+
const url = query ? `${baseUrl}?${queryString.stringify(query)}` : baseUrl;
|
|
40
|
+
return initializer.fetch(url, {
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${await exercisesAuthToken()}`,
|
|
43
|
+
},
|
|
44
|
+
method,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const searchDigest = profile.track('searchExercisesDigest', () => async (query, page = 1, per_page = 100) => {
|
|
48
|
+
const response = await request(METHOD.HEAD, 'exercises', { query, page, per_page });
|
|
49
|
+
return assertString(response.headers.get('X-Digest'), 'OpenStax Exercises search endpoint HEAD did not return an X-Digest header');
|
|
50
|
+
});
|
|
51
|
+
const search = profile.track('searchExercises', () => async (query, page = 1, per_page = 100) => {
|
|
52
|
+
const response = await request(METHOD.GET, 'exercises', { query, page, per_page });
|
|
53
|
+
const digest = assertString(response.headers.get('X-Digest'), 'OpenStax Exercises search endpoint GET did not return an X-Digest header');
|
|
54
|
+
const { items, total_count } = await response.json();
|
|
55
|
+
return { digest, items: await Promise.all(items.map(doDefaultCorrectness)), total_count };
|
|
56
|
+
});
|
|
57
|
+
const get = profile.track('getExercise', () => async (uuid) => {
|
|
58
|
+
const response = await request(METHOD.GET, `exercises/${uuid}`);
|
|
59
|
+
return response.status === 404
|
|
60
|
+
? undefined
|
|
61
|
+
: response.json().then(doDefaultCorrectness);
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
searchDigest,
|
|
65
|
+
get,
|
|
66
|
+
search,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { LrsGateway, XapiStatement } from '.';
|
|
2
|
+
export declare type ActivityState = {
|
|
3
|
+
attempts: number;
|
|
4
|
+
completedAttempts: number;
|
|
5
|
+
currentAttempt?: XapiStatement;
|
|
6
|
+
currentAttemptCompleted?: XapiStatement;
|
|
7
|
+
currentAttemptStatements: XapiStatement[];
|
|
8
|
+
mostRecentAttemptWithCompleted?: XapiStatement;
|
|
9
|
+
mostRecentAttemptWithCompletedCompleted?: XapiStatement;
|
|
10
|
+
};
|
|
11
|
+
export declare const matchAttempt: (statement: XapiStatement) => boolean;
|
|
12
|
+
export declare const matchAttemptCompleted: (attempt: XapiStatement) => (statement: XapiStatement) => boolean;
|
|
13
|
+
export declare const resolveActivityAttempts: (statements: XapiStatement[], activityIRI: string, parentActivityAttempt?: string | undefined) => XapiStatement[];
|
|
14
|
+
export declare const resolveCompletedForAttempt: (statements: XapiStatement[], activityIRI: string, attempt: XapiStatement) => XapiStatement | undefined;
|
|
15
|
+
export declare const oldestStatement: (statements: XapiStatement[]) => XapiStatement | undefined;
|
|
16
|
+
export declare const mostRecentStatement: (statements: XapiStatement[]) => XapiStatement | undefined;
|
|
17
|
+
export declare const resolveActivityAttemptInfo: (statements: XapiStatement[], activityIRI: string, options?: {
|
|
18
|
+
currentAttempt?: string | undefined;
|
|
19
|
+
parentActivityAttempt?: string | undefined;
|
|
20
|
+
currentPreference?: "latest" | "oldest" | undefined;
|
|
21
|
+
} | undefined) => ActivityState;
|
|
22
|
+
export declare const loadStatementsForActivityAndFirstChildren: (gateway: LrsGateway, activityIRI: string, attempt?: string | undefined) => Promise<XapiStatement[]>;
|
|
23
|
+
export declare const loadStatementsForAttempt: (gateway: LrsGateway, attempt: string) => Promise<XapiStatement[]>;
|
|
24
|
+
export declare const loadStatementsForActivity: (gateway: LrsGateway, activityIRI: string, attempt?: string | undefined) => Promise<XapiStatement[]>;
|
|
25
|
+
export declare const loadActivityAttemptInfo: (gateway: LrsGateway, activityIRI: string, options?: {
|
|
26
|
+
currentAttempt?: string | undefined;
|
|
27
|
+
parentActivityAttempt?: string | undefined;
|
|
28
|
+
} | undefined) => Promise<ActivityState>;
|
|
29
|
+
export declare const createStatement: (verb: XapiStatement['verb'], activity: {
|
|
30
|
+
iri: string;
|
|
31
|
+
type: string;
|
|
32
|
+
name: string;
|
|
33
|
+
extensions?: {
|
|
34
|
+
[key: string]: string;
|
|
35
|
+
} | undefined;
|
|
36
|
+
}, attempt: string, parentActivityIRI?: string | undefined) => Pick<XapiStatement, 'object' | 'verb' | 'context'>;
|
|
37
|
+
export declare const createAttemptStatement: (activity: {
|
|
38
|
+
iri: string;
|
|
39
|
+
type: string;
|
|
40
|
+
name: string;
|
|
41
|
+
extensions?: {
|
|
42
|
+
[key: string]: string;
|
|
43
|
+
} | undefined;
|
|
44
|
+
}, parentActivity?: {
|
|
45
|
+
iri?: string | undefined;
|
|
46
|
+
attempt?: string | undefined;
|
|
47
|
+
} | undefined) => Pick<XapiStatement, 'object' | 'verb' | 'context'>;
|
|
48
|
+
export declare const putAttemptStatement: (gateway: LrsGateway, activity: {
|
|
49
|
+
iri: string;
|
|
50
|
+
type: string;
|
|
51
|
+
name: string;
|
|
52
|
+
extensions?: {
|
|
53
|
+
[key: string]: string;
|
|
54
|
+
} | undefined;
|
|
55
|
+
}, parentActivity?: {
|
|
56
|
+
iri?: string | undefined;
|
|
57
|
+
attempt?: string | undefined;
|
|
58
|
+
} | undefined) => Promise<import(".").EagerXapiStatement>;
|
|
59
|
+
export declare const createAttemptActivityStatement: (attemptStatement: XapiStatement, verb: XapiStatement['verb'], result?: XapiStatement['result']) => Pick<XapiStatement, 'object' | 'verb' | 'context' | 'result'>;
|
|
60
|
+
export declare const putAttemptActivityStatement: (gateway: LrsGateway, attemptStatement: XapiStatement, verb: XapiStatement['verb'], result?: XapiStatement['result']) => Promise<import(".").EagerXapiStatement>;
|
|
61
|
+
export declare const createCompletedStatement: (attemptStatement: XapiStatement, result?: XapiStatement['result']) => Pick<XapiStatement, 'object' | 'verb' | 'context' | 'result'>;
|
|
62
|
+
export declare const putCompletedStatement: (gateway: LrsGateway, attemptStatement: XapiStatement, result: XapiStatement['result']) => Promise<import(".").EagerXapiStatement>;
|