cloudcommerce 0.0.40 → 0.0.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/package.json +8 -8
  3. package/packages/api/lib/index.d.ts +9 -3
  4. package/packages/api/lib/index.js +12 -2
  5. package/packages/api/lib/index.js.map +1 -1
  6. package/packages/api/lib/types.d.ts +12 -2
  7. package/packages/api/package.json +1 -1
  8. package/packages/api/src/index.ts +28 -5
  9. package/packages/api/src/types.ts +13 -1
  10. package/packages/apps/discounts/package.json +1 -1
  11. package/packages/cli/package.json +1 -1
  12. package/packages/firebase/package.json +5 -4
  13. package/packages/modules/lib/firebase/ajv.js +33 -0
  14. package/packages/modules/lib/firebase/ajv.js.map +1 -0
  15. package/packages/modules/lib/firebase/call-app-module.js +70 -0
  16. package/packages/modules/lib/firebase/call-app-module.js.map +1 -0
  17. package/packages/modules/lib/firebase/checkout.js +1 -0
  18. package/packages/modules/lib/firebase/checkout.js.map +1 -0
  19. package/packages/modules/lib/firebase/handle-module.js +161 -0
  20. package/packages/modules/lib/firebase/handle-module.js.map +1 -0
  21. package/packages/modules/lib/firebase/proxy-apps.js +1 -0
  22. package/packages/modules/lib/firebase/proxy-apps.js.map +1 -0
  23. package/packages/modules/lib/firebase/serve-modules-api.js +57 -0
  24. package/packages/modules/lib/firebase/serve-modules-api.js.map +1 -0
  25. package/packages/modules/lib/firebase.js +10 -3
  26. package/packages/modules/lib/firebase.js.map +1 -1
  27. package/packages/modules/lib/index.js +11 -7
  28. package/packages/modules/lib/index.js.map +1 -1
  29. package/packages/modules/package.json +5 -2
  30. package/packages/modules/src/firebase/ajv.ts +38 -0
  31. package/packages/modules/src/firebase/call-app-module.ts +72 -0
  32. package/packages/modules/src/firebase/{.gitkeep → checkout.ts} +0 -0
  33. package/packages/modules/src/firebase/handle-module.ts +191 -0
  34. package/packages/modules/src/firebase/proxy-apps.ts +0 -0
  35. package/packages/modules/src/firebase/serve-modules-api.ts +66 -0
  36. package/packages/modules/src/firebase.ts +10 -3
  37. package/packages/modules/src/index.ts +11 -8
  38. package/packages/passport/package.json +2 -2
  39. package/packages/ssr/package.json +2 -2
  40. package/packages/storefront/package.json +2 -2
  41. package/packages/types/package.json +1 -1
  42. package/pnpm-lock.yaml +440 -168
@@ -0,0 +1,57 @@
1
+ import { schemas } from '../index.js';
2
+ import handleModule from './handle-module.js';
3
+
4
+ export default (req, res, apiAuth) => {
5
+ const { method } = req;
6
+ if (method !== 'POST' && method !== 'GET') {
7
+ return res.sendStatus(405);
8
+ }
9
+ if (method === 'POST'
10
+ && (!req.body || typeof req.body !== 'object' || Array.isArray(req.body))) {
11
+ return res.sendStatus(400);
12
+ }
13
+ let { url } = req;
14
+ if (url.endsWith('.json')) {
15
+ url = url.slice(0, -5);
16
+ }
17
+ const modName = url.split('/')[1];
18
+ const sendSchema = (isResponseSchema = false) => {
19
+ return res.status(200)
20
+ .setHeader('Cache-Control', 'public, max-age=3600')
21
+ .send(schemas[modName][isResponseSchema ? 'response' : 'params']);
22
+ };
23
+ if (modName === '@checkout') {
24
+ if (url === '/@checkout') {
25
+ return res.status(200).send({
26
+ status: 200,
27
+ message: 'CHECKOUT',
28
+ });
29
+ }
30
+ if (url === '/@checkout/schema') {
31
+ return sendSchema();
32
+ }
33
+ return res.sendStatus(404);
34
+ }
35
+ if (schemas[modName]) {
36
+ const { params: schema, response: responseSchema } = schemas[modName];
37
+ if (!schema.$schema) {
38
+ schema.$schema = 'http://json-schema.org/draft-06/schema#';
39
+ schema.title = `Module \`${modName}\`: Params model`;
40
+ }
41
+ if (!responseSchema.$schema) {
42
+ responseSchema.$schema = 'http://json-schema.org/draft-06/schema#';
43
+ responseSchema.title = `Module \`${modName}\`: App response model`;
44
+ }
45
+ if (url === `/${modName}/schema`) {
46
+ return sendSchema();
47
+ }
48
+ if (url === `/${modName}/response_schema`) {
49
+ return sendSchema(true);
50
+ }
51
+ if (url === `/${modName}`) {
52
+ return handleModule(modName, schema, responseSchema, req, res, apiAuth);
53
+ }
54
+ }
55
+ return res.sendStatus(404);
56
+ };
57
+ // # sourceMappingURL=serve-modules-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve-modules-api.js","sourceRoot":"","sources":["../../src/firebase/serve-modules-api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,YAAY,MAAM,iBAAiB,CAAC;AAE3C,eAAe,CACb,GAAY,EACZ,GAAa,EACb,OAAqD,EACrD,EAAE;IACF,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IACvB,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,KAAK,EAAE;QACzC,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;KAC5B;IACD,IACE,MAAM,KAAK,MAAM;WACd,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EACzE;QACA,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;KAC5B;IAED,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;IAClB,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;QACzB,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;KACxB;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,CAAC,gBAAgB,GAAG,KAAK,EAAE,EAAE;QAC9C,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;aACnB,SAAS,CAAC,eAAe,EAAE,sBAAsB,CAAC;aAClD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,IAAI,OAAO,KAAK,WAAW,EAAE;QAC3B,IAAI,GAAG,KAAK,YAAY,EAAE;YACxB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,UAAU;aACpB,CAAC,CAAC;SACJ;QACD,IAAI,GAAG,KAAK,mBAAmB,EAAE;YAC/B,OAAO,UAAU,EAAE,CAAC;SACrB;QACD,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;KAC5B;IAED,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QACpB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACtE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;YACnB,MAAM,CAAC,OAAO,GAAG,yCAAyC,CAAC;YAC3D,MAAM,CAAC,KAAK,GAAG,YAAY,OAAO,kBAAkB,CAAC;SACtD;QACD,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;YAC3B,cAAc,CAAC,OAAO,GAAG,yCAAyC,CAAC;YACnE,cAAc,CAAC,KAAK,GAAG,YAAY,OAAO,wBAAwB,CAAC;SACpE;QACD,IAAI,GAAG,KAAK,IAAI,OAAO,SAAS,EAAE;YAChC,OAAO,UAAU,EAAE,CAAC;SACrB;QACD,IAAI,GAAG,KAAK,IAAI,OAAO,kBAAkB,EAAE;YACzC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;SACzB;QACD,IAAI,GAAG,KAAK,IAAI,OAAO,EAAE,EAAE;YACzB,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;SACzE;KACF;IACD,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC,CAAC"}
@@ -5,12 +5,19 @@ import { initializeApp } from 'firebase-admin/app';
5
5
  // eslint-disable-next-line import/no-unresolved
6
6
  import { onRequest } from 'firebase-functions/v2/https';
7
7
  import config from '@cloudcommerce/firebase/lib/config';
8
+ import getEnv from '@cloudcommerce/firebase/lib/env';
9
+ import serveModulesApi from './firebase/serve-modules-api.js';
8
10
 
9
11
  initializeApp();
10
- const options = config.get().httpsFunctionOptions;
12
+ const { httpsFunctionOptions } = config.get();
11
13
 
12
- export const modulesApi = onRequest(options, (request, response) => {
14
+ export const modulesApi = onRequest(httpsFunctionOptions, (req, res) => {
15
+ const { authenticationId, apiKey } = getEnv();
16
+ // Hide API key for security
13
17
  process.env.ECOM_API_KEY = '***';
14
- response.send('Hello modules!');
18
+ serveModulesApi(req, res, {
19
+ authenticationId,
20
+ apiKey,
21
+ });
15
22
  });
16
23
  // # sourceMappingURL=firebase.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"firebase.js","sourceRoot":"","sources":["../src/firebase.ts"],"names":[],"mappings":"AAAA,iDAAiD;AAEjD,OAAO,gCAAgC,CAAC;AACxC,gDAAgD;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,gDAAgD;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,MAAM,MAAM,oCAAoC,CAAC;AAExD,aAAa,EAAE,CAAC;AAChB,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC;AAElD,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;IACjE,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"firebase.js","sourceRoot":"","sources":["../src/firebase.ts"],"names":[],"mappings":"AAAA,iDAAiD;AAEjD,OAAO,gCAAgC,CAAC;AACxC,gDAAgD;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,gDAAgD;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,MAAM,MAAM,oCAAoC,CAAC;AACxD,OAAO,MAAM,MAAM,iCAAiC,CAAC;AACrD,OAAO,eAAe,MAAM,8BAA8B,CAAC;AAE3D,aAAa,EAAE,CAAC;AAChB,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC;AAE9C,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACrE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9C,4BAA4B;IAC5B,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC;IACjC,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE;QACxB,gBAAgB;QAChB,MAAM;KACP,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -4,18 +4,22 @@ import * as listPayments from '../schemas/list_payments.cjs';
4
4
  import * as createTransaction from '../schemas/create_transaction.cjs';
5
5
  import * as checkout from '../schemas/@checkout.cjs';
6
6
 
7
+ const schemas = {
8
+ calculate_shipping: calculateShipping,
9
+ apply_discount: applyDiscount,
10
+ list_payments: listPayments,
11
+ create_transaction: createTransaction,
12
+ '@checkout': checkout,
13
+ };
14
+
7
15
  export default {
8
16
  calculateShipping,
9
17
  applyDiscount,
10
18
  listPayments,
11
19
  createTransaction,
12
20
  checkout,
13
- schemas: {
14
- calculate_shipping: calculateShipping,
15
- apply_discount: applyDiscount,
16
- list_payments: listPayments,
17
- create_transaction: createTransaction,
18
- '@checkout': checkout,
19
- },
21
+ schemas,
20
22
  };
23
+
24
+ export { schemas };
21
25
  // # sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,iBAAiB,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,aAAa,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,iBAAiB,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAC;AAErD,eAAe;IACb,iBAAiB;IACjB,aAAa;IACb,YAAY;IACZ,iBAAiB;IACjB,QAAQ;IAER,OAAO,EAAE;QACP,kBAAkB,EAAE,iBAAiB;QACrC,cAAc,EAAE,aAAa;QAC7B,aAAa,EAAE,YAAY;QAC3B,kBAAkB,EAAE,iBAAiB;QACrC,WAAW,EAAE,QAAQ;KACtB;CACF,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,iBAAiB,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,aAAa,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,iBAAiB,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAC;AAErD,MAAM,OAAO,GAAG;IACd,kBAAkB,EAAE,iBAAiB;IACrC,cAAc,EAAE,aAAa;IAC7B,aAAa,EAAE,YAAY;IAC3B,kBAAkB,EAAE,iBAAiB;IACrC,WAAW,EAAE,QAAQ;CACtB,CAAC;AAEF,eAAe;IACb,iBAAiB;IACjB,aAAa;IACb,YAAY;IACZ,iBAAiB;IACjB,QAAQ;IACR,OAAO;CACR,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudcommerce/modules",
3
3
  "type": "module",
4
- "version": "0.0.40",
4
+ "version": "0.0.43",
5
5
  "description": "E-Com Plus Cloud Commerce modules API",
6
6
  "main": "lib/index.cjs",
7
7
  "exports": {
@@ -24,8 +24,11 @@
24
24
  "build:types": "sh scripts/build-types.sh"
25
25
  },
26
26
  "dependencies": {
27
+ "@cloudcommerce/api": "workspace:*",
27
28
  "@cloudcommerce/firebase": "workspace:*",
28
- "firebase-admin": "^11.0.0",
29
+ "ajv": "^8.11.0",
30
+ "axios": "^0.27.2",
31
+ "firebase-admin": "^11.0.1",
29
32
  "firebase-functions": "^3.22.0",
30
33
  "source-map-support": "^0.5.21"
31
34
  },
@@ -0,0 +1,38 @@
1
+ import type { Response } from 'firebase-functions';
2
+ import Ajv, { Options, ErrorObject } from 'ajv';
3
+
4
+ const ajvOptions: Options = {
5
+ coerceTypes: false,
6
+ useDefaults: true,
7
+ removeAdditional: true,
8
+ // Explicitly set `allErrors` to false, when set to true a DoS attack is possible
9
+ allErrors: false,
10
+ multipleOfPrecision: 5,
11
+ allowMatchingProperties: true,
12
+ };
13
+ const ajv = new Ajv(ajvOptions);
14
+
15
+ const parseAjvErrors = (errors?: ErrorObject[] | null, ajvInstance = ajv) => {
16
+ return ajvInstance.errorsText(errors, { separator: '\n' });
17
+ };
18
+
19
+ const sendRequestError = (res: Response, modName: string, errors?: ErrorObject[] | null) => {
20
+ res.status(400).send({
21
+ status: 400,
22
+ error_code: 'MOD901',
23
+ message: 'Bad-formatted JSON body (POST) or URL params (GET), details in `user_message`',
24
+ user_message: {
25
+ en_us: ajv.errorsText(errors, { separator: '\n' }),
26
+ },
27
+ more_info: `/${modName}/schema`,
28
+ });
29
+ };
30
+
31
+ export default ajv;
32
+
33
+ export {
34
+ ajv,
35
+ ajvOptions,
36
+ parseAjvErrors,
37
+ sendRequestError,
38
+ };
@@ -0,0 +1,72 @@
1
+ import { logger } from 'firebase-functions';
2
+ import axios, { AxiosResponse } from 'axios';
3
+ import config from '@cloudcommerce/firebase/lib/config';
4
+
5
+ // Blacklist urls to prevent consecultive errors
6
+ const blacklist = {};
7
+
8
+ export default async (url: string, data: any, isBigTimeout: boolean) => {
9
+ if (blacklist[url] > 2) {
10
+ logger.log(`> Skipping blacklisted ${url}`);
11
+ const err = new Error('Blacklited endpoint URL');
12
+ return Promise.reject(err);
13
+ }
14
+ const { storeId } = config.get();
15
+
16
+ const debug = (response: AxiosResponse) => {
17
+ const status = response ? response.status : 0;
18
+ if (!blacklist[url]) {
19
+ blacklist[url] = 1;
20
+ } else {
21
+ blacklist[url] += 1;
22
+ }
23
+ setTimeout(() => {
24
+ if (blacklist[url] > 1) {
25
+ blacklist[url] -= 1;
26
+ } else {
27
+ delete blacklist[url];
28
+ }
29
+ }, !status ? 180000 : 6000);
30
+
31
+ logger.info(`${url} : ${status}`);
32
+ if (status >= 400 && status < 500) {
33
+ const { data: resData } = response;
34
+ if (typeof resData === 'object' && resData !== null) {
35
+ const { error, message } = resData;
36
+ if (typeof error === 'string' && error.length && typeof message === 'string') {
37
+ logger.warn(JSON.stringify({ error, message }));
38
+ }
39
+ }
40
+ }
41
+ };
42
+
43
+ return axios({
44
+ method: 'POST',
45
+ maxRedirects: 2,
46
+ responseType: 'json',
47
+ maxContentLength: 1000000, // 1MB
48
+ url,
49
+ data,
50
+ headers: {
51
+ 'X-Store-ID': storeId.toString(),
52
+ },
53
+ // Wait 10s by default and 30s in specific cases
54
+ timeout: isBigTimeout ? 30000 : 10000,
55
+ })
56
+ .then((response) => {
57
+ debug(response);
58
+ return response.data;
59
+ })
60
+ .catch((err) => {
61
+ const { response } = err;
62
+ debug(response);
63
+ if (err.message || err.code) {
64
+ let msg = `Axios error ${err.code}: ${err.message}`;
65
+ if (data) {
66
+ msg += `\n\n${JSON.stringify(data)}`;
67
+ }
68
+ logger.warn(msg);
69
+ }
70
+ throw err;
71
+ });
72
+ };
@@ -0,0 +1,191 @@
1
+ import type { Request, Response } from 'firebase-functions';
2
+ import type { Applications } from '@cloudcommerce/types';
3
+ import { logger } from 'firebase-functions';
4
+ import Ajv, { ValidateFunction } from 'ajv';
5
+ import api, { ApiError, ApiConfig } from '@cloudcommerce/api';
6
+ import config from '@cloudcommerce/firebase/lib/config';
7
+ import {
8
+ ajv,
9
+ ajvOptions,
10
+ parseAjvErrors,
11
+ sendRequestError,
12
+ } from './ajv';
13
+ import callAppModule from './call-app-module';
14
+
15
+ const ajvAppsResponse = new Ajv({ ...ajvOptions, allErrors: true });
16
+
17
+ // Cache apps list and no params modules results
18
+ const appsCache = {};
19
+ const resultsCache = {};
20
+
21
+ async function runModule(
22
+ apiAuth: { authenticationId: string, apiKey: string },
23
+ params: { [key: string]: any },
24
+ res: Response,
25
+ modName: string,
26
+ validate: ValidateFunction,
27
+ responseValidate: ValidateFunction,
28
+ appId?: any,
29
+ ) {
30
+ const respond = (result: any[]) => res.send({
31
+ result,
32
+ meta: params,
33
+ });
34
+ const { storeId } = config.get();
35
+ const isEmptyParams = (!params || !Object.keys(params).length);
36
+ if (!validate(params)) {
37
+ return sendRequestError(res, modName, validate.errors);
38
+ }
39
+ let canCache = true;
40
+ const cacheKey = `${storeId}:${modName}`;
41
+ const listAppsParams: ApiConfig['params'] = {
42
+ state: 'active',
43
+ [`modules.${modName}.enabled`]: true,
44
+ fields: `_id,app_id,version,data,hidden_data,modules.${modName}`,
45
+ };
46
+ if (
47
+ appId
48
+ && (typeof appId === 'number' || (typeof appId === 'string' && /^\d+$/.test(appId)))
49
+ ) {
50
+ canCache = false;
51
+ listAppsParams.app_id = appId;
52
+ listAppsParams.limit = 1;
53
+ }
54
+ let canCacheResults = false;
55
+ if (canCache && isEmptyParams) {
56
+ if (resultsCache[cacheKey]) {
57
+ return respond(resultsCache[cacheKey]);
58
+ }
59
+ canCacheResults = true;
60
+ }
61
+
62
+ let appsList: Applications[];
63
+ if (canCache && appsCache[cacheKey]) {
64
+ appsList = appsCache[cacheKey];
65
+ } else {
66
+ try {
67
+ const { data } = await api.get('applications', {
68
+ ...apiAuth,
69
+ params: listAppsParams,
70
+ });
71
+ appsList = data.result;
72
+ } catch (err: any) {
73
+ logger.error(err);
74
+ const error = err as ApiError;
75
+ return res.status(500).send({
76
+ status: 500,
77
+ error_code: 'MOD801',
78
+ message: `Store API returned status ${error.statusCode} trying to list apps`,
79
+ more_info: error.data?.user_message?.en_us,
80
+ });
81
+ }
82
+ }
83
+
84
+ if (Array.isArray(appsList)) {
85
+ if (!appsList.length) {
86
+ return respond([]);
87
+ }
88
+ if (canCache && !appsCache[cacheKey]) {
89
+ appsCache[cacheKey] = appsList;
90
+ setTimeout(() => {
91
+ appsCache[cacheKey] = null;
92
+ delete appsCache[cacheKey];
93
+ }, appsList.length ? 60000 : 3000);
94
+ }
95
+ const moduleReqs: Promise<any>[] = [];
96
+ for (let i = 0; i < appsList.length; i++) {
97
+ const application = appsList[i] as Applications & { modules: { [key: string]: any } };
98
+ if (!application.hidden_data) {
99
+ application.hidden_data = {};
100
+ }
101
+ if (!application.data) {
102
+ application.data = {};
103
+ }
104
+ const appModuleUrl = application.modules[modName].endpoint as string;
105
+ // Handle request with big timeout if proxying one app (by ID) only
106
+ const isBigTimeout = !!(appId);
107
+ const appModuleBody = {
108
+ module: modName,
109
+ params,
110
+ application,
111
+ };
112
+
113
+ const reqStartTime = Date.now();
114
+ moduleReqs.push(new Promise((resolve) => {
115
+ let response: any;
116
+ let isError = false;
117
+ let errorMessage: string | null = null;
118
+ callAppModule(appModuleUrl, appModuleBody, isBigTimeout)
119
+ .then((appResponse) => {
120
+ response = appResponse;
121
+ })
122
+ .catch((err: any) => {
123
+ response = null;
124
+ isError = true;
125
+ errorMessage = err.message;
126
+ })
127
+ .finally(() => {
128
+ const result = {
129
+ _id: application._id,
130
+ app_id: application.app_id,
131
+ took: Date.now() - reqStartTime,
132
+ version: application.version,
133
+ validated: false,
134
+ response_errors: null,
135
+ error: isError,
136
+ error_message: errorMessage,
137
+ response,
138
+ };
139
+ if (typeof response === 'object' && response !== null) {
140
+ result.validated = responseValidate(response);
141
+ if (!result.validated) {
142
+ // @ts-ignore
143
+ result.response_errors = parseAjvErrors(
144
+ responseValidate.errors,
145
+ ajvAppsResponse,
146
+ );
147
+ }
148
+ }
149
+ resolve(result);
150
+ });
151
+ }));
152
+ }
153
+
154
+ return Promise.all(moduleReqs).then((results) => {
155
+ if (!results.find(({ response }) => response)) {
156
+ res.status(409);
157
+ canCacheResults = false;
158
+ }
159
+ if (canCacheResults && !resultsCache[cacheKey]) {
160
+ resultsCache[cacheKey] = results;
161
+ setTimeout(() => {
162
+ resultsCache[cacheKey] = null;
163
+ delete resultsCache[cacheKey];
164
+ }, 60000);
165
+ }
166
+ return respond(results);
167
+ });
168
+ }
169
+ // Shoud never happen
170
+ return res.sendStatus(501);
171
+ }
172
+
173
+ export default (
174
+ modName: string,
175
+ schema: { [key: string]: any },
176
+ responseSchema: { [key: string]: any },
177
+ req: Request,
178
+ res: Response,
179
+ apiAuth: { authenticationId: string, apiKey: string },
180
+ ) => {
181
+ const validate = ajv.compile(schema);
182
+ const responseValidate = ajvAppsResponse.compile(responseSchema);
183
+ return {
184
+ GET() {
185
+ runModule(apiAuth, req.query, res, modName, validate, responseValidate);
186
+ },
187
+ POST() {
188
+ runModule(apiAuth, req.body, res, modName, validate, responseValidate, req.query.app_id);
189
+ },
190
+ };
191
+ };
File without changes
@@ -0,0 +1,66 @@
1
+ import type { Request, Response } from 'firebase-functions';
2
+ import { schemas } from '../index';
3
+ import handleModule from './handle-module';
4
+
5
+ export default (
6
+ req: Request,
7
+ res: Response,
8
+ apiAuth: { authenticationId: string, apiKey: string },
9
+ ) => {
10
+ const { method } = req;
11
+ if (method !== 'POST' && method !== 'GET') {
12
+ return res.sendStatus(405);
13
+ }
14
+ if (
15
+ method === 'POST'
16
+ && (!req.body || typeof req.body !== 'object' || Array.isArray(req.body))
17
+ ) {
18
+ return res.sendStatus(400);
19
+ }
20
+
21
+ let { url } = req;
22
+ if (url.endsWith('.json')) {
23
+ url = url.slice(0, -5);
24
+ }
25
+ const modName = url.split('/')[1];
26
+ const sendSchema = (isResponseSchema = false) => {
27
+ return res.status(200)
28
+ .setHeader('Cache-Control', 'public, max-age=3600')
29
+ .send(schemas[modName][isResponseSchema ? 'response' : 'params']);
30
+ };
31
+
32
+ if (modName === '@checkout') {
33
+ if (url === '/@checkout') {
34
+ return res.status(200).send({
35
+ status: 200,
36
+ message: 'CHECKOUT',
37
+ });
38
+ }
39
+ if (url === '/@checkout/schema') {
40
+ return sendSchema();
41
+ }
42
+ return res.sendStatus(404);
43
+ }
44
+
45
+ if (schemas[modName]) {
46
+ const { params: schema, response: responseSchema } = schemas[modName];
47
+ if (!schema.$schema) {
48
+ schema.$schema = 'http://json-schema.org/draft-06/schema#';
49
+ schema.title = `Module \`${modName}\`: Params model`;
50
+ }
51
+ if (!responseSchema.$schema) {
52
+ responseSchema.$schema = 'http://json-schema.org/draft-06/schema#';
53
+ responseSchema.title = `Module \`${modName}\`: App response model`;
54
+ }
55
+ if (url === `/${modName}/schema`) {
56
+ return sendSchema();
57
+ }
58
+ if (url === `/${modName}/response_schema`) {
59
+ return sendSchema(true);
60
+ }
61
+ if (url === `/${modName}`) {
62
+ return handleModule(modName, schema, responseSchema, req, res, apiAuth);
63
+ }
64
+ }
65
+ return res.sendStatus(404);
66
+ };
@@ -6,11 +6,18 @@ import { initializeApp } from 'firebase-admin/app';
6
6
  // eslint-disable-next-line import/no-unresolved
7
7
  import { onRequest } from 'firebase-functions/v2/https';
8
8
  import config from '@cloudcommerce/firebase/lib/config';
9
+ import getEnv from '@cloudcommerce/firebase/lib/env';
10
+ import serveModulesApi from './firebase/serve-modules-api';
9
11
 
10
12
  initializeApp();
11
- const options = config.get().httpsFunctionOptions;
13
+ const { httpsFunctionOptions } = config.get();
12
14
 
13
- export const modulesApi = onRequest(options, (request, response) => {
15
+ export const modulesApi = onRequest(httpsFunctionOptions, (req, res) => {
16
+ const { authenticationId, apiKey } = getEnv();
17
+ // Hide API key for security
14
18
  process.env.ECOM_API_KEY = '***';
15
- response.send('Hello modules!');
19
+ serveModulesApi(req, res, {
20
+ authenticationId,
21
+ apiKey,
22
+ });
16
23
  });
@@ -4,18 +4,21 @@ import * as listPayments from '../schemas/list_payments.cjs';
4
4
  import * as createTransaction from '../schemas/create_transaction.cjs';
5
5
  import * as checkout from '../schemas/@checkout.cjs';
6
6
 
7
+ const schemas = {
8
+ calculate_shipping: calculateShipping,
9
+ apply_discount: applyDiscount,
10
+ list_payments: listPayments,
11
+ create_transaction: createTransaction,
12
+ '@checkout': checkout,
13
+ };
14
+
7
15
  export default {
8
16
  calculateShipping,
9
17
  applyDiscount,
10
18
  listPayments,
11
19
  createTransaction,
12
20
  checkout,
13
-
14
- schemas: {
15
- calculate_shipping: calculateShipping,
16
- apply_discount: applyDiscount,
17
- list_payments: listPayments,
18
- create_transaction: createTransaction,
19
- '@checkout': checkout,
20
- },
21
+ schemas,
21
22
  };
23
+
24
+ export { schemas };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudcommerce/passport",
3
3
  "type": "module",
4
- "version": "0.0.40",
4
+ "version": "0.0.43",
5
5
  "description": "E-Com Plus Cloud Commerce customers authentication (passport) API",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -25,7 +25,7 @@
25
25
  "dependencies": {
26
26
  "@cloudcommerce/api": "workspace:*",
27
27
  "@cloudcommerce/firebase": "workspace:*",
28
- "firebase-admin": "^11.0.0",
28
+ "firebase-admin": "^11.0.1",
29
29
  "firebase-functions": "^3.22.0",
30
30
  "source-map-support": "^0.5.21"
31
31
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudcommerce/ssr",
3
3
  "type": "module",
4
- "version": "0.0.40",
4
+ "version": "0.0.43",
5
5
  "description": "E-Com Plus Cloud Commerce storefront SSR",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -26,7 +26,7 @@
26
26
  "@cloudcommerce/api": "workspace:*",
27
27
  "@cloudcommerce/firebase": "workspace:*",
28
28
  "@cloudcommerce/storefront": "workspace:*",
29
- "firebase-admin": "^11.0.0",
29
+ "firebase-admin": "^11.0.1",
30
30
  "firebase-functions": "^3.22.0",
31
31
  "source-map-support": "^0.5.21"
32
32
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudcommerce/storefront",
3
3
  "type": "module",
4
- "version": "0.0.40",
4
+ "version": "0.0.43",
5
5
  "description": "E-Com Plus Cloud Commerce storefront with Astro",
6
6
  "main": "src/index.js",
7
7
  "repository": {
@@ -20,6 +20,6 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@cloudcommerce/api": "workspace:*",
23
- "astro": "1.0.0-rc.1"
23
+ "astro": "1.0.0-rc.3"
24
24
  }
25
25
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudcommerce/types",
3
3
  "type": "module",
4
- "version": "0.0.40",
4
+ "version": "0.0.43",
5
5
  "description": "E-Com Plus Cloud Commerce reusable type definitions",
6
6
  "main": "index.ts",
7
7
  "repository": {