@openstax/ts-utils 1.1.40 → 1.1.43

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.
@@ -10,3 +10,7 @@ export declare class NotFoundError extends Error {
10
10
  static readonly TYPE = "NotFoundError";
11
11
  static matches: (e: any) => e is typeof NotFoundError;
12
12
  }
13
+ export declare class SessionExpiredError extends Error {
14
+ static readonly TYPE = "SessionExpiredError";
15
+ static matches: (e: any) => e is typeof SessionExpiredError;
16
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NotFoundError = exports.UnauthorizedError = exports.InvalidRequestError = void 0;
3
+ exports.SessionExpiredError = exports.NotFoundError = exports.UnauthorizedError = exports.InvalidRequestError = void 0;
4
4
  /*
5
5
  * if code is split into multiple bundles, sometimes each bundle
6
6
  * will get its own definition of this module and then instanceof checks
@@ -30,3 +30,8 @@ class NotFoundError extends Error {
30
30
  exports.NotFoundError = NotFoundError;
31
31
  NotFoundError.TYPE = 'NotFoundError';
32
32
  NotFoundError.matches = errorIsType(NotFoundError);
33
+ class SessionExpiredError extends Error {
34
+ }
35
+ exports.SessionExpiredError = SessionExpiredError;
36
+ SessionExpiredError.TYPE = 'SessionExpiredError';
37
+ SessionExpiredError.matches = errorIsType(SessionExpiredError);
@@ -31,6 +31,7 @@ const pathToRegexp = __importStar(require("path-to-regexp"));
31
31
  const query_string_1 = __importDefault(require("query-string"));
32
32
  const __1 = require("../..");
33
33
  const config_1 = require("../../config");
34
+ const errors_1 = require("../../errors");
34
35
  const loadResponse = (response) => () => {
35
36
  const [contentType] = (response.headers.get('content-type') || '').split(';');
36
37
  switch (contentType) {
@@ -61,16 +62,24 @@ const makeRouteClient = (initializer, config, route, authProvider) => {
61
62
  ...fetchConfig === null || fetchConfig === void 0 ? void 0 : fetchConfig.headers,
62
63
  ...(body ? { 'content-type': 'application/json' } : {}),
63
64
  }
64
- })).then(response => ({
65
- status: response.status,
66
- acceptStatus: (...status) => {
67
- if (!status.includes(response.status)) {
68
- throw new Error('unexpected response from api');
69
- }
70
- return { status: response.status, load: (0, exports.loadResponse)(response) };
71
- },
72
- load: (0, exports.loadResponse)(response),
73
- }));
65
+ })).then(response => {
66
+ if (response.status === 401) {
67
+ throw new errors_1.UnauthorizedError();
68
+ }
69
+ if (response.status === 440) {
70
+ throw new errors_1.SessionExpiredError();
71
+ }
72
+ return {
73
+ status: response.status,
74
+ acceptStatus: (...status) => {
75
+ if (!status.includes(response.status)) {
76
+ throw new Error('unexpected response from api');
77
+ }
78
+ return { status: response.status, load: (0, exports.loadResponse)(response) };
79
+ },
80
+ load: (0, exports.loadResponse)(response),
81
+ };
82
+ });
74
83
  };
75
84
  routeClient.renderUrl = renderUrl;
76
85
  return routeClient;
@@ -30,6 +30,7 @@ exports.decryptAndVerify = void 0;
30
30
  const crypto = __importStar(require("crypto"));
31
31
  const util_1 = require("util");
32
32
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
33
+ const errors_1 = require("../../../errors");
33
34
  const guards_1 = require("../../../guards");
34
35
  const decrypt = (input, key) => {
35
36
  const splitInput = input.split('.');
@@ -51,7 +52,7 @@ const decryptAndVerify = (token, encryptionPrivateKey, signaturePublicKey) => {
51
52
  // Decrypt SSO cookie
52
53
  const plaintext = decrypt(token, encryptionPrivateKey);
53
54
  const payload = jsonwebtoken_1.default.verify(plaintext, signaturePublicKey, {
54
- clockTolerance: 300 // 5 minutes
55
+ clockTolerance: 300 // 5 minutes
55
56
  });
56
57
  if (!(0, guards_1.isPlainObject)(payload) || !(0, guards_1.isPlainObject)(payload.sub) || !payload.sub.uuid) {
57
58
  return undefined;
@@ -59,7 +60,10 @@ const decryptAndVerify = (token, encryptionPrivateKey, signaturePublicKey) => {
59
60
  // TS is confused because the library types the `sub` as a string
60
61
  return payload.sub;
61
62
  }
62
- catch {
63
+ catch (err) {
64
+ if (err instanceof jsonwebtoken_1.default.TokenExpiredError) {
65
+ throw new errors_1.SessionExpiredError();
66
+ }
63
67
  return undefined;
64
68
  }
65
69
  };
@@ -205,8 +205,7 @@ const createAttemptStatement = (activity, parentActivity) => {
205
205
  exports.createAttemptStatement = createAttemptStatement;
206
206
  /* resolves with the statement id */
207
207
  const putAttemptStatement = async (gateway, activity, parentActivity) => {
208
- return await gateway.putXapiStatements([(0, exports.createAttemptStatement)(activity, parentActivity)])
209
- .then(statements => statements[0]);
208
+ return (await gateway.putXapiStatements([(0, exports.createAttemptStatement)(activity, parentActivity)]))[0];
210
209
  };
211
210
  exports.putAttemptStatement = putAttemptStatement;
212
211
  /*
@@ -230,8 +229,7 @@ const createAttemptActivityStatement = (attemptStatement, verb, result) => {
230
229
  };
231
230
  exports.createAttemptActivityStatement = createAttemptActivityStatement;
232
231
  const putAttemptActivityStatement = async (gateway, attemptStatement, verb, result) => {
233
- return await gateway.putXapiStatements([(0, exports.createAttemptActivityStatement)(attemptStatement, verb, result)])
234
- .then(statements => statements[0]);
232
+ return (await gateway.putXapiStatements([(0, exports.createAttemptActivityStatement)(attemptStatement, verb, result)]))[0];
235
233
  };
236
234
  exports.putAttemptActivityStatement = putAttemptActivityStatement;
237
235
  /*
@@ -269,7 +267,6 @@ const createCompletedStatement = (attemptStatement, result) => {
269
267
  };
270
268
  exports.createCompletedStatement = createCompletedStatement;
271
269
  const putCompletedStatement = async (gateway, attemptStatement, result) => {
272
- return await gateway.putXapiStatements([(0, exports.createCompletedStatement)(attemptStatement, result)])
273
- .then(statements => statements[0]);
270
+ return (await gateway.putXapiStatements([(0, exports.createCompletedStatement)(attemptStatement, result)]))[0];
274
271
  };
275
272
  exports.putCompletedStatement = putCompletedStatement;
@@ -40,6 +40,7 @@ const lrsGateway = (initializer) => (configProvider) => {
40
40
  const lrsHost = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.lrsHost));
41
41
  const lrsAuthorization = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.lrsAuthorization));
42
42
  return (authProvider) => {
43
+ // Note: This method actually uses POST
43
44
  const putXapiStatements = async (statements) => {
44
45
  const user = (0, assertions_1.assertDefined)(await authProvider.getUser(), new errors_1.UnauthorizedError);
45
46
  const statementsWithDefaults = statements.map(statement => ({
@@ -53,7 +54,7 @@ const lrsGateway = (initializer) => (configProvider) => {
53
54
  },
54
55
  timestamp: (0, formatISO_1.default)(new Date())
55
56
  }));
56
- return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements', {
57
+ const response = await initializer.fetch((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements', {
57
58
  body: JSON.stringify(statementsWithDefaults),
58
59
  headers: {
59
60
  Authorization: await lrsAuthorization(),
@@ -61,42 +62,50 @@ const lrsGateway = (initializer) => (configProvider) => {
61
62
  'X-Experience-API-Version': '1.0.0',
62
63
  },
63
64
  method: routing_1.METHOD.POST,
64
- })
65
- .then(response => response.json())
66
- .then(ids => ids.map((id, index) => ({ id, ...statementsWithDefaults[index] })));
65
+ });
66
+ if (![200, 201].includes(response.status)) {
67
+ throw new Error(`Unexpected LRS POST statements response code ${response.status} with body:
68
+
69
+ ${await response.text()}`);
70
+ }
71
+ const ids = await response.json();
72
+ return ids.map((id, index) => ({ id, ...statementsWithDefaults[index] }));
67
73
  };
68
- const getMoreXapiStatements = async (more) => {
69
- return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + more, {
70
- headers: {
71
- Authorization: await lrsAuthorization(),
72
- 'X-Experience-API-Version': '1.0.0',
73
- },
74
- })
75
- .then(response => response.json())
76
- .then(json => json);
74
+ // Note: This code does not currently handle a single statement response,
75
+ // which can return 404 if the statement is not found
76
+ const formatGetXapiStatementsResponse = async (responsePromise) => {
77
+ const response = await responsePromise;
78
+ if (response.status !== 200) {
79
+ throw new Error(`Unexpected LRS GET statements response code ${response.status} with body:
80
+
81
+ ${await response.text()}`);
82
+ }
83
+ return response.json();
77
84
  };
78
- const getXapiStatements = async ({ user, anyUser, ...options }) => {
79
- return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements?' + queryString.stringify({
80
- ...options,
81
- ...(anyUser === true ? {} : {
82
- agent: JSON.stringify({
83
- account: {
84
- homePage: 'https://openstax.org',
85
- name: user || (0, assertions_1.assertDefined)(await authProvider.getUser(), new errors_1.UnauthorizedError()).uuid,
86
- },
87
- objectType: 'Agent',
88
- }),
89
- })
90
- }), {
91
- headers: {
92
- Authorization: await lrsAuthorization(),
93
- 'X-Experience-API-Version': '1.0.0',
94
- },
85
+ const getMoreXapiStatements = async (more) => formatGetXapiStatementsResponse(initializer.fetch((await lrsHost()).replace(/\/+$/, '') + more, {
86
+ headers: {
87
+ Authorization: await lrsAuthorization(),
88
+ 'X-Experience-API-Version': '1.0.0',
89
+ },
90
+ }));
91
+ const getXapiStatements = async ({ user, anyUser, ...options }) => formatGetXapiStatementsResponse(initializer.fetch((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements?' + queryString.stringify({
92
+ ...options,
93
+ ...(anyUser === true ? {} : {
94
+ agent: JSON.stringify({
95
+ account: {
96
+ homePage: 'https://openstax.org',
97
+ name: user || (0, assertions_1.assertDefined)(await authProvider.getUser(), new errors_1.UnauthorizedError()).uuid,
98
+ },
99
+ objectType: 'Agent',
100
+ }),
95
101
  })
96
- .then(response => response.json())
97
- .then(json => json);
98
- };
99
- const getAllXapiStatements = (...args) => {
102
+ }), {
103
+ headers: {
104
+ Authorization: await lrsAuthorization(),
105
+ 'X-Experience-API-Version': '1.0.0',
106
+ },
107
+ }));
108
+ const getAllXapiStatements = async (...args) => {
100
109
  const loadRemaining = async (result) => {
101
110
  if (!result.more) {
102
111
  return result.statements;
@@ -104,7 +113,7 @@ const lrsGateway = (initializer) => (configProvider) => {
104
113
  const { more, statements } = await getMoreXapiStatements(result.more);
105
114
  return loadRemaining({ more, statements: [...result.statements, ...statements] });
106
115
  };
107
- return getXapiStatements(...args).then(loadRemaining);
116
+ return loadRemaining(await getXapiStatements(...args));
108
117
  };
109
118
  return {
110
119
  putXapiStatements,
@@ -10,6 +10,10 @@ declare type Field = {
10
10
  key: string;
11
11
  type: 'keyword';
12
12
  };
13
+ export interface IndexOptions<T> {
14
+ body: T;
15
+ id: string;
16
+ }
13
17
  export interface SearchOptions {
14
18
  page?: number;
15
19
  query: string | undefined;
@@ -1,7 +1,8 @@
1
- import { SearchOptions } from '.';
1
+ import { IndexOptions, SearchOptions } from '.';
2
2
  export declare const memorySearchTheBadWay: <T>({ loadAllDocumentsTheBadWay }: {
3
3
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
4
4
  }) => {
5
+ index: (_options: IndexOptions<T>) => Promise<undefined>;
5
6
  search: (options: SearchOptions) => Promise<{
6
7
  items: T[];
7
8
  pageSize: number;
@@ -8,6 +8,7 @@ const MAX_RESULTS = 10;
8
8
  const resolveField = (document, field) => field.key.toString().split('.').reduce((result, key) => result[key], document);
9
9
  const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
10
10
  return {
11
+ index: async (_options) => undefined,
11
12
  search: async (options) => {
12
13
  const results = (await loadAllDocumentsTheBadWay())
13
14
  .map(document => {