@bigbinary/neeto-playwright-commons 1.26.35 → 1.27.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.
package/index.cjs.js CHANGED
@@ -152,15 +152,26 @@ class ApiKeysApi {
152
152
  }
153
153
  }
154
154
 
155
+ const CUSTOM_DOMAIN_BASE_URL = `${BASE_URL}/custom_domains`;
155
156
  class CustomDomainApi {
156
157
  constructor(neetoPlaywrightUtilities) {
157
158
  this.neetoPlaywrightUtilities = neetoPlaywrightUtilities;
159
+ this.create = (hostname, baseUrl = "") => this.neetoPlaywrightUtilities.apiRequest({
160
+ url: `${baseUrl}${CUSTOM_DOMAIN_BASE_URL}`,
161
+ method: "post",
162
+ body: { hostname },
163
+ });
158
164
  this.delete = (customDomain) => this.neetoPlaywrightUtilities.apiRequest({
159
- url: `${BASE_URL}/custom_domains/${customDomain}`,
165
+ url: `${CUSTOM_DOMAIN_BASE_URL}/${customDomain}`,
160
166
  method: "delete",
161
167
  });
162
- this.fetch = () => this.neetoPlaywrightUtilities.apiRequest({
163
- url: `${BASE_URL}/custom_domains`,
168
+ this.fetch = (baseUrl = "") => this.neetoPlaywrightUtilities.apiRequest({
169
+ url: `${baseUrl}${CUSTOM_DOMAIN_BASE_URL}`,
170
+ });
171
+ this.validate = (baseUrl = "") => this.neetoPlaywrightUtilities.apiRequest({
172
+ url: `${baseUrl}${CUSTOM_DOMAIN_BASE_URL}/validate_domain`,
173
+ failOnStatusCode: false,
174
+ method: "patch",
164
175
  });
165
176
  }
166
177
  }
@@ -4982,6 +4993,7 @@ const COMMON_SELECTORS = {
4982
4993
  columnsDropdownIcon: "columns-dropdown-icon",
4983
4994
  columnDragHandle: "column-drag-handle",
4984
4995
  reactSelectContainer: ".neeto-ui-react-select__container",
4996
+ calloutElement: "callout-element",
4985
4997
  };
4986
4998
 
4987
4999
  const THANK_YOU_SELECTORS = {
@@ -29084,9 +29096,10 @@ function requireAddressparser () {
29084
29096
  * Converts tokens for a single address into an address object
29085
29097
  *
29086
29098
  * @param {Array} tokens Tokens object
29099
+ * @param {Number} depth Current recursion depth for nested group protection
29087
29100
  * @return {Object} Address object
29088
29101
  */
29089
- function _handleAddress(tokens) {
29102
+ function _handleAddress(tokens, depth) {
29090
29103
  let isGroup = false;
29091
29104
  let state = 'text';
29092
29105
  let address;
@@ -29167,7 +29180,7 @@ function requireAddressparser () {
29167
29180
  // Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
29168
29181
  let groupMembers = [];
29169
29182
  if (data.group.length) {
29170
- let parsedGroup = addressparser(data.group.join(','));
29183
+ let parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
29171
29184
  // Flatten: if any member is itself a group, extract its members into the sequence
29172
29185
  parsedGroup.forEach(member => {
29173
29186
  if (member.group) {
@@ -29377,6 +29390,13 @@ function requireAddressparser () {
29377
29390
  }
29378
29391
  }
29379
29392
 
29393
+ /**
29394
+ * Maximum recursion depth for parsing nested groups.
29395
+ * RFC 5322 doesn't allow nested groups, so this is a safeguard against
29396
+ * malicious input that could cause stack overflow.
29397
+ */
29398
+ const MAX_NESTED_GROUP_DEPTH = 50;
29399
+
29380
29400
  /**
29381
29401
  * Parses structured e-mail addresses from an address field
29382
29402
  *
@@ -29389,10 +29409,18 @@ function requireAddressparser () {
29389
29409
  * [{name: 'Name', address: 'address@domain'}]
29390
29410
  *
29391
29411
  * @param {String} str Address field
29412
+ * @param {Object} options Optional options object
29413
+ * @param {Number} options._depth Internal recursion depth counter (do not set manually)
29392
29414
  * @return {Array} An array of address objects
29393
29415
  */
29394
29416
  function addressparser(str, options) {
29395
29417
  options = options || {};
29418
+ let depth = options._depth || 0;
29419
+
29420
+ // Prevent stack overflow from deeply nested groups (DoS protection)
29421
+ if (depth > MAX_NESTED_GROUP_DEPTH) {
29422
+ return [];
29423
+ }
29396
29424
 
29397
29425
  let tokenizer = new Tokenizer(str);
29398
29426
  let tokens = tokenizer.tokenize();
@@ -29417,7 +29445,7 @@ function requireAddressparser () {
29417
29445
  }
29418
29446
 
29419
29447
  addresses.forEach(address => {
29420
- address = _handleAddress(address);
29448
+ address = _handleAddress(address, depth);
29421
29449
  if (address.length) {
29422
29450
  parsedAddresses = parsedAddresses.concat(address);
29423
29451
  }
@@ -40929,6 +40957,8 @@ function requireStreams () {
40929
40957
  return streams;
40930
40958
  }
40931
40959
 
40960
+ lib$a.exports;
40961
+
40932
40962
  var hasRequiredLib$a;
40933
40963
 
40934
40964
  function requireLib$a () {
@@ -40940,22 +40970,21 @@ function requireLib$a () {
40940
40970
 
40941
40971
  var bomHandling = requireBomHandling();
40942
40972
  var mergeModules = requireMergeExports();
40943
- var iconv = module.exports;
40944
40973
 
40945
40974
  // All codecs and aliases are kept here, keyed by encoding name/alias.
40946
40975
  // They are lazy loaded in `iconv.getCodec` from `encodings/index.js`.
40947
40976
  // Cannot initialize with { __proto__: null } because Boolean({ __proto__: null }) === true
40948
- iconv.encodings = null;
40977
+ module.exports.encodings = null;
40949
40978
 
40950
40979
  // Characters emitted in case of error.
40951
- iconv.defaultCharUnicode = "�";
40952
- iconv.defaultCharSingleByte = "?";
40980
+ module.exports.defaultCharUnicode = "�";
40981
+ module.exports.defaultCharSingleByte = "?";
40953
40982
 
40954
40983
  // Public API.
40955
- iconv.encode = function encode (str, encoding, options) {
40984
+ module.exports.encode = function encode (str, encoding, options) {
40956
40985
  str = "" + (str || ""); // Ensure string.
40957
40986
 
40958
- var encoder = iconv.getEncoder(encoding, options);
40987
+ var encoder = module.exports.getEncoder(encoding, options);
40959
40988
 
40960
40989
  var res = encoder.write(str);
40961
40990
  var trail = encoder.end();
@@ -40963,17 +40992,17 @@ function requireLib$a () {
40963
40992
  return (trail && trail.length > 0) ? Buffer.concat([res, trail]) : res
40964
40993
  };
40965
40994
 
40966
- iconv.decode = function decode (buf, encoding, options) {
40995
+ module.exports.decode = function decode (buf, encoding, options) {
40967
40996
  if (typeof buf === "string") {
40968
- if (!iconv.skipDecodeWarning) {
40997
+ if (!module.exports.skipDecodeWarning) {
40969
40998
  console.error("Iconv-lite warning: decode()-ing strings is deprecated. Refer to https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding");
40970
- iconv.skipDecodeWarning = true;
40999
+ module.exports.skipDecodeWarning = true;
40971
41000
  }
40972
41001
 
40973
41002
  buf = Buffer.from("" + (buf || ""), "binary"); // Ensure buffer.
40974
41003
  }
40975
41004
 
40976
- var decoder = iconv.getDecoder(encoding, options);
41005
+ var decoder = module.exports.getDecoder(encoding, options);
40977
41006
 
40978
41007
  var res = decoder.write(buf);
40979
41008
  var trail = decoder.end();
@@ -40981,9 +41010,9 @@ function requireLib$a () {
40981
41010
  return trail ? (res + trail) : res
40982
41011
  };
40983
41012
 
40984
- iconv.encodingExists = function encodingExists (enc) {
41013
+ module.exports.encodingExists = function encodingExists (enc) {
40985
41014
  try {
40986
- iconv.getCodec(enc);
41015
+ module.exports.getCodec(enc);
40987
41016
  return true
40988
41017
  } catch (e) {
40989
41018
  return false
@@ -40991,31 +41020,31 @@ function requireLib$a () {
40991
41020
  };
40992
41021
 
40993
41022
  // Legacy aliases to convert functions
40994
- iconv.toEncoding = iconv.encode;
40995
- iconv.fromEncoding = iconv.decode;
41023
+ module.exports.toEncoding = module.exports.encode;
41024
+ module.exports.fromEncoding = module.exports.decode;
40996
41025
 
40997
41026
  // Search for a codec in iconv.encodings. Cache codec data in iconv._codecDataCache.
40998
- iconv._codecDataCache = { __proto__: null };
41027
+ module.exports._codecDataCache = { __proto__: null };
40999
41028
 
41000
- iconv.getCodec = function getCodec (encoding) {
41001
- if (!iconv.encodings) {
41029
+ module.exports.getCodec = function getCodec (encoding) {
41030
+ if (!module.exports.encodings) {
41002
41031
  var raw = requireEncodings();
41003
41032
  // TODO: In future versions when old nodejs support is removed can use object.assign
41004
- iconv.encodings = { __proto__: null }; // Initialize as empty object.
41005
- mergeModules(iconv.encodings, raw);
41033
+ module.exports.encodings = { __proto__: null }; // Initialize as empty object.
41034
+ mergeModules(module.exports.encodings, raw);
41006
41035
  }
41007
41036
 
41008
41037
  // Canonicalize encoding name: strip all non-alphanumeric chars and appended year.
41009
- var enc = iconv._canonicalizeEncoding(encoding);
41038
+ var enc = module.exports._canonicalizeEncoding(encoding);
41010
41039
 
41011
41040
  // Traverse iconv.encodings to find actual codec.
41012
41041
  var codecOptions = {};
41013
41042
  while (true) {
41014
- var codec = iconv._codecDataCache[enc];
41043
+ var codec = module.exports._codecDataCache[enc];
41015
41044
 
41016
41045
  if (codec) { return codec }
41017
41046
 
41018
- var codecDef = iconv.encodings[enc];
41047
+ var codecDef = module.exports.encodings[enc];
41019
41048
 
41020
41049
  switch (typeof codecDef) {
41021
41050
  case "string": // Direct alias to other encoding.
@@ -41036,9 +41065,9 @@ function requireLib$a () {
41036
41065
  // The codec function must load all tables and return object with .encoder and .decoder methods.
41037
41066
  // It'll be called only once (for each different options object).
41038
41067
  //
41039
- codec = new codecDef(codecOptions, iconv);
41068
+ codec = new codecDef(codecOptions, module.exports);
41040
41069
 
41041
- iconv._codecDataCache[codecOptions.encodingName] = codec; // Save it to be reused later.
41070
+ module.exports._codecDataCache[codecOptions.encodingName] = codec; // Save it to be reused later.
41042
41071
  return codec
41043
41072
 
41044
41073
  default:
@@ -41047,13 +41076,13 @@ function requireLib$a () {
41047
41076
  }
41048
41077
  };
41049
41078
 
41050
- iconv._canonicalizeEncoding = function (encoding) {
41079
+ module.exports._canonicalizeEncoding = function (encoding) {
41051
41080
  // Canonicalize encoding name: strip all non-alphanumeric chars and appended year.
41052
41081
  return ("" + encoding).toLowerCase().replace(/:\d{4}$|[^0-9a-z]/g, "")
41053
41082
  };
41054
41083
 
41055
- iconv.getEncoder = function getEncoder (encoding, options) {
41056
- var codec = iconv.getCodec(encoding);
41084
+ module.exports.getEncoder = function getEncoder (encoding, options) {
41085
+ var codec = module.exports.getCodec(encoding);
41057
41086
  var encoder = new codec.encoder(options, codec);
41058
41087
 
41059
41088
  if (codec.bomAware && options && options.addBOM) { encoder = new bomHandling.PrependBOM(encoder, options); }
@@ -41061,8 +41090,8 @@ function requireLib$a () {
41061
41090
  return encoder
41062
41091
  };
41063
41092
 
41064
- iconv.getDecoder = function getDecoder (encoding, options) {
41065
- var codec = iconv.getCodec(encoding);
41093
+ module.exports.getDecoder = function getDecoder (encoding, options) {
41094
+ var codec = module.exports.getCodec(encoding);
41066
41095
  var decoder = new codec.decoder(options, codec);
41067
41096
 
41068
41097
  if (codec.bomAware && !(options && options.stripBOM === false)) { decoder = new bomHandling.StripBOM(decoder, options); }
@@ -41075,26 +41104,26 @@ function requireLib$a () {
41075
41104
  // up to 100Kb to the output bundle. To avoid unnecessary code bloat, we don't enable Streaming API in browser by default.
41076
41105
  // If you would like to enable it explicitly, please add the following code to your app:
41077
41106
  // > iconv.enableStreamingAPI(require('stream'));
41078
- iconv.enableStreamingAPI = function enableStreamingAPI (streamModule) {
41079
- if (iconv.supportsStreams) { return }
41107
+ module.exports.enableStreamingAPI = function enableStreamingAPI (streamModule) {
41108
+ if (module.exports.supportsStreams) { return }
41080
41109
 
41081
41110
  // Dependency-inject stream module to create IconvLite stream classes.
41082
41111
  var streams = requireStreams()(streamModule);
41083
41112
 
41084
41113
  // Not public API yet, but expose the stream classes.
41085
- iconv.IconvLiteEncoderStream = streams.IconvLiteEncoderStream;
41086
- iconv.IconvLiteDecoderStream = streams.IconvLiteDecoderStream;
41114
+ module.exports.IconvLiteEncoderStream = streams.IconvLiteEncoderStream;
41115
+ module.exports.IconvLiteDecoderStream = streams.IconvLiteDecoderStream;
41087
41116
 
41088
41117
  // Streaming API.
41089
- iconv.encodeStream = function encodeStream (encoding, options) {
41090
- return new iconv.IconvLiteEncoderStream(iconv.getEncoder(encoding, options), options)
41118
+ module.exports.encodeStream = function encodeStream (encoding, options) {
41119
+ return new module.exports.IconvLiteEncoderStream(module.exports.getEncoder(encoding, options), options)
41091
41120
  };
41092
41121
 
41093
- iconv.decodeStream = function decodeStream (encoding, options) {
41094
- return new iconv.IconvLiteDecoderStream(iconv.getDecoder(encoding, options), options)
41122
+ module.exports.decodeStream = function decodeStream (encoding, options) {
41123
+ return new module.exports.IconvLiteDecoderStream(module.exports.getDecoder(encoding, options), options)
41095
41124
  };
41096
41125
 
41097
- iconv.supportsStreams = true;
41126
+ module.exports.supportsStreams = true;
41098
41127
  };
41099
41128
 
41100
41129
  // Enable Streaming API automatically if 'stream' module is available and non-empty (the majority of environments).
@@ -41104,10 +41133,10 @@ function requireLib$a () {
41104
41133
  } catch (e) {}
41105
41134
 
41106
41135
  if (streamModule && streamModule.Transform) {
41107
- iconv.enableStreamingAPI(streamModule);
41136
+ module.exports.enableStreamingAPI(streamModule);
41108
41137
  } else {
41109
41138
  // In rare cases where 'stream' module is not available by default, throw a helpful exception.
41110
- iconv.encodeStream = iconv.decodeStream = function () {
41139
+ module.exports.encodeStream = module.exports.decodeStream = function () {
41111
41140
  throw new Error("iconv-lite Streaming API is not enabled. Use iconv.enableStreamingAPI(require('stream')); to enable it.")
41112
41141
  };
41113
41142
  }
@@ -51184,7 +51213,6 @@ var require$$11 = [
51184
51213
  "drive",
51185
51214
  "dtv",
51186
51215
  "dubai",
51187
- "dunlop",
51188
51216
  "dupont",
51189
51217
  "durban",
51190
51218
  "dvag",
@@ -53416,7 +53444,11 @@ function requireMailParser () {
53416
53444
  result.push(textPart);
53417
53445
  }
53418
53446
 
53419
- result.push(`<a href="${link.url}">${link.text}</a>`);
53447
+ // Escape quotes in URL to prevent XSS
53448
+ let safeUrl = link.url.replace(/"/g, '&quot;');
53449
+ // Escape HTML entities in link text
53450
+ let safeText = he.encode(link.text, { useNamedReferences: true });
53451
+ result.push(`<a href="${safeUrl}">${safeText}</a>`);
53420
53452
 
53421
53453
  last = link.lastIndex;
53422
53454
  });
@@ -119372,12 +119404,13 @@ class CustomDomainPage {
119372
119404
  .getByRole("cell", { name: domain.split(".")[0] })).toBeVisible(),
119373
119405
  ]);
119374
119406
  await this.neetoPlaywrightUtilities.waitForPageLoad();
119407
+ await validateButton.click();
119408
+ await test.expect(this.page.getByTestId(COMMON_SELECTORS.calloutElement)).toBeVisible({ timeout: 15000 });
119375
119409
  let isCertificateLimitExceeded = false;
119376
119410
  await test.expect(async () => {
119377
119411
  isCertificateLimitExceeded = await this.isCertificateLimitExceeded();
119378
119412
  if (isCertificateLimitExceeded)
119379
119413
  return;
119380
- await validateButton.click();
119381
119414
  await this.neetoPlaywrightUtilities.verifyToast({
119382
119415
  message: this.t("neetoCustomDomains.validation.successMessage"),
119383
119416
  });
@@ -119387,13 +119420,21 @@ class CustomDomainPage {
119387
119420
  return;
119388
119421
  }
119389
119422
  await this.neetoPlaywrightUtilities.waitForPageLoad();
119390
- await test.expect(this.page.getByTestId(CUSTOM_DOMAIN_SELECTORS.activeTagContainer)).toBeVisible();
119423
+ await test.expect(validateButton).toBeHidden();
119424
+ };
119425
+ this.getDomainStatus = async (hostname, baseURL) => {
119426
+ var _a;
119427
+ const response = await this.customDomainApi
119428
+ .fetch(baseURL)
119429
+ .then(response => response === null || response === void 0 ? void 0 : response.json());
119430
+ const domain = (_a = response === null || response === void 0 ? void 0 : response.custom_domains) === null || _a === void 0 ? void 0 : _a.find((domain) => { var _a; return ((_a = domain.hostname) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === hostname.toLowerCase(); });
119431
+ return domain === null || domain === void 0 ? void 0 : domain.status;
119391
119432
  };
119392
119433
  this.loginToCustomDomain = async () => {
119393
119434
  await this.page.close();
119394
119435
  const loginPage = this.loginPage;
119395
119436
  const { email } = getGlobalUserState();
119396
- const loginUrl = `${this.baseURL}${ROUTES.admin}`;
119437
+ const loginUrl = `${this.customDomainURL}${ROUTES.admin}`;
119397
119438
  const playwrightUtils = new CustomCommands(loginPage, loginPage.request);
119398
119439
  const organizationPage = new OrganizationPage(loginPage, playwrightUtils);
119399
119440
  await loginPage.goto(loginUrl);
@@ -119405,7 +119446,7 @@ class CustomDomainPage {
119405
119446
  this.waitForTrustedSSL = () => test.expect
119406
119447
  .poll(() => new Promise(resolve => {
119407
119448
  https
119408
- .get(this.baseURL, { rejectUnauthorized: true }, res => {
119449
+ .get(this.customDomainURL, { rejectUnauthorized: true }, res => {
119409
119450
  res.resume();
119410
119451
  resolve(true);
119411
119452
  })
@@ -119419,7 +119460,7 @@ class CustomDomainPage {
119419
119460
  await this.loginPage.context().storageState({ path: STORAGE_STATE });
119420
119461
  const mergedCredentials = ramda.mergeAll([readFileSyncIfExists(), { user }]);
119421
119462
  writeDataToFile(JSON.stringify(mergedCredentials, null, 2));
119422
- updateCredentials({ key: "baseUrl", value: this.baseURL });
119463
+ updateCredentials({ key: "baseUrl", value: this.customDomainURL });
119423
119464
  updateCredentials({ key: "domain", value: CUSTOM_DOMAIN_SUFFIX });
119424
119465
  };
119425
119466
  // eslint-disable-next-line playwright/no-wait-for-timeout
@@ -119433,21 +119474,57 @@ class CustomDomainPage {
119433
119474
  await this.loginToCustomDomain();
119434
119475
  await this.saveCustomDomainState();
119435
119476
  };
119477
+ this.setupCustomDomainViaAPI = async () => {
119478
+ if (shouldSkipCustomDomainSetup())
119479
+ return;
119480
+ await this.connectViaAPI();
119481
+ const context = await this.browser.newContext(EMPTY_STORAGE_STATE);
119482
+ this.loginPage = await context.newPage();
119483
+ await this.loginToCustomDomain();
119484
+ await this.saveCustomDomainState();
119485
+ };
119436
119486
  this.connectCustomDomain = async (subdomain) => {
119437
119487
  if (shouldSkipCustomDomainSetup())
119438
119488
  return;
119439
- const baseURL = baseURLGenerator(this.product, subdomain);
119440
119489
  const domain = this.getCustomDomain(subdomain);
119441
- this.baseURL = `https://${domain}`;
119490
+ const baseURL = baseURLGenerator(this.product, this.subdomain);
119491
+ this.customDomainURL = `https://${domain}`;
119442
119492
  await this.neetoPlaywrightUtilities.waitForPageLoad();
119443
119493
  await this.page.goto(`${baseURL}${ROUTES.adminPanel.customDomain}`);
119444
119494
  await this.neetoPlaywrightUtilities.waitForPageLoad();
119445
119495
  await this.addCustomDomain(domain);
119446
119496
  await this.validateCustomDomain(domain);
119447
- process.env.BASE_URL = this.baseURL;
119497
+ process.env.BASE_URL = this.customDomainURL;
119448
119498
  await this.waitForTrustedSSL();
119449
119499
  await this.waitForDomainPropagation();
119450
119500
  };
119501
+ this.connectViaAPI = async (subdomain) => {
119502
+ if (shouldSkipCustomDomainSetup())
119503
+ return;
119504
+ const domain = this.getCustomDomain(subdomain);
119505
+ this.customDomainURL = `https://${domain}`;
119506
+ const baseURL = baseURLGenerator(this.product, this.subdomain);
119507
+ await this.page.goto(`${baseURL}${ROUTES.adminPanel.customDomain}`);
119508
+ await this.page.waitForLoadState("domcontentloaded");
119509
+ await this.customDomainApi.create(domain, baseURL);
119510
+ await this.validateDomain(baseURL);
119511
+ await this.awaitDomainValidation(domain, baseURL);
119512
+ process.env.BASE_URL = this.customDomainURL;
119513
+ await this.waitForTrustedSSL();
119514
+ await this.waitForDomainPropagation();
119515
+ };
119516
+ this.validateDomain = (baseURL) => test.expect
119517
+ .poll(async () => { var _a; return (_a = (await this.customDomainApi.validate(baseURL))) === null || _a === void 0 ? void 0 : _a.status(); }, {
119518
+ timeout: 2 * 60000,
119519
+ intervals: [10000],
119520
+ })
119521
+ .toBe(200);
119522
+ this.awaitDomainValidation = (hostname, baseURL) => test.expect
119523
+ .poll(async () => this.getDomainStatus(hostname, baseURL), {
119524
+ timeout: 2 * 60000,
119525
+ intervals: [5000],
119526
+ })
119527
+ .toBe("active");
119451
119528
  this.getCustomDomainFromAPI = async () => {
119452
119529
  const { custom_domains } = await this.customDomainApi
119453
119530
  .fetch()