@webiny/aws-helpers 5.26.1-beta.0

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 (83) hide show
  1. package/LICENSE +26 -0
  2. package/cloudfrontFunctions/cookies.d.ts +8 -0
  3. package/cloudfrontFunctions/cookies.js +28 -0
  4. package/cloudfrontFunctions/cookies.js.map +1 -0
  5. package/cloudfrontFunctions/headers.d.ts +5 -0
  6. package/cloudfrontFunctions/headers.js +26 -0
  7. package/cloudfrontFunctions/headers.js.map +1 -0
  8. package/cloudfrontFunctions/index.d.ts +6 -0
  9. package/cloudfrontFunctions/index.js +6 -0
  10. package/cloudfrontFunctions/index.js.map +1 -0
  11. package/cloudfrontFunctions/querystring.d.ts +2 -0
  12. package/cloudfrontFunctions/querystring.js +38 -0
  13. package/cloudfrontFunctions/querystring.js.map +1 -0
  14. package/cloudfrontFunctions/redirect.d.ts +8 -0
  15. package/cloudfrontFunctions/redirect.js +22 -0
  16. package/cloudfrontFunctions/redirect.js.map +1 -0
  17. package/cloudfrontFunctions/types.d.ts +78 -0
  18. package/cloudfrontFunctions/types.js +1 -0
  19. package/cloudfrontFunctions/types.js.map +1 -0
  20. package/cloudfrontFunctions/utils.d.ts +17 -0
  21. package/cloudfrontFunctions/utils.js +21 -0
  22. package/cloudfrontFunctions/utils.js.map +1 -0
  23. package/index.d.ts +1 -0
  24. package/index.js +1 -0
  25. package/index.js.map +1 -0
  26. package/lambdaEdge/cookies.d.ts +3 -0
  27. package/lambdaEdge/cookies.js +25 -0
  28. package/lambdaEdge/cookies.js.map +1 -0
  29. package/lambdaEdge/headers.d.ts +6 -0
  30. package/lambdaEdge/headers.js +8 -0
  31. package/lambdaEdge/headers.js.map +1 -0
  32. package/lambdaEdge/index.d.ts +7 -0
  33. package/lambdaEdge/index.js +7 -0
  34. package/lambdaEdge/index.js.map +1 -0
  35. package/lambdaEdge/redirect.d.ts +8 -0
  36. package/lambdaEdge/redirect.js +32 -0
  37. package/lambdaEdge/redirect.js.map +1 -0
  38. package/lambdaEdge/request.d.ts +2 -0
  39. package/lambdaEdge/request.js +19 -0
  40. package/lambdaEdge/request.js.map +1 -0
  41. package/lambdaEdge/response.d.ts +2 -0
  42. package/lambdaEdge/response.js +7 -0
  43. package/lambdaEdge/response.js.map +1 -0
  44. package/lambdaEdge/types.d.ts +10 -0
  45. package/lambdaEdge/types.js +1 -0
  46. package/lambdaEdge/types.js.map +1 -0
  47. package/lambdaEdge/utils.d.ts +3 -0
  48. package/lambdaEdge/utils.js +6 -0
  49. package/lambdaEdge/utils.js.map +1 -0
  50. package/package.json +48 -0
  51. package/stagedRollouts/functions/adminOriginRequest.d.ts +2 -0
  52. package/stagedRollouts/functions/adminOriginRequest.js +29 -0
  53. package/stagedRollouts/functions/adminOriginRequest.js.map +1 -0
  54. package/stagedRollouts/functions/configOriginRequest.d.ts +2 -0
  55. package/stagedRollouts/functions/configOriginRequest.js +52 -0
  56. package/stagedRollouts/functions/configOriginRequest.js.map +1 -0
  57. package/stagedRollouts/functions/originRequest.d.ts +2 -0
  58. package/stagedRollouts/functions/originRequest.js +15 -0
  59. package/stagedRollouts/functions/originRequest.js.map +1 -0
  60. package/stagedRollouts/functions/viewerRequest.d.ts +1 -0
  61. package/stagedRollouts/functions/viewerRequest.js +42 -0
  62. package/stagedRollouts/functions/viewerRequest.js.map +1 -0
  63. package/stagedRollouts/functions/viewerResponse.d.ts +1 -0
  64. package/stagedRollouts/functions/viewerResponse.js +18 -0
  65. package/stagedRollouts/functions/viewerResponse.js.map +1 -0
  66. package/stagedRollouts/utils/common.d.ts +3 -0
  67. package/stagedRollouts/utils/common.js +5 -0
  68. package/stagedRollouts/utils/common.js.map +1 -0
  69. package/stagedRollouts/utils/headerBlacklist.d.ts +5 -0
  70. package/stagedRollouts/utils/headerBlacklist.js +13 -0
  71. package/stagedRollouts/utils/headerBlacklist.js.map +1 -0
  72. package/stagedRollouts/utils/loadOriginPage.d.ts +8 -0
  73. package/stagedRollouts/utils/loadOriginPage.js +107 -0
  74. package/stagedRollouts/utils/loadOriginPage.js.map +1 -0
  75. package/stagedRollouts/utils/loadTrafficSplittingConfig.d.ts +12 -0
  76. package/stagedRollouts/utils/loadTrafficSplittingConfig.js +65 -0
  77. package/stagedRollouts/utils/loadTrafficSplittingConfig.js.map +1 -0
  78. package/stagedRollouts/utils/loadVariantOrigin.d.ts +4 -0
  79. package/stagedRollouts/utils/loadVariantOrigin.js +83 -0
  80. package/stagedRollouts/utils/loadVariantOrigin.js.map +1 -0
  81. package/stagedRollouts/utils/log.d.ts +1 -0
  82. package/stagedRollouts/utils/log.js +7 -0
  83. package/stagedRollouts/utils/log.js.map +1 -0
@@ -0,0 +1,2 @@
1
+ declare const _default: import("aws-lambda").CloudFrontRequestHandler;
2
+ export default _default;
@@ -0,0 +1,29 @@
1
+ import { defineLambdaEdgeRequestHandler, setDomainOrigin } from "../../lambdaEdge";
2
+ import { pointsToFile } from "../utils/common";
3
+ import { loadOriginPage } from "../utils/loadOriginPage";
4
+ import { loadVariantOrigin } from "../utils/loadVariantOrigin";
5
+ export default defineLambdaEdgeRequestHandler(async event => {
6
+ try {
7
+ const result = await loadVariantOrigin(event);
8
+
9
+ if ("variant" in result) {
10
+ const cf = event.Records[0].cf;
11
+ const request = cf.request;
12
+ const variant = result.variant; // For file requests we just pass the request to proper origin.
13
+
14
+ if (pointsToFile(request.uri)) {
15
+ setDomainOrigin(request, variant.domain);
16
+ return request;
17
+ } // For pages we make a custom HTTP request to the origin and transform page properly.
18
+ // For example we change asset URLs to be absolute.
19
+
20
+
21
+ return await loadOriginPage(variant.domain, request.uri);
22
+ }
23
+
24
+ return result;
25
+ } catch (e) {
26
+ console.error(e);
27
+ throw e;
28
+ }
29
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["adminOriginRequest.ts"],"names":["defineLambdaEdgeRequestHandler","setDomainOrigin","pointsToFile","loadOriginPage","loadVariantOrigin","event","result","cf","Records","request","variant","uri","domain","e","console","error"],"mappings":"AAAA,SAASA,8BAAT,EAAyCC,eAAzC;AAEA,SAASC,YAAT;AACA,SAASC,cAAT;AACA,SAASC,iBAAT;AAEA,eAAeJ,8BAA8B,CAAC,MAAMK,KAAN,IAAe;AACzD,MAAI;AACA,UAAMC,MAAM,GAAG,MAAMF,iBAAiB,CAACC,KAAD,CAAtC;;AAEA,QAAI,aAAaC,MAAjB,EAAyB;AACrB,YAAMC,EAAE,GAAGF,KAAK,CAACG,OAAN,CAAc,CAAd,EAAiBD,EAA5B;AACA,YAAME,OAAO,GAAGF,EAAE,CAACE,OAAnB;AACA,YAAMC,OAAO,GAAGJ,MAAM,CAACI,OAAvB,CAHqB,CAKrB;;AACA,UAAIR,YAAY,CAACO,OAAO,CAACE,GAAT,CAAhB,EAA+B;AAC3BV,QAAAA,eAAe,CAACQ,OAAD,EAAUC,OAAO,CAACE,MAAlB,CAAf;AACA,eAAOH,OAAP;AACH,OAToB,CAWrB;AACA;;;AACA,aAAO,MAAMN,cAAc,CAACO,OAAO,CAACE,MAAT,EAAiBH,OAAO,CAACE,GAAzB,CAA3B;AACH;;AACD,WAAOL,MAAP;AACH,GAnBD,CAmBE,OAAOO,CAAP,EAAU;AACRC,IAAAA,OAAO,CAACC,KAAR,CAAcF,CAAd;AACA,UAAMA,CAAN;AACH;AACJ,CAxB4C,CAA7C","sourcesContent":["import { defineLambdaEdgeRequestHandler, setDomainOrigin } from \"~/lambdaEdge\";\n\nimport { pointsToFile } from \"../utils/common\";\nimport { loadOriginPage } from \"../utils/loadOriginPage\";\nimport { loadVariantOrigin } from \"../utils/loadVariantOrigin\";\n\nexport default defineLambdaEdgeRequestHandler(async event => {\n try {\n const result = await loadVariantOrigin(event);\n\n if (\"variant\" in result) {\n const cf = event.Records[0].cf;\n const request = cf.request;\n const variant = result.variant;\n\n // For file requests we just pass the request to proper origin.\n if (pointsToFile(request.uri)) {\n setDomainOrigin(request, variant.domain);\n return request;\n }\n\n // For pages we make a custom HTTP request to the origin and transform page properly.\n // For example we change asset URLs to be absolute.\n return await loadOriginPage(variant.domain, request.uri);\n }\n return result;\n } catch (e) {\n console.error(e);\n throw e;\n }\n});\n"]}
@@ -0,0 +1,2 @@
1
+ declare const _default: import("aws-lambda").CloudFrontRequestHandler;
2
+ export default _default;
@@ -0,0 +1,52 @@
1
+ import { defineLambdaEdgeRequestHandler, setHeader } from "../../lambdaEdge";
2
+ import { get } from "https";
3
+ export default defineLambdaEdgeRequestHandler(async event => {
4
+ try {
5
+ const configJson = await loadConfigJson(event);
6
+ const response = {
7
+ status: "200",
8
+ statusDescription: "OK",
9
+ headers: {},
10
+ body: configJson
11
+ };
12
+ setHeader(response.headers, {
13
+ key: "Content-Type",
14
+ value: "application/json"
15
+ });
16
+ setHeader(response.headers, {
17
+ key: "Cache-Control",
18
+ value: "public, max-age=31536000"
19
+ });
20
+ return response;
21
+ } catch (e) {
22
+ console.error(e);
23
+ throw e;
24
+ }
25
+ });
26
+
27
+ function loadConfigJson(event) {
28
+ return new Promise((resolve, reject) => {
29
+ let dataString = ""; // Retrieve domain of the CloudFront distribution.
30
+
31
+ const domain = event.Records[0].cf.config.distributionDomainName;
32
+ const req = get({
33
+ hostname: domain,
34
+ port: 443,
35
+ // TODO: we will call WCP instead of a static file here
36
+ path: "/_config.json"
37
+ }, function (res) {
38
+ res.on("data", chunk => {
39
+ dataString += chunk;
40
+ });
41
+ res.on("end", () => {
42
+ resolve(dataString);
43
+ });
44
+ });
45
+ req.on("error", e => {
46
+ reject({
47
+ statusCode: 500,
48
+ body: e.message
49
+ });
50
+ });
51
+ });
52
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["configOriginRequest.ts"],"names":["defineLambdaEdgeRequestHandler","setHeader","get","event","configJson","loadConfigJson","response","status","statusDescription","headers","body","key","value","e","console","error","Promise","resolve","reject","dataString","domain","Records","cf","config","distributionDomainName","req","hostname","port","path","res","on","chunk","statusCode","message"],"mappings":"AAAA,SAGIA,8BAHJ,EAIIC,SAJJ;AAOA,SAASC,GAAT,QAAoB,OAApB;AAEA,eAAeF,8BAA8B,CAAC,MAAMG,KAAN,IAAe;AACzD,MAAI;AACA,UAAMC,UAAU,GAAG,MAAMC,cAAc,CAACF,KAAD,CAAvC;AACA,UAAMG,QAA4B,GAAG;AACjCC,MAAAA,MAAM,EAAE,KADyB;AAEjCC,MAAAA,iBAAiB,EAAE,IAFc;AAGjCC,MAAAA,OAAO,EAAE,EAHwB;AAIjCC,MAAAA,IAAI,EAAEN;AAJ2B,KAArC;AAOAH,IAAAA,SAAS,CAACK,QAAQ,CAACG,OAAV,EAAmB;AACxBE,MAAAA,GAAG,EAAE,cADmB;AAExBC,MAAAA,KAAK,EAAE;AAFiB,KAAnB,CAAT;AAKAX,IAAAA,SAAS,CAACK,QAAQ,CAACG,OAAV,EAAmB;AACxBE,MAAAA,GAAG,EAAE,eADmB;AAExBC,MAAAA,KAAK,EAAE;AAFiB,KAAnB,CAAT;AAKA,WAAON,QAAP;AACH,GApBD,CAoBE,OAAOO,CAAP,EAAU;AACRC,IAAAA,OAAO,CAACC,KAAR,CAAcF,CAAd;AACA,UAAMA,CAAN;AACH;AACJ,CAzB4C,CAA7C;;AA2BA,SAASR,cAAT,CAAwBF,KAAxB,EAAuD;AACnD,SAAO,IAAIa,OAAJ,CAAoB,CAACC,OAAD,EAAUC,MAAV,KAAqB;AAC5C,QAAIC,UAAU,GAAG,EAAjB,CAD4C,CAG5C;;AACA,UAAMC,MAAM,GAAGjB,KAAK,CAACkB,OAAN,CAAc,CAAd,EAAiBC,EAAjB,CAAoBC,MAApB,CAA2BC,sBAA1C;AAEA,UAAMC,GAAG,GAAGvB,GAAG,CACX;AACIwB,MAAAA,QAAQ,EAAEN,MADd;AAEIO,MAAAA,IAAI,EAAE,GAFV;AAGI;AACAC,MAAAA,IAAI,EAAE;AAJV,KADW,EAOX,UAAUC,GAAV,EAAe;AACXA,MAAAA,GAAG,CAACC,EAAJ,CAAO,MAAP,EAAeC,KAAK,IAAI;AACpBZ,QAAAA,UAAU,IAAIY,KAAd;AACH,OAFD;AAGAF,MAAAA,GAAG,CAACC,EAAJ,CAAO,KAAP,EAAc,MAAM;AAChBb,QAAAA,OAAO,CAACE,UAAD,CAAP;AACH,OAFD;AAGH,KAdU,CAAf;AAiBAM,IAAAA,GAAG,CAACK,EAAJ,CAAO,OAAP,EAAgBjB,CAAC,IAAI;AACjBK,MAAAA,MAAM,CAAC;AACHc,QAAAA,UAAU,EAAE,GADT;AAEHtB,QAAAA,IAAI,EAAEG,CAAC,CAACoB;AAFL,OAAD,CAAN;AAIH,KALD;AAMH,GA7BM,CAAP;AA8BH","sourcesContent":["import {\n CloudFrontRequestEvent,\n CloudFrontResponse,\n defineLambdaEdgeRequestHandler,\n setHeader\n} from \"~/lambdaEdge\";\n\nimport { get } from \"https\";\n\nexport default defineLambdaEdgeRequestHandler(async event => {\n try {\n const configJson = await loadConfigJson(event);\n const response: CloudFrontResponse = {\n status: \"200\",\n statusDescription: \"OK\",\n headers: {},\n body: configJson\n };\n\n setHeader(response.headers, {\n key: \"Content-Type\",\n value: \"application/json\"\n });\n\n setHeader(response.headers, {\n key: \"Cache-Control\",\n value: \"public, max-age=31536000\"\n });\n\n return response;\n } catch (e) {\n console.error(e);\n throw e;\n }\n});\n\nfunction loadConfigJson(event: CloudFrontRequestEvent) {\n return new Promise<string>((resolve, reject) => {\n let dataString = \"\";\n\n // Retrieve domain of the CloudFront distribution.\n const domain = event.Records[0].cf.config.distributionDomainName;\n\n const req = get(\n {\n hostname: domain,\n port: 443,\n // TODO: we will call WCP instead of a static file here\n path: \"/_config.json\"\n },\n function (res) {\n res.on(\"data\", chunk => {\n dataString += chunk;\n });\n res.on(\"end\", () => {\n resolve(dataString);\n });\n }\n );\n\n req.on(\"error\", e => {\n reject({\n statusCode: 500,\n body: e.message\n });\n });\n });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ declare const _default: import("aws-lambda").CloudFrontRequestHandler;
2
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import { defineLambdaEdgeRequestHandler, setDomainOrigin } from "../../lambdaEdge";
2
+ import { loadVariantOrigin } from "../utils/loadVariantOrigin";
3
+ export default defineLambdaEdgeRequestHandler(async event => {
4
+ const result = await loadVariantOrigin(event);
5
+
6
+ if ("variant" in result) {
7
+ const cf = event.Records[0].cf;
8
+ const request = cf.request;
9
+ const variant = result.variant;
10
+ setDomainOrigin(request, variant.domain);
11
+ return request;
12
+ }
13
+
14
+ return result;
15
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["originRequest.ts"],"names":["defineLambdaEdgeRequestHandler","setDomainOrigin","loadVariantOrigin","event","result","cf","Records","request","variant","domain"],"mappings":"AAAA,SAASA,8BAAT,EAAyCC,eAAzC;AAEA,SAASC,iBAAT;AAEA,eAAeF,8BAA8B,CAAC,MAAMG,KAAN,IAAe;AACzD,QAAMC,MAAM,GAAG,MAAMF,iBAAiB,CAACC,KAAD,CAAtC;;AAEA,MAAI,aAAaC,MAAjB,EAAyB;AACrB,UAAMC,EAAE,GAAGF,KAAK,CAACG,OAAN,CAAc,CAAd,EAAiBD,EAA5B;AACA,UAAME,OAAO,GAAGF,EAAE,CAACE,OAAnB;AACA,UAAMC,OAAO,GAAGJ,MAAM,CAACI,OAAvB;AAEAP,IAAAA,eAAe,CAACM,OAAD,EAAUC,OAAO,CAACC,MAAlB,CAAf;AACA,WAAOF,OAAP;AACH;;AAED,SAAOH,MAAP;AACH,CAb4C,CAA7C","sourcesContent":["import { defineLambdaEdgeRequestHandler, setDomainOrigin } from \"~/lambdaEdge\";\n\nimport { loadVariantOrigin } from \"../utils/loadVariantOrigin\";\n\nexport default defineLambdaEdgeRequestHandler(async event => {\n const result = await loadVariantOrigin(event);\n\n if (\"variant\" in result) {\n const cf = event.Records[0].cf;\n const request = cf.request;\n const variant = result.variant;\n\n setDomainOrigin(request, variant.domain);\n return request;\n }\n\n return result;\n});\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import { defineCloudfrontFunctionRequestHandler } from "../../cloudfrontFunctions";
2
+ import { variantFixedKey, variantRandomKey } from "../utils/common";
3
+ defineCloudfrontFunctionRequestHandler(event => {
4
+ var _request$cookies, _request$cookies$vari, _request$headers$vari, _request$cookies2, _request$cookies2$var;
5
+
6
+ const request = event.request; // Try to get stage name from cookie or header.
7
+
8
+ const variantFixed = ((_request$cookies = request.cookies) === null || _request$cookies === void 0 ? void 0 : (_request$cookies$vari = _request$cookies[variantFixedKey]) === null || _request$cookies$vari === void 0 ? void 0 : _request$cookies$vari.value) || ((_request$headers$vari = request.headers[variantFixedKey]) === null || _request$headers$vari === void 0 ? void 0 : _request$headers$vari.value);
9
+
10
+ if (variantFixed) {
11
+ // If there is a fixed variant set, we just pass it further to origin request handler.
12
+ request.headers[variantFixedKey] = {
13
+ value: variantFixed
14
+ }; // If variant is explicitly selected, remove any random header a user may have passed.
15
+ // We want to have either one or another for better cache hit rate.
16
+
17
+ delete request.headers[variantRandomKey];
18
+ return request;
19
+ } // Otherwise we try to retrieve randomized number from user cookie.
20
+ // This random value will be passed further to the origin request to select based on config.
21
+
22
+
23
+ let variantRandom = Number((_request$cookies2 = request.cookies) === null || _request$cookies2 === void 0 ? void 0 : (_request$cookies2$var = _request$cookies2[variantRandomKey]) === null || _request$cookies2$var === void 0 ? void 0 : _request$cookies2$var.value);
24
+
25
+ if (isNaN(variantRandom) || variantRandom < 1 || variantRandom > 100) {
26
+ // If no value is present we simply randomize one.
27
+ // This formula gives you an integer 1-100 (inclusive).
28
+ // This way we have exactly 100 possible values.
29
+ // Math.random() return values in range [0, 1) - 0 inclusive, 1 exclusive.
30
+ // So we need to adjust it a litte bit to achieve what we want.
31
+ variantRandom = Math.floor(Math.random() * 100 + 1);
32
+ } // Adjust random value to a specific interval optimize caching.
33
+ // Less possible values means less separate cache entries in CDN an better cache hit ratio.
34
+ // TODO this value can set during deployment, to allow users to decide on trade-off between better caching and finer traffic splitting
35
+
36
+
37
+ variantRandom -= variantRandom % 5;
38
+ request.headers[variantRandomKey] = {
39
+ value: variantRandom.toString()
40
+ };
41
+ return request;
42
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["viewerRequest.ts"],"names":["defineCloudfrontFunctionRequestHandler","variantFixedKey","variantRandomKey","event","request","variantFixed","cookies","value","headers","variantRandom","Number","isNaN","Math","floor","random","toString"],"mappings":"AAAA,SAASA,sCAAT;AAEA,SAASC,eAAT,EAA0BC,gBAA1B;AAEAF,sCAAsC,CAACG,KAAK,IAAI;AAAA;;AAC5C,QAAMC,OAAO,GAAGD,KAAK,CAACC,OAAtB,CAD4C,CAG5C;;AACA,QAAMC,YAAY,GACd,qBAAAD,OAAO,CAACE,OAAR,+FAAkBL,eAAlB,iFAAoCM,KAApC,+BAA6CH,OAAO,CAACI,OAAR,CAAgBP,eAAhB,CAA7C,0DAA6C,sBAAkCM,KAA/E,CADJ;;AAEA,MAAIF,YAAJ,EAAkB;AACd;AACAD,IAAAA,OAAO,CAACI,OAAR,CAAgBP,eAAhB,IAAmC;AAC/BM,MAAAA,KAAK,EAAEF;AADwB,KAAnC,CAFc,CAMd;AACA;;AACA,WAAOD,OAAO,CAACI,OAAR,CAAgBN,gBAAhB,CAAP;AACA,WAAOE,OAAP;AACH,GAhB2C,CAkB5C;AACA;;;AACA,MAAIK,aAAa,GAAGC,MAAM,sBAACN,OAAO,CAACE,OAAT,+EAAC,kBAAkBJ,gBAAlB,CAAD,0DAAC,sBAAqCK,KAAtC,CAA1B;;AACA,MAAII,KAAK,CAACF,aAAD,CAAL,IAAwBA,aAAa,GAAG,CAAxC,IAA6CA,aAAa,GAAG,GAAjE,EAAsE;AAClE;AACA;AACA;AACA;AACA;AACAA,IAAAA,aAAa,GAAGG,IAAI,CAACC,KAAL,CAAWD,IAAI,CAACE,MAAL,KAAgB,GAAhB,GAAsB,CAAjC,CAAhB;AACH,GA5B2C,CA8B5C;AACA;AACA;;;AACAL,EAAAA,aAAa,IAAIA,aAAa,GAAG,CAAjC;AAEAL,EAAAA,OAAO,CAACI,OAAR,CAAgBN,gBAAhB,IAAoC;AAChCK,IAAAA,KAAK,EAAEE,aAAa,CAACM,QAAd;AADyB,GAApC;AAIA,SAAOX,OAAP;AACH,CAxCqC,CAAtC","sourcesContent":["import { defineCloudfrontFunctionRequestHandler } from \"~/cloudfrontFunctions\";\n\nimport { variantFixedKey, variantRandomKey } from \"../utils/common\";\n\ndefineCloudfrontFunctionRequestHandler(event => {\n const request = event.request;\n\n // Try to get stage name from cookie or header.\n const variantFixed =\n request.cookies?.[variantFixedKey]?.value || request.headers[variantFixedKey]?.value;\n if (variantFixed) {\n // If there is a fixed variant set, we just pass it further to origin request handler.\n request.headers[variantFixedKey] = {\n value: variantFixed\n };\n\n // If variant is explicitly selected, remove any random header a user may have passed.\n // We want to have either one or another for better cache hit rate.\n delete request.headers[variantRandomKey];\n return request;\n }\n\n // Otherwise we try to retrieve randomized number from user cookie.\n // This random value will be passed further to the origin request to select based on config.\n let variantRandom = Number(request.cookies?.[variantRandomKey]?.value);\n if (isNaN(variantRandom) || variantRandom < 1 || variantRandom > 100) {\n // If no value is present we simply randomize one.\n // This formula gives you an integer 1-100 (inclusive).\n // This way we have exactly 100 possible values.\n // Math.random() return values in range [0, 1) - 0 inclusive, 1 exclusive.\n // So we need to adjust it a litte bit to achieve what we want.\n variantRandom = Math.floor(Math.random() * 100 + 1);\n }\n\n // Adjust random value to a specific interval optimize caching.\n // Less possible values means less separate cache entries in CDN an better cache hit ratio.\n // TODO this value can set during deployment, to allow users to decide on trade-off between better caching and finer traffic splitting\n variantRandom -= variantRandom % 5;\n\n request.headers[variantRandomKey] = {\n value: variantRandom.toString()\n };\n\n return request;\n});\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { defineCloudfrontFunctionResponseHandler, setResponseCookie } from "../../cloudfrontFunctions";
2
+ import { variantRandomKey } from "../utils/common";
3
+ defineCloudfrontFunctionResponseHandler(event => {
4
+ var _request$headers$vari;
5
+
6
+ const request = event.request;
7
+ const response = event.response;
8
+ const variantRandom = (_request$headers$vari = request.headers[variantRandomKey]) === null || _request$headers$vari === void 0 ? void 0 : _request$headers$vari.value;
9
+
10
+ if (variantRandom) {
11
+ setResponseCookie(response, {
12
+ name: variantRandomKey,
13
+ value: variantRandom
14
+ });
15
+ }
16
+
17
+ return response;
18
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["viewerResponse.ts"],"names":["defineCloudfrontFunctionResponseHandler","setResponseCookie","variantRandomKey","event","request","response","variantRandom","headers","value","name"],"mappings":"AAAA,SAASA,uCAAT,EAAkDC,iBAAlD;AAEA,SAASC,gBAAT;AAEAF,uCAAuC,CAACG,KAAK,IAAI;AAAA;;AAC7C,QAAMC,OAAO,GAAGD,KAAK,CAACC,OAAtB;AACA,QAAMC,QAAQ,GAAGF,KAAK,CAACE,QAAvB;AAEA,QAAMC,aAAa,4BAAGF,OAAO,CAACG,OAAR,CAAgBL,gBAAhB,CAAH,0DAAG,sBAAmCM,KAAzD;;AACA,MAAIF,aAAJ,EAAmB;AACfL,IAAAA,iBAAiB,CAACI,QAAD,EAAW;AACxBI,MAAAA,IAAI,EAAEP,gBADkB;AAExBM,MAAAA,KAAK,EAAEF;AAFiB,KAAX,CAAjB;AAIH;;AAED,SAAOD,QAAP;AACH,CAbsC,CAAvC","sourcesContent":["import { defineCloudfrontFunctionResponseHandler, setResponseCookie } from \"~/cloudfrontFunctions\";\n\nimport { variantRandomKey } from \"../utils/common\";\n\ndefineCloudfrontFunctionResponseHandler(event => {\n const request = event.request;\n const response = event.response;\n\n const variantRandom = request.headers[variantRandomKey]?.value;\n if (variantRandom) {\n setResponseCookie(response, {\n name: variantRandomKey,\n value: variantRandom\n });\n }\n\n return response;\n});\n"]}
@@ -0,0 +1,3 @@
1
+ export declare const variantFixedKey = "webiny-variant-fixed";
2
+ export declare const variantRandomKey = "webiny-variant-random";
3
+ export declare function pointsToFile(uri: string): boolean;
@@ -0,0 +1,5 @@
1
+ export const variantFixedKey = "webiny-variant-fixed";
2
+ export const variantRandomKey = "webiny-variant-random";
3
+ export function pointsToFile(uri) {
4
+ return /\/[^/]+\.[^/]+$/.test(uri);
5
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["common.ts"],"names":["variantFixedKey","variantRandomKey","pointsToFile","uri","test"],"mappings":"AAAA,OAAO,MAAMA,eAAe,GAAG,sBAAxB;AACP,OAAO,MAAMC,gBAAgB,GAAG,uBAAzB;AAEP,OAAO,SAASC,YAAT,CAAsBC,GAAtB,EAAmC;AACtC,SAAO,kBAAkBC,IAAlB,CAAuBD,GAAvB,CAAP;AACH","sourcesContent":["export const variantFixedKey = \"webiny-variant-fixed\";\nexport const variantRandomKey = \"webiny-variant-random\";\n\nexport function pointsToFile(uri: string) {\n return /\\/[^/]+\\.[^/]+$/.test(uri);\n}\n"]}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Some headers cannot be modified with lambda edge.
3
+ * Source: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions-restrictions.html
4
+ */
5
+ export declare function isHeaderBlacklisted(header: string): boolean;
@@ -0,0 +1,13 @@
1
+ const blacklistedHeaders = new Set([// blacklisted headers
2
+ "connection", "expect", "keep-Alive", "proxy-authenticate", "proxy-authorization", "proxy-connection", "trailer", "upgrade", "x-accel-buffering", "x-accel-charset", "x-accel-limit-rate", "x-accel-redirect", "x-cache", "x-forwarded-proto", "x-real-op", // readonly headers
3
+ "content-length", "transfer-encoding", "via"]);
4
+ const blacklistedHeadersRegex = [/^x-amz-cf-/, /^x-edge-/];
5
+ /**
6
+ * Some headers cannot be modified with lambda edge.
7
+ * Source: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions-restrictions.html
8
+ */
9
+
10
+ export function isHeaderBlacklisted(header) {
11
+ header = header.toLocaleLowerCase();
12
+ return blacklistedHeaders.has(header) || blacklistedHeadersRegex.some(r => r.test(header));
13
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["headerBlacklist.ts"],"names":["blacklistedHeaders","Set","blacklistedHeadersRegex","isHeaderBlacklisted","header","toLocaleLowerCase","has","some","r","test"],"mappings":"AAAA,MAAMA,kBAAkB,GAAG,IAAIC,GAAJ,CAAQ,CAC/B;AACA,YAF+B,EAG/B,QAH+B,EAI/B,YAJ+B,EAK/B,oBAL+B,EAM/B,qBAN+B,EAO/B,kBAP+B,EAQ/B,SAR+B,EAS/B,SAT+B,EAU/B,mBAV+B,EAW/B,iBAX+B,EAY/B,oBAZ+B,EAa/B,kBAb+B,EAc/B,SAd+B,EAe/B,mBAf+B,EAgB/B,WAhB+B,EAiB/B;AACA,gBAlB+B,EAmB/B,mBAnB+B,EAoB/B,KApB+B,CAAR,CAA3B;AAuBA,MAAMC,uBAAuB,GAAG,CAAC,YAAD,EAAe,UAAf,CAAhC;AAEA;AACA;AACA;AACA;;AACA,OAAO,SAASC,mBAAT,CAA6BC,MAA7B,EAA6C;AAChDA,EAAAA,MAAM,GAAGA,MAAM,CAACC,iBAAP,EAAT;AACA,SAAOL,kBAAkB,CAACM,GAAnB,CAAuBF,MAAvB,KAAkCF,uBAAuB,CAACK,IAAxB,CAA6BC,CAAC,IAAIA,CAAC,CAACC,IAAF,CAAOL,MAAP,CAAlC,CAAzC;AACH","sourcesContent":["const blacklistedHeaders = new Set([\n // blacklisted headers\n \"connection\",\n \"expect\",\n \"keep-Alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"proxy-connection\",\n \"trailer\",\n \"upgrade\",\n \"x-accel-buffering\",\n \"x-accel-charset\",\n \"x-accel-limit-rate\",\n \"x-accel-redirect\",\n \"x-cache\",\n \"x-forwarded-proto\",\n \"x-real-op\",\n // readonly headers\n \"content-length\",\n \"transfer-encoding\",\n \"via\"\n]);\n\nconst blacklistedHeadersRegex = [/^x-amz-cf-/, /^x-edge-/];\n\n/**\n * Some headers cannot be modified with lambda edge.\n * Source: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions-restrictions.html\n */\nexport function isHeaderBlacklisted(header: string) {\n header = header.toLocaleLowerCase();\n return blacklistedHeaders.has(header) || blacklistedHeadersRegex.some(r => r.test(header));\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import { CloudFrontResponse } from "../../lambdaEdge";
2
+ /**
3
+ * Load HTML from origin and perform transformation of URLs to absolute
4
+ * @param domain Domain of the origin cloudfront
5
+ * @param path Path of the file we want to retrieve
6
+ * @returns Response object
7
+ */
8
+ export declare function loadOriginPage(domain: string, path: string): Promise<CloudFrontResponse>;
@@ -0,0 +1,107 @@
1
+ import { get } from "https";
2
+ import { load } from "cheerio";
3
+ import { parse as parseSrcset, stringify as stringifySrcset } from "srcset";
4
+ import { isHeaderBlacklisted } from "./headerBlacklist";
5
+ import { logDebug } from "./log";
6
+ /**
7
+ * Load HTML from origin and perform transformation of URLs to absolute
8
+ * @param domain Domain of the origin cloudfront
9
+ * @param path Path of the file we want to retrieve
10
+ * @returns Response object
11
+ */
12
+
13
+ export function loadOriginPage(domain, path) {
14
+ return new Promise((resolve, reject) => {
15
+ logDebug(`Pulling page from ${domain}${path}`);
16
+ let responseBody = "";
17
+ const req = get({
18
+ hostname: domain,
19
+ port: 443,
20
+ path: path
21
+ }, res => {
22
+ res.on("data", chunk => responseBody += chunk);
23
+ res.on("end", () => {
24
+ logDebug(`Parsing page`);
25
+ const html = parseHtml(responseBody, domain);
26
+ const response = {
27
+ body: html,
28
+ status: "200",
29
+ statusDescription: "ok",
30
+ headers: {}
31
+ }; // Rewrite headers from the HTTP response
32
+
33
+ for (const header of Object.keys(res.headers)) {
34
+ // We cannot set any header we want.
35
+ // CloudFront is restricting us from setting or changing some headers.
36
+ if (isHeaderBlacklisted(header)) {
37
+ continue;
38
+ }
39
+
40
+ const value = res.headers[header];
41
+
42
+ if (Array.isArray(value)) {
43
+ response.headers[header] = value.map(h => ({
44
+ key: header,
45
+ value: h
46
+ }));
47
+ } else if (value) {
48
+ response.headers[header] = [{
49
+ key: header,
50
+ value: value
51
+ }];
52
+ }
53
+ }
54
+
55
+ logDebug(`Page parsed`);
56
+ resolve(response);
57
+ });
58
+ });
59
+ req.on("error", e => {
60
+ reject({
61
+ statusCode: 500,
62
+ body: e.message
63
+ });
64
+ });
65
+ });
66
+ }
67
+
68
+ function parseHtml(html, domain) {
69
+ const doc = load(html);
70
+ const host = `https://${domain}`;
71
+ doc("head > link").each((_i, el) => {
72
+ prefixUrl(el, "href", host);
73
+ });
74
+ doc("script").each((_i, el) => {
75
+ prefixUrl(el, "src", host);
76
+ });
77
+ doc("body").find("img").each((_i, el) => {
78
+ prefixUrl(el, "src", host); // Handle also srcset property for responsive images.
79
+
80
+ const srcset = el.attribs["srcset"];
81
+
82
+ if (srcset) {
83
+ const srcsetParsed = parseSrcset(srcset);
84
+ const srcsetRebased = srcsetParsed.map(src => {
85
+ if (src.url.startsWith("/")) {
86
+ return {
87
+ url: host + src.url,
88
+ density: src.density,
89
+ width: src.width
90
+ };
91
+ }
92
+
93
+ return src;
94
+ });
95
+ el.attribs["srcset"] = stringifySrcset(srcsetRebased);
96
+ }
97
+ });
98
+ return doc.html();
99
+ }
100
+
101
+ function prefixUrl(el, attr, host) {
102
+ const href = el.attribs[attr];
103
+
104
+ if (href && href.startsWith("/")) {
105
+ el.attribs[attr] = host + href;
106
+ }
107
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["loadOriginPage.ts"],"names":["get","load","parse","parseSrcset","stringify","stringifySrcset","isHeaderBlacklisted","logDebug","loadOriginPage","domain","path","Promise","resolve","reject","responseBody","req","hostname","port","res","on","chunk","html","parseHtml","response","body","status","statusDescription","headers","header","Object","keys","value","Array","isArray","map","h","key","e","statusCode","message","doc","host","each","_i","el","prefixUrl","find","srcset","attribs","srcsetParsed","srcsetRebased","src","url","startsWith","density","width","attr","href"],"mappings":"AACA,SAASA,GAAT,QAAoB,OAApB;AACA,SAASC,IAAT,QAA8B,SAA9B;AACA,SAASC,KAAK,IAAIC,WAAlB,EAA+BC,SAAS,IAAIC,eAA5C,QAAqF,QAArF;AACA,SAASC,mBAAT;AACA,SAASC,QAAT;AAEA;AACA;AACA;AACA;AACA;AACA;;AACA,OAAO,SAASC,cAAT,CAAwBC,MAAxB,EAAwCC,IAAxC,EAAsD;AACzD,SAAO,IAAIC,OAAJ,CAAgC,CAACC,OAAD,EAAUC,MAAV,KAAqB;AACxDN,IAAAA,QAAQ,CAAE,qBAAoBE,MAAO,GAAEC,IAAK,EAApC,CAAR;AACA,QAAII,YAAY,GAAG,EAAnB;AACA,UAAMC,GAAG,GAAGf,GAAG,CACX;AACIgB,MAAAA,QAAQ,EAAEP,MADd;AAEIQ,MAAAA,IAAI,EAAE,GAFV;AAGIP,MAAAA,IAAI,EAAEA;AAHV,KADW,EAMXQ,GAAG,IAAI;AACHA,MAAAA,GAAG,CAACC,EAAJ,CAAO,MAAP,EAAeC,KAAK,IAAKN,YAAY,IAAIM,KAAzC;AACAF,MAAAA,GAAG,CAACC,EAAJ,CAAO,KAAP,EAAc,MAAM;AAChBZ,QAAAA,QAAQ,CAAE,cAAF,CAAR;AACA,cAAMc,IAAI,GAAGC,SAAS,CAACR,YAAD,EAAeL,MAAf,CAAtB;AACA,cAAMc,QAA4B,GAAG;AACjCC,UAAAA,IAAI,EAAEH,IAD2B;AAEjCI,UAAAA,MAAM,EAAE,KAFyB;AAGjCC,UAAAA,iBAAiB,EAAE,IAHc;AAIjCC,UAAAA,OAAO,EAAE;AAJwB,SAArC,CAHgB,CAUhB;;AACA,aAAK,MAAMC,MAAX,IAAqBC,MAAM,CAACC,IAAP,CAAYZ,GAAG,CAACS,OAAhB,CAArB,EAA+C;AAC3C;AACA;AACA,cAAIrB,mBAAmB,CAACsB,MAAD,CAAvB,EAAiC;AAC7B;AACH;;AAED,gBAAMG,KAAK,GAAGb,GAAG,CAACS,OAAJ,CAAYC,MAAZ,CAAd;;AACA,cAAII,KAAK,CAACC,OAAN,CAAcF,KAAd,CAAJ,EAA0B;AACtBR,YAAAA,QAAQ,CAACI,OAAT,CAAiBC,MAAjB,IAA2BG,KAAK,CAACG,GAAN,CAAUC,CAAC,KAAK;AACvCC,cAAAA,GAAG,EAAER,MADkC;AAEvCG,cAAAA,KAAK,EAAEI;AAFgC,aAAL,CAAX,CAA3B;AAIH,WALD,MAKO,IAAIJ,KAAJ,EAAW;AACdR,YAAAA,QAAQ,CAACI,OAAT,CAAiBC,MAAjB,IAA2B,CACvB;AACIQ,cAAAA,GAAG,EAAER,MADT;AAEIG,cAAAA,KAAK,EAAEA;AAFX,aADuB,CAA3B;AAMH;AACJ;;AAEDxB,QAAAA,QAAQ,CAAE,aAAF,CAAR;AACAK,QAAAA,OAAO,CAACW,QAAD,CAAP;AACH,OApCD;AAqCH,KA7CU,CAAf;AAgDAR,IAAAA,GAAG,CAACI,EAAJ,CAAO,OAAP,EAAgBkB,CAAC,IAAI;AACjBxB,MAAAA,MAAM,CAAC;AACHyB,QAAAA,UAAU,EAAE,GADT;AAEHd,QAAAA,IAAI,EAAEa,CAAC,CAACE;AAFL,OAAD,CAAN;AAIH,KALD;AAMH,GAzDM,CAAP;AA0DH;;AAED,SAASjB,SAAT,CAAmBD,IAAnB,EAAiCZ,MAAjC,EAAiD;AAC7C,QAAM+B,GAAG,GAAGvC,IAAI,CAACoB,IAAD,CAAhB;AACA,QAAMoB,IAAI,GAAI,WAAUhC,MAAO,EAA/B;AAEA+B,EAAAA,GAAG,CAAC,aAAD,CAAH,CAAmBE,IAAnB,CAAwB,CAACC,EAAD,EAAKC,EAAL,KAAY;AAChCC,IAAAA,SAAS,CAACD,EAAD,EAAK,MAAL,EAAaH,IAAb,CAAT;AACH,GAFD;AAIAD,EAAAA,GAAG,CAAC,QAAD,CAAH,CAAcE,IAAd,CAAmB,CAACC,EAAD,EAAKC,EAAL,KAAY;AAC3BC,IAAAA,SAAS,CAACD,EAAD,EAAK,KAAL,EAAYH,IAAZ,CAAT;AACH,GAFD;AAIAD,EAAAA,GAAG,CAAC,MAAD,CAAH,CACKM,IADL,CACU,KADV,EAEKJ,IAFL,CAEU,CAACC,EAAD,EAAKC,EAAL,KAAY;AACdC,IAAAA,SAAS,CAACD,EAAD,EAAK,KAAL,EAAYH,IAAZ,CAAT,CADc,CAGd;;AACA,UAAMM,MAAM,GAAGH,EAAE,CAACI,OAAH,CAAW,QAAX,CAAf;;AACA,QAAID,MAAJ,EAAY;AACR,YAAME,YAAY,GAAG9C,WAAW,CAAC4C,MAAD,CAAhC;AACA,YAAMG,aAAa,GAAGD,YAAY,CAACf,GAAb,CAAmCiB,GAAG,IAAI;AAC5D,YAAIA,GAAG,CAACC,GAAJ,CAAQC,UAAR,CAAmB,GAAnB,CAAJ,EAA6B;AACzB,iBAAO;AACHD,YAAAA,GAAG,EAAEX,IAAI,GAAGU,GAAG,CAACC,GADb;AAEHE,YAAAA,OAAO,EAAEH,GAAG,CAACG,OAFV;AAGHC,YAAAA,KAAK,EAAEJ,GAAG,CAACI;AAHR,WAAP;AAKH;;AAED,eAAOJ,GAAP;AACH,OAVqB,CAAtB;AAYAP,MAAAA,EAAE,CAACI,OAAH,CAAW,QAAX,IAAuB3C,eAAe,CAAC6C,aAAD,CAAtC;AACH;AACJ,GAvBL;AAyBA,SAAOV,GAAG,CAACnB,IAAJ,EAAP;AACH;;AAED,SAASwB,SAAT,CAAmBD,EAAnB,EAAgCY,IAAhC,EAA8Cf,IAA9C,EAA4D;AACxD,QAAMgB,IAAI,GAAGb,EAAE,CAACI,OAAH,CAAWQ,IAAX,CAAb;;AACA,MAAIC,IAAI,IAAIA,IAAI,CAACJ,UAAL,CAAgB,GAAhB,CAAZ,EAAkC;AAC9BT,IAAAA,EAAE,CAACI,OAAH,CAAWQ,IAAX,IAAmBf,IAAI,GAAGgB,IAA1B;AACH;AACJ","sourcesContent":["import { CloudFrontResponse } from \"~/lambdaEdge\";\nimport { get } from \"https\";\nimport { load, Element } from \"cheerio\";\nimport { parse as parseSrcset, stringify as stringifySrcset, SrcSetDefinition } from \"srcset\";\nimport { isHeaderBlacklisted } from \"./headerBlacklist\";\nimport { logDebug } from \"./log\";\n\n/**\n * Load HTML from origin and perform transformation of URLs to absolute\n * @param domain Domain of the origin cloudfront\n * @param path Path of the file we want to retrieve\n * @returns Response object\n */\nexport function loadOriginPage(domain: string, path: string) {\n return new Promise<CloudFrontResponse>((resolve, reject) => {\n logDebug(`Pulling page from ${domain}${path}`);\n let responseBody = \"\";\n const req = get(\n {\n hostname: domain,\n port: 443,\n path: path\n },\n res => {\n res.on(\"data\", chunk => (responseBody += chunk));\n res.on(\"end\", () => {\n logDebug(`Parsing page`);\n const html = parseHtml(responseBody, domain);\n const response: CloudFrontResponse = {\n body: html,\n status: \"200\",\n statusDescription: \"ok\",\n headers: {}\n };\n\n // Rewrite headers from the HTTP response\n for (const header of Object.keys(res.headers)) {\n // We cannot set any header we want.\n // CloudFront is restricting us from setting or changing some headers.\n if (isHeaderBlacklisted(header)) {\n continue;\n }\n\n const value = res.headers[header];\n if (Array.isArray(value)) {\n response.headers[header] = value.map(h => ({\n key: header,\n value: h\n }));\n } else if (value) {\n response.headers[header] = [\n {\n key: header,\n value: value\n }\n ];\n }\n }\n\n logDebug(`Page parsed`);\n resolve(response);\n });\n }\n );\n\n req.on(\"error\", e => {\n reject({\n statusCode: 500,\n body: e.message\n });\n });\n });\n}\n\nfunction parseHtml(html: string, domain: string) {\n const doc = load(html);\n const host = `https://${domain}`;\n\n doc(\"head > link\").each((_i, el) => {\n prefixUrl(el, \"href\", host);\n });\n\n doc(\"script\").each((_i, el) => {\n prefixUrl(el, \"src\", host);\n });\n\n doc(\"body\")\n .find(\"img\")\n .each((_i, el) => {\n prefixUrl(el, \"src\", host);\n\n // Handle also srcset property for responsive images.\n const srcset = el.attribs[\"srcset\"];\n if (srcset) {\n const srcsetParsed = parseSrcset(srcset);\n const srcsetRebased = srcsetParsed.map<SrcSetDefinition>(src => {\n if (src.url.startsWith(\"/\")) {\n return {\n url: host + src.url,\n density: src.density,\n width: src.width\n };\n }\n\n return src;\n });\n\n el.attribs[\"srcset\"] = stringifySrcset(srcsetRebased);\n }\n });\n\n return doc.html();\n}\n\nfunction prefixUrl(el: Element, attr: string, host: string) {\n const href = el.attribs[attr];\n if (href && href.startsWith(\"/\")) {\n el.attribs[attr] = host + href;\n }\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import { CloudFrontRequestEvent } from "../../lambdaEdge";
2
+ export interface VariantConfig {
3
+ domain: string;
4
+ weight: number;
5
+ }
6
+ export declare type GatewayConfig = Record<string, VariantConfig>;
7
+ /**
8
+ * Loads traffic splitting config.
9
+ * It will, however not call WCP directly, but serve it from a locally cached file,
10
+ * as explained here https://www.notion.so/webiny/How-traffic-config-is-cached-2c8db57ca2b547a2b2fb1adf378cd191
11
+ */
12
+ export declare function loadTrafficSplittingConfig(event: CloudFrontRequestEvent): Promise<GatewayConfig>;
@@ -0,0 +1,65 @@
1
+ import { get } from "https";
2
+ import { logDebug } from "./log"; // Config file has a fixed URL within CDN, so it can be properly cached.
3
+ // This way we achieve better performance, because CDN does not have to call WCP API for config every time,
4
+ // but it can use it's own cache for a lookup.
5
+
6
+ const configPath = "/_config"; // Config is locally cached within live lambda for a short time (1 minute).
7
+ // Config must be cached per domain.
8
+ // Otherwise cache will spill over different apps, because we may share this lambda.
9
+
10
+ const configCache = new Map();
11
+
12
+ /**
13
+ * Loads traffic splitting config.
14
+ * It will, however not call WCP directly, but serve it from a locally cached file,
15
+ * as explained here https://www.notion.so/webiny/How-traffic-config-is-cached-2c8db57ca2b547a2b2fb1adf378cd191
16
+ */
17
+ export async function loadTrafficSplittingConfig(event) {
18
+ // Retrieve domain of the CloudFront distribution.
19
+ // We need it to make sure we cache config per application.
20
+ // For example API and website could share the same lambda instance.
21
+ // So we cache it separately for each domain (each CloudFront).
22
+ const domain = event.Records[0].cf.config.distributionDomainName;
23
+ let config = configCache.get(domain);
24
+
25
+ if (!config || isCacheExpired(config.timestamp)) {
26
+ logDebug("No config in cache");
27
+ config = {
28
+ config: await loadConfigCore(domain),
29
+ timestamp: Date.now()
30
+ };
31
+ configCache.set(domain, config);
32
+ }
33
+
34
+ return config.config;
35
+ }
36
+
37
+ function loadConfigCore(domain) {
38
+ return new Promise((resolve, reject) => {
39
+ let dataString = "";
40
+ const req = get({
41
+ hostname: domain,
42
+ port: 443,
43
+ path: configPath
44
+ }, function (res) {
45
+ res.on("data", chunk => {
46
+ dataString += chunk;
47
+ });
48
+ res.on("end", () => {
49
+ resolve(JSON.parse(dataString));
50
+ });
51
+ });
52
+ req.on("error", e => {
53
+ reject({
54
+ statusCode: 500,
55
+ body: e.message
56
+ });
57
+ });
58
+ });
59
+ }
60
+
61
+ function isCacheExpired(timestamp) {
62
+ const ttl = 60 * 1000; // 1 minute
63
+
64
+ return Date.now() - timestamp > ttl;
65
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["loadTrafficSplittingConfig.ts"],"names":["get","logDebug","configPath","configCache","Map","loadTrafficSplittingConfig","event","domain","Records","cf","config","distributionDomainName","isCacheExpired","timestamp","loadConfigCore","Date","now","set","Promise","resolve","reject","dataString","req","hostname","port","path","res","on","chunk","JSON","parse","e","statusCode","body","message","ttl"],"mappings":"AAAA,SAASA,GAAT,QAAoB,OAApB;AAEA,SAASC,QAAT,gB,CAEA;AACA;AACA;;AACA,MAAMC,UAAU,GAAG,UAAnB,C,CAEA;AACA;AACA;;AACA,MAAMC,WAAW,GAAG,IAAIC,GAAJ,EAApB;;AAcA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,0BAAf,CAA0CC,KAA1C,EAAyE;AAC5E;AACA;AACA;AACA;AACA,QAAMC,MAAM,GAAGD,KAAK,CAACE,OAAN,CAAc,CAAd,EAAiBC,EAAjB,CAAoBC,MAApB,CAA2BC,sBAA1C;AAEA,MAAID,MAAM,GAAGP,WAAW,CAACH,GAAZ,CAAgBO,MAAhB,CAAb;;AACA,MAAI,CAACG,MAAD,IAAWE,cAAc,CAACF,MAAM,CAACG,SAAR,CAA7B,EAAiD;AAC7CZ,IAAAA,QAAQ,CAAC,oBAAD,CAAR;AACAS,IAAAA,MAAM,GAAG;AACLA,MAAAA,MAAM,EAAE,MAAMI,cAAc,CAACP,MAAD,CADvB;AAELM,MAAAA,SAAS,EAAEE,IAAI,CAACC,GAAL;AAFN,KAAT;AAKAb,IAAAA,WAAW,CAACc,GAAZ,CAAgBV,MAAhB,EAAwBG,MAAxB;AACH;;AAED,SAAOA,MAAM,CAACA,MAAd;AACH;;AAED,SAASI,cAAT,CAAwBP,MAAxB,EAAwC;AACpC,SAAO,IAAIW,OAAJ,CAA2B,CAACC,OAAD,EAAUC,MAAV,KAAqB;AACnD,QAAIC,UAAU,GAAG,EAAjB;AAEA,UAAMC,GAAG,GAAGtB,GAAG,CACX;AACIuB,MAAAA,QAAQ,EAAEhB,MADd;AAEIiB,MAAAA,IAAI,EAAE,GAFV;AAGIC,MAAAA,IAAI,EAAEvB;AAHV,KADW,EAMX,UAAUwB,GAAV,EAAe;AACXA,MAAAA,GAAG,CAACC,EAAJ,CAAO,MAAP,EAAeC,KAAK,IAAI;AACpBP,QAAAA,UAAU,IAAIO,KAAd;AACH,OAFD;AAGAF,MAAAA,GAAG,CAACC,EAAJ,CAAO,KAAP,EAAc,MAAM;AAChBR,QAAAA,OAAO,CAACU,IAAI,CAACC,KAAL,CAAWT,UAAX,CAAD,CAAP;AACH,OAFD;AAGH,KAbU,CAAf;AAgBAC,IAAAA,GAAG,CAACK,EAAJ,CAAO,OAAP,EAAgBI,CAAC,IAAI;AACjBX,MAAAA,MAAM,CAAC;AACHY,QAAAA,UAAU,EAAE,GADT;AAEHC,QAAAA,IAAI,EAAEF,CAAC,CAACG;AAFL,OAAD,CAAN;AAIH,KALD;AAMH,GAzBM,CAAP;AA0BH;;AAED,SAAStB,cAAT,CAAwBC,SAAxB,EAA2C;AACvC,QAAMsB,GAAG,GAAG,KAAK,IAAjB,CADuC,CAChB;;AACvB,SAAOpB,IAAI,CAACC,GAAL,KAAaH,SAAb,GAAyBsB,GAAhC;AACH","sourcesContent":["import { get } from \"https\";\nimport { CloudFrontRequestEvent } from \"~/lambdaEdge\";\nimport { logDebug } from \"./log\";\n\n// Config file has a fixed URL within CDN, so it can be properly cached.\n// This way we achieve better performance, because CDN does not have to call WCP API for config every time,\n// but it can use it's own cache for a lookup.\nconst configPath = \"/_config\";\n\n// Config is locally cached within live lambda for a short time (1 minute).\n// Config must be cached per domain.\n// Otherwise cache will spill over different apps, because we may share this lambda.\nconst configCache = new Map<string, GatewayConfigCache>();\n\ninterface GatewayConfigCache {\n config: GatewayConfig;\n timestamp: number;\n}\n\nexport interface VariantConfig {\n domain: string;\n weight: number;\n}\n\nexport type GatewayConfig = Record<string, VariantConfig>;\n\n/**\n * Loads traffic splitting config.\n * It will, however not call WCP directly, but serve it from a locally cached file,\n * as explained here https://www.notion.so/webiny/How-traffic-config-is-cached-2c8db57ca2b547a2b2fb1adf378cd191\n */\nexport async function loadTrafficSplittingConfig(event: CloudFrontRequestEvent) {\n // Retrieve domain of the CloudFront distribution.\n // We need it to make sure we cache config per application.\n // For example API and website could share the same lambda instance.\n // So we cache it separately for each domain (each CloudFront).\n const domain = event.Records[0].cf.config.distributionDomainName;\n\n let config = configCache.get(domain);\n if (!config || isCacheExpired(config.timestamp)) {\n logDebug(\"No config in cache\");\n config = {\n config: await loadConfigCore(domain),\n timestamp: Date.now()\n };\n\n configCache.set(domain, config);\n }\n\n return config.config;\n}\n\nfunction loadConfigCore(domain: string) {\n return new Promise<GatewayConfig>((resolve, reject) => {\n let dataString = \"\";\n\n const req = get(\n {\n hostname: domain,\n port: 443,\n path: configPath\n },\n function (res) {\n res.on(\"data\", chunk => {\n dataString += chunk;\n });\n res.on(\"end\", () => {\n resolve(JSON.parse(dataString));\n });\n }\n );\n\n req.on(\"error\", e => {\n reject({\n statusCode: 500,\n body: e.message\n });\n });\n });\n}\n\nfunction isCacheExpired(timestamp: number) {\n const ttl = 60 * 1000; // 1 minute\n return Date.now() - timestamp > ttl;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import { CloudFrontRequestEvent } from "../../lambdaEdge";
2
+ export declare function loadVariantOrigin(event: CloudFrontRequestEvent): Promise<import("aws-lambda").CloudFrontRequest | import("aws-lambda").CloudFrontResultResponse | {
3
+ variant: import("./loadTrafficSplittingConfig").VariantConfig;
4
+ }>;
@@ -0,0 +1,83 @@
1
+ import { getHeader, notFoundResponse } from "../../lambdaEdge";
2
+ import { variantFixedKey, variantRandomKey } from "../utils/common";
3
+ import { loadTrafficSplittingConfig } from "../utils/loadTrafficSplittingConfig";
4
+ import { logDebug } from "./log";
5
+ export async function loadVariantOrigin(event) {
6
+ const cf = event.Records[0].cf;
7
+ const request = cf.request;
8
+ const config = await loadTrafficSplittingConfig(event);
9
+ const variantFixed = getHeader(request.headers, variantFixedKey);
10
+
11
+ if (variantFixed) {
12
+ const variantConfig = config[variantFixed];
13
+
14
+ if (variantConfig) {
15
+ return {
16
+ variant: variantConfig
17
+ };
18
+ } else {
19
+ return notFoundResponse(`No variant ${variantFixed} found`);
20
+ }
21
+ }
22
+
23
+ const variantRandom = Number(getHeader(request.headers, variantRandomKey));
24
+
25
+ if (isNaN(variantRandom)) {
26
+ logDebug("No random variant passed, passing the request"); // Random variant header should be always present.
27
+ // It it's not, something bad happened, so we just pass request further.
28
+
29
+ return request;
30
+ }
31
+
32
+ logDebug(`Variant random ${variantRandom}`);
33
+ const variantConfig = getRandomVariant(config, variantRandom);
34
+
35
+ if (!variantConfig) {
36
+ // If no variant is matching the random value, just return 404.
37
+ // This should happen only if there is really not a single variant serving traffic.
38
+ logDebug(`No variant is found`);
39
+ return notFoundResponse(`No variant is found`);
40
+ }
41
+
42
+ return {
43
+ variant: variantConfig
44
+ };
45
+ }
46
+
47
+ function getRandomVariant(config, random) {
48
+ let totalWeight = 0;
49
+ const variants = Object.keys(config);
50
+
51
+ for (const variant of variants) {
52
+ const variantConfig = config[variant];
53
+
54
+ if (variantConfig.weight) {
55
+ // do not count bad or negative weights
56
+ totalWeight += variantConfig.weight;
57
+ }
58
+ }
59
+
60
+ if (totalWeight <= 0 || random < 0) {
61
+ return;
62
+ } // Normalize random value to total weight of traffic splitting rates.
63
+
64
+
65
+ random = random * totalWeight / 100;
66
+
67
+ for (const variant of variants) {
68
+ const variantConfig = config[variant];
69
+
70
+ if (!variantConfig.weight) {
71
+ continue;
72
+ }
73
+
74
+ if (random <= variantConfig.weight) {
75
+ logDebug(`Variant ${variant} selected`);
76
+ return config[variant];
77
+ } else {
78
+ random -= variantConfig.weight;
79
+ }
80
+ }
81
+
82
+ return;
83
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["loadVariantOrigin.ts"],"names":["getHeader","notFoundResponse","variantFixedKey","variantRandomKey","loadTrafficSplittingConfig","logDebug","loadVariantOrigin","event","cf","Records","request","config","variantFixed","headers","variantConfig","variant","variantRandom","Number","isNaN","getRandomVariant","random","totalWeight","variants","Object","keys","weight"],"mappings":"AAAA,SAAiCA,SAAjC,EAA4CC,gBAA5C;AAEA,SAASC,eAAT,EAA0BC,gBAA1B;AACA,SAAwBC,0BAAxB;AACA,SAASC,QAAT;AAEA,OAAO,eAAeC,iBAAf,CAAiCC,KAAjC,EAAgE;AACnE,QAAMC,EAAE,GAAGD,KAAK,CAACE,OAAN,CAAc,CAAd,EAAiBD,EAA5B;AACA,QAAME,OAAO,GAAGF,EAAE,CAACE,OAAnB;AAEA,QAAMC,MAAM,GAAG,MAAMP,0BAA0B,CAACG,KAAD,CAA/C;AAEA,QAAMK,YAAY,GAAGZ,SAAS,CAACU,OAAO,CAACG,OAAT,EAAkBX,eAAlB,CAA9B;;AACA,MAAIU,YAAJ,EAAkB;AACd,UAAME,aAAa,GAAGH,MAAM,CAACC,YAAD,CAA5B;;AACA,QAAIE,aAAJ,EAAmB;AACf,aAAO;AAAEC,QAAAA,OAAO,EAAED;AAAX,OAAP;AACH,KAFD,MAEO;AACH,aAAOb,gBAAgB,CAAE,cAAaW,YAAa,QAA5B,CAAvB;AACH;AACJ;;AAED,QAAMI,aAAa,GAAGC,MAAM,CAACjB,SAAS,CAACU,OAAO,CAACG,OAAT,EAAkBV,gBAAlB,CAAV,CAA5B;;AACA,MAAIe,KAAK,CAACF,aAAD,CAAT,EAA0B;AACtBX,IAAAA,QAAQ,CAAC,+CAAD,CAAR,CADsB,CAEtB;AACA;;AACA,WAAOK,OAAP;AACH;;AAEDL,EAAAA,QAAQ,CAAE,kBAAiBW,aAAc,EAAjC,CAAR;AAEA,QAAMF,aAAa,GAAGK,gBAAgB,CAACR,MAAD,EAASK,aAAT,CAAtC;;AACA,MAAI,CAACF,aAAL,EAAoB;AAChB;AACA;AACAT,IAAAA,QAAQ,CAAE,qBAAF,CAAR;AACA,WAAOJ,gBAAgB,CAAE,qBAAF,CAAvB;AACH;;AAED,SAAO;AAAEc,IAAAA,OAAO,EAAED;AAAX,GAAP;AACH;;AAED,SAASK,gBAAT,CAA0BR,MAA1B,EAAiDS,MAAjD,EAAiE;AAC7D,MAAIC,WAAW,GAAG,CAAlB;AAEA,QAAMC,QAAQ,GAAGC,MAAM,CAACC,IAAP,CAAYb,MAAZ,CAAjB;;AACA,OAAK,MAAMI,OAAX,IAAsBO,QAAtB,EAAgC;AAC5B,UAAMR,aAAa,GAAGH,MAAM,CAACI,OAAD,CAA5B;;AACA,QAAID,aAAa,CAACW,MAAlB,EAA0B;AACtB;AACAJ,MAAAA,WAAW,IAAIP,aAAa,CAACW,MAA7B;AACH;AACJ;;AAED,MAAIJ,WAAW,IAAI,CAAf,IAAoBD,MAAM,GAAG,CAAjC,EAAoC;AAChC;AACH,GAd4D,CAgB7D;;;AACAA,EAAAA,MAAM,GAAIA,MAAM,GAAGC,WAAV,GAAyB,GAAlC;;AAEA,OAAK,MAAMN,OAAX,IAAsBO,QAAtB,EAAgC;AAC5B,UAAMR,aAAa,GAAGH,MAAM,CAACI,OAAD,CAA5B;;AACA,QAAI,CAACD,aAAa,CAACW,MAAnB,EAA2B;AACvB;AACH;;AAED,QAAIL,MAAM,IAAIN,aAAa,CAACW,MAA5B,EAAoC;AAChCpB,MAAAA,QAAQ,CAAE,WAAUU,OAAQ,WAApB,CAAR;AACA,aAAOJ,MAAM,CAACI,OAAD,CAAb;AACH,KAHD,MAGO;AACHK,MAAAA,MAAM,IAAIN,aAAa,CAACW,MAAxB;AACH;AACJ;;AAED;AACH","sourcesContent":["import { CloudFrontRequestEvent, getHeader, notFoundResponse } from \"~/lambdaEdge\";\n\nimport { variantFixedKey, variantRandomKey } from \"../utils/common\";\nimport { GatewayConfig, loadTrafficSplittingConfig } from \"../utils/loadTrafficSplittingConfig\";\nimport { logDebug } from \"./log\";\n\nexport async function loadVariantOrigin(event: CloudFrontRequestEvent) {\n const cf = event.Records[0].cf;\n const request = cf.request;\n\n const config = await loadTrafficSplittingConfig(event);\n\n const variantFixed = getHeader(request.headers, variantFixedKey);\n if (variantFixed) {\n const variantConfig = config[variantFixed];\n if (variantConfig) {\n return { variant: variantConfig };\n } else {\n return notFoundResponse(`No variant ${variantFixed} found`);\n }\n }\n\n const variantRandom = Number(getHeader(request.headers, variantRandomKey));\n if (isNaN(variantRandom)) {\n logDebug(\"No random variant passed, passing the request\");\n // Random variant header should be always present.\n // It it's not, something bad happened, so we just pass request further.\n return request;\n }\n\n logDebug(`Variant random ${variantRandom}`);\n\n const variantConfig = getRandomVariant(config, variantRandom);\n if (!variantConfig) {\n // If no variant is matching the random value, just return 404.\n // This should happen only if there is really not a single variant serving traffic.\n logDebug(`No variant is found`);\n return notFoundResponse(`No variant is found`);\n }\n\n return { variant: variantConfig };\n}\n\nfunction getRandomVariant(config: GatewayConfig, random: number) {\n let totalWeight = 0;\n\n const variants = Object.keys(config);\n for (const variant of variants) {\n const variantConfig = config[variant];\n if (variantConfig.weight) {\n // do not count bad or negative weights\n totalWeight += variantConfig.weight;\n }\n }\n\n if (totalWeight <= 0 || random < 0) {\n return;\n }\n\n // Normalize random value to total weight of traffic splitting rates.\n random = (random * totalWeight) / 100;\n\n for (const variant of variants) {\n const variantConfig = config[variant];\n if (!variantConfig.weight) {\n continue;\n }\n\n if (random <= variantConfig.weight) {\n logDebug(`Variant ${variant} selected`);\n return config[variant];\n } else {\n random -= variantConfig.weight;\n }\n }\n\n return;\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function logDebug(...args: any[]): void;
@@ -0,0 +1,7 @@
1
+ export function logDebug(...args) {
2
+ // If DEBUG variable is not set during build,
3
+ // this function should be minified to an empty one and removed completely from the bundle.
4
+ if (process.env.DEBUG === "true") {
5
+ console.log(...args);
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["log.ts"],"names":["logDebug","args","process","env","DEBUG","console","log"],"mappings":"AAAA,OAAO,SAASA,QAAT,CAAkB,GAAGC,IAArB,EAAkC;AACrC;AACA;AACA,MAAIC,OAAO,CAACC,GAAR,CAAYC,KAAZ,KAAsB,MAA1B,EAAkC;AAC9BC,IAAAA,OAAO,CAACC,GAAR,CAAY,GAAGL,IAAf;AACH;AACJ","sourcesContent":["export function logDebug(...args: any[]) {\n // If DEBUG variable is not set during build,\n // this function should be minified to an empty one and removed completely from the bundle.\n if (process.env.DEBUG === \"true\") {\n console.log(...args);\n }\n}\n"]}