seatable-html-page-sdk 0.0.12 → 0.0.13-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -272,16 +272,16 @@
272
272
  const FormDataCtor = typeof G.FormData !== 'undefined' ? G.FormData : undefined;
273
273
 
274
274
  const isFormData = (thing) => {
275
- let kind;
276
- return thing && (
277
- (FormDataCtor && thing instanceof FormDataCtor) || (
278
- isFunction$1(thing.append) && (
279
- (kind = kindOf(thing)) === 'formdata' ||
280
- // detect form-data instance
281
- (kind === 'object' && isFunction$1(thing.toString) && thing.toString() === '[object FormData]')
282
- )
283
- )
284
- );
275
+ if (!thing) return false;
276
+ if (FormDataCtor && thing instanceof FormDataCtor) return true;
277
+ // Reject plain objects inheriting directly from Object.prototype so prototype-pollution gadgets can't spoof FormData (GHSA-6chq-wfr3-2hj9).
278
+ const proto = getPrototypeOf(thing);
279
+ if (!proto || proto === Object.prototype) return false;
280
+ if (!isFunction$1(thing.append)) return false;
281
+ const kind = kindOf(thing);
282
+ return kind === 'formdata' ||
283
+ // detect form-data instance
284
+ (kind === 'object' && isFunction$1(thing.toString) && thing.toString() === '[object FormData]');
285
285
  };
286
286
 
287
287
  /**
@@ -948,40 +948,40 @@
948
948
  return axiosError;
949
949
  }
950
950
 
951
- /**
952
- * Create an Error with the specified message, config, error code, request and response.
953
- *
954
- * @param {string} message The error message.
955
- * @param {string} [code] The error code (for example, 'ECONNABORTED').
956
- * @param {Object} [config] The config.
957
- * @param {Object} [request] The request.
958
- * @param {Object} [response] The response.
959
- *
960
- * @returns {Error} The created error.
961
- */
962
- constructor(message, code, config, request, response) {
963
- super(message);
964
-
965
- // Make message enumerable to maintain backward compatibility
966
- // The native Error constructor sets message as non-enumerable,
967
- // but axios < v1.13.3 had it as enumerable
968
- Object.defineProperty(this, 'message', {
969
- value: message,
970
- enumerable: true,
971
- writable: true,
972
- configurable: true
973
- });
974
-
975
- this.name = 'AxiosError';
976
- this.isAxiosError = true;
977
- code && (this.code = code);
978
- config && (this.config = config);
979
- request && (this.request = request);
980
- if (response) {
981
- this.response = response;
982
- this.status = response.status;
983
- }
951
+ /**
952
+ * Create an Error with the specified message, config, error code, request and response.
953
+ *
954
+ * @param {string} message The error message.
955
+ * @param {string} [code] The error code (for example, 'ECONNABORTED').
956
+ * @param {Object} [config] The config.
957
+ * @param {Object} [request] The request.
958
+ * @param {Object} [response] The response.
959
+ *
960
+ * @returns {Error} The created error.
961
+ */
962
+ constructor(message, code, config, request, response) {
963
+ super(message);
964
+
965
+ // Make message enumerable to maintain backward compatibility
966
+ // The native Error constructor sets message as non-enumerable,
967
+ // but axios < v1.13.3 had it as enumerable
968
+ Object.defineProperty(this, 'message', {
969
+ value: message,
970
+ enumerable: true,
971
+ writable: true,
972
+ configurable: true,
973
+ });
974
+
975
+ this.name = 'AxiosError';
976
+ this.isAxiosError = true;
977
+ code && (this.code = code);
978
+ config && (this.config = config);
979
+ request && (this.request = request);
980
+ if (response) {
981
+ this.response = response;
982
+ this.status = response.status;
984
983
  }
984
+ }
985
985
 
986
986
  toJSON() {
987
987
  return {
@@ -1017,6 +1017,7 @@
1017
1017
  AxiosError.ERR_CANCELED = 'ERR_CANCELED';
1018
1018
  AxiosError.ERR_NOT_SUPPORT = 'ERR_NOT_SUPPORT';
1019
1019
  AxiosError.ERR_INVALID_URL = 'ERR_INVALID_URL';
1020
+ AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED = 'ERR_FORM_DATA_DEPTH_EXCEEDED';
1020
1021
 
1021
1022
  var AxiosError$1 = AxiosError;
1022
1023
 
@@ -1133,6 +1134,7 @@
1133
1134
  const dots = options.dots;
1134
1135
  const indexes = options.indexes;
1135
1136
  const _Blob = options.Blob || (typeof Blob !== 'undefined' && Blob);
1137
+ const maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth;
1136
1138
  const useBlob = _Blob && utils$1.isSpecCompliantForm(formData);
1137
1139
 
1138
1140
  if (!utils$1.isFunction(visitor)) {
@@ -1225,9 +1227,16 @@
1225
1227
  isVisitable,
1226
1228
  });
1227
1229
 
1228
- function build(value, path) {
1230
+ function build(value, path, depth = 0) {
1229
1231
  if (utils$1.isUndefined(value)) return;
1230
1232
 
1233
+ if (depth > maxDepth) {
1234
+ throw new AxiosError$1(
1235
+ 'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth,
1236
+ AxiosError$1.ERR_FORM_DATA_DEPTH_EXCEEDED
1237
+ );
1238
+ }
1239
+
1231
1240
  if (stack.indexOf(value) !== -1) {
1232
1241
  throw Error('Circular reference detected in ' + path.join('.'));
1233
1242
  }
@@ -1240,7 +1249,7 @@
1240
1249
  visitor.call(formData, el, utils$1.isString(key) ? key.trim() : key, path, exposedHelpers);
1241
1250
 
1242
1251
  if (result === true) {
1243
- build(el, path ? path.concat(key) : [key]);
1252
+ build(el, path ? path.concat(key) : [key], depth + 1);
1244
1253
  }
1245
1254
  });
1246
1255
 
@@ -1272,9 +1281,8 @@
1272
1281
  ')': '%29',
1273
1282
  '~': '%7E',
1274
1283
  '%20': '+',
1275
- '%00': '\x00',
1276
1284
  };
1277
- return encodeURIComponent(str).replace(/[!'()~]|%20|%00/g, function replacer(match) {
1285
+ return encodeURIComponent(str).replace(/[!'()~]|%20/g, function replacer(match) {
1278
1286
  return charMap[match];
1279
1287
  });
1280
1288
  }
@@ -1596,7 +1604,9 @@
1596
1604
 
1597
1605
  if (isLast) {
1598
1606
  if (utils$1.hasOwnProp(target, name)) {
1599
- target[name] = [target[name], value];
1607
+ target[name] = utils$1.isArray(target[name])
1608
+ ? target[name].concat(value)
1609
+ : [target[name], value];
1600
1610
  } else {
1601
1611
  target[name] = value;
1602
1612
  }
@@ -1630,6 +1640,8 @@
1630
1640
  return null;
1631
1641
  }
1632
1642
 
1643
+ const own = (obj, key) => (obj != null && utils$1.hasOwnProp(obj, key) ? obj[key] : undefined);
1644
+
1633
1645
  /**
1634
1646
  * It takes a string, tries to parse it, and if it fails, it returns the stringified version
1635
1647
  * of the input
@@ -1697,20 +1709,22 @@
1697
1709
  let isFileList;
1698
1710
 
1699
1711
  if (isObjectPayload) {
1712
+ const formSerializer = own(this, 'formSerializer');
1700
1713
  if (contentType.indexOf('application/x-www-form-urlencoded') > -1) {
1701
- return toURLEncodedForm(data, this.formSerializer).toString();
1714
+ return toURLEncodedForm(data, formSerializer).toString();
1702
1715
  }
1703
1716
 
1704
1717
  if (
1705
1718
  (isFileList = utils$1.isFileList(data)) ||
1706
1719
  contentType.indexOf('multipart/form-data') > -1
1707
1720
  ) {
1708
- const _FormData = this.env && this.env.FormData;
1721
+ const env = own(this, 'env');
1722
+ const _FormData = env && env.FormData;
1709
1723
 
1710
1724
  return toFormData(
1711
1725
  isFileList ? { 'files[]': data } : data,
1712
1726
  _FormData && new _FormData(),
1713
- this.formSerializer
1727
+ formSerializer
1714
1728
  );
1715
1729
  }
1716
1730
  }
@@ -1726,9 +1740,10 @@
1726
1740
 
1727
1741
  transformResponse: [
1728
1742
  function transformResponse(data) {
1729
- const transitional = this.transitional || defaults.transitional;
1743
+ const transitional = own(this, 'transitional') || defaults.transitional;
1730
1744
  const forcedJSONParsing = transitional && transitional.forcedJSONParsing;
1731
- const JSONRequested = this.responseType === 'json';
1745
+ const responseType = own(this, 'responseType');
1746
+ const JSONRequested = responseType === 'json';
1732
1747
 
1733
1748
  if (utils$1.isResponse(data) || utils$1.isReadableStream(data)) {
1734
1749
  return data;
@@ -1737,17 +1752,17 @@
1737
1752
  if (
1738
1753
  data &&
1739
1754
  utils$1.isString(data) &&
1740
- ((forcedJSONParsing && !this.responseType) || JSONRequested)
1755
+ ((forcedJSONParsing && !responseType) || JSONRequested)
1741
1756
  ) {
1742
1757
  const silentJSONParsing = transitional && transitional.silentJSONParsing;
1743
1758
  const strictJSONParsing = !silentJSONParsing && JSONRequested;
1744
1759
 
1745
1760
  try {
1746
- return JSON.parse(data, this.parseReviver);
1761
+ return JSON.parse(data, own(this, 'parseReviver'));
1747
1762
  } catch (e) {
1748
1763
  if (strictJSONParsing) {
1749
1764
  if (e.name === 'SyntaxError') {
1750
- throw AxiosError$1.from(e, AxiosError$1.ERR_BAD_RESPONSE, this, null, this.response);
1765
+ throw AxiosError$1.from(e, AxiosError$1.ERR_BAD_RESPONSE, this, null, own(this, 'response'));
1751
1766
  }
1752
1767
  throw e;
1753
1768
  }
@@ -1861,41 +1876,41 @@
1861
1876
 
1862
1877
  const $internals = Symbol('internals');
1863
1878
 
1864
- const isValidHeaderValue = (value) => !/[\r\n]/.test(value);
1879
+ const INVALID_HEADER_VALUE_CHARS_RE = /[^\x09\x20-\x7E\x80-\xFF]/g;
1865
1880
 
1866
- function assertValidHeaderValue(value, header) {
1867
- if (value === false || value == null) {
1868
- return;
1869
- }
1881
+ function trimSPorHTAB(str) {
1882
+ let start = 0;
1883
+ let end = str.length;
1870
1884
 
1871
- if (utils$1.isArray(value)) {
1872
- value.forEach((v) => assertValidHeaderValue(v, header));
1873
- return;
1874
- }
1885
+ while (start < end) {
1886
+ const code = str.charCodeAt(start);
1875
1887
 
1876
- if (!isValidHeaderValue(String(value))) {
1877
- throw new Error(`Invalid character in header content ["${header}"]`);
1878
- }
1879
- }
1880
-
1881
- function normalizeHeader(header) {
1882
- return header && String(header).trim().toLowerCase();
1883
- }
1888
+ if (code !== 0x09 && code !== 0x20) {
1889
+ break;
1890
+ }
1884
1891
 
1885
- function stripTrailingCRLF(str) {
1886
- let end = str.length;
1892
+ start += 1;
1893
+ }
1887
1894
 
1888
- while (end > 0) {
1889
- const charCode = str.charCodeAt(end - 1);
1895
+ while (end > start) {
1896
+ const code = str.charCodeAt(end - 1);
1890
1897
 
1891
- if (charCode !== 10 && charCode !== 13) {
1898
+ if (code !== 0x09 && code !== 0x20) {
1892
1899
  break;
1893
1900
  }
1894
1901
 
1895
1902
  end -= 1;
1896
1903
  }
1897
1904
 
1898
- return end === str.length ? str : str.slice(0, end);
1905
+ return start === 0 && end === str.length ? str : str.slice(start, end);
1906
+ }
1907
+
1908
+ function normalizeHeader(header) {
1909
+ return header && String(header).trim().toLowerCase();
1910
+ }
1911
+
1912
+ function sanitizeHeaderValue(str) {
1913
+ return trimSPorHTAB(str.replace(INVALID_HEADER_VALUE_CHARS_RE, ''));
1899
1914
  }
1900
1915
 
1901
1916
  function normalizeValue(value) {
@@ -1903,7 +1918,7 @@
1903
1918
  return value;
1904
1919
  }
1905
1920
 
1906
- return utils$1.isArray(value) ? value.map(normalizeValue) : stripTrailingCRLF(String(value));
1921
+ return utils$1.isArray(value) ? value.map(normalizeValue) : sanitizeHeaderValue(String(value));
1907
1922
  }
1908
1923
 
1909
1924
  function parseTokens(str) {
@@ -1985,7 +2000,6 @@
1985
2000
  _rewrite === true ||
1986
2001
  (_rewrite === undefined && self[key] !== false)
1987
2002
  ) {
1988
- assertValidHeaderValue(_value, _header);
1989
2003
  self[key || _header] = normalizeValue(_value);
1990
2004
  }
1991
2005
  }
@@ -2412,13 +2426,13 @@
2412
2426
  const _speedometer = speedometer(50, 250);
2413
2427
 
2414
2428
  return throttle((e) => {
2415
- const loaded = e.loaded;
2429
+ const rawLoaded = e.loaded;
2416
2430
  const total = e.lengthComputable ? e.total : undefined;
2417
- const progressBytes = loaded - bytesNotified;
2431
+ const loaded = total != null ? Math.min(rawLoaded, total) : rawLoaded;
2432
+ const progressBytes = Math.max(0, loaded - bytesNotified);
2418
2433
  const rate = _speedometer(progressBytes);
2419
- const inRange = loaded <= total;
2420
2434
 
2421
- bytesNotified = loaded;
2435
+ bytesNotified = Math.max(bytesNotified, loaded);
2422
2436
 
2423
2437
  const data = {
2424
2438
  loaded,
@@ -2426,7 +2440,7 @@
2426
2440
  progress: total ? loaded / total : undefined,
2427
2441
  bytes: progressBytes,
2428
2442
  rate: rate ? rate : undefined,
2429
- estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
2443
+ estimated: rate && total ? (total - loaded) / rate : undefined,
2430
2444
  event: e,
2431
2445
  lengthComputable: total != null,
2432
2446
  [isDownloadStream ? 'download' : 'upload']: true,
@@ -2560,7 +2574,7 @@
2560
2574
  */
2561
2575
  function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls) {
2562
2576
  let isRelativeUrl = !isAbsoluteURL(requestedURL);
2563
- if (baseURL && (isRelativeUrl || allowAbsoluteUrls == false)) {
2577
+ if (baseURL && (isRelativeUrl || allowAbsoluteUrls === false)) {
2564
2578
  return combineURLs(baseURL, requestedURL);
2565
2579
  }
2566
2580
  return requestedURL;
@@ -2580,7 +2594,18 @@
2580
2594
  function mergeConfig(config1, config2) {
2581
2595
  // eslint-disable-next-line no-param-reassign
2582
2596
  config2 = config2 || {};
2583
- const config = {};
2597
+
2598
+ // Use a null-prototype object so that downstream reads such as `config.auth`
2599
+ // or `config.baseURL` cannot inherit polluted values from Object.prototype
2600
+ // (see GHSA-q8qp-cvcw-x6jj). `hasOwnProperty` is restored as a non-enumerable
2601
+ // own slot to preserve ergonomics for user code that relies on it.
2602
+ const config = Object.create(null);
2603
+ Object.defineProperty(config, 'hasOwnProperty', {
2604
+ value: Object.prototype.hasOwnProperty,
2605
+ enumerable: false,
2606
+ writable: true,
2607
+ configurable: true,
2608
+ });
2584
2609
 
2585
2610
  function getMergedValue(target, source, prop, caseless) {
2586
2611
  if (utils$1.isPlainObject(target) && utils$1.isPlainObject(source)) {
@@ -2619,9 +2644,9 @@
2619
2644
 
2620
2645
  // eslint-disable-next-line consistent-return
2621
2646
  function mergeDirectKeys(a, b, prop) {
2622
- if (prop in config2) {
2647
+ if (utils$1.hasOwnProp(config2, prop)) {
2623
2648
  return getMergedValue(a, b);
2624
- } else if (prop in config1) {
2649
+ } else if (utils$1.hasOwnProp(config1, prop)) {
2625
2650
  return getMergedValue(undefined, a);
2626
2651
  }
2627
2652
  }
@@ -2653,6 +2678,7 @@
2653
2678
  httpsAgent: defaultToConfig2,
2654
2679
  cancelToken: defaultToConfig2,
2655
2680
  socketPath: defaultToConfig2,
2681
+ allowedSocketPaths: defaultToConfig2,
2656
2682
  responseEncoding: defaultToConfig2,
2657
2683
  validateStatus: mergeDirectKeys,
2658
2684
  headers: (a, b, prop) =>
@@ -2662,7 +2688,9 @@
2662
2688
  utils$1.forEach(Object.keys({ ...config1, ...config2 }), function computeConfigValue(prop) {
2663
2689
  if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') return;
2664
2690
  const merge = utils$1.hasOwnProp(mergeMap, prop) ? mergeMap[prop] : mergeDeepProperties;
2665
- const configValue = merge(config1[prop], config2[prop], prop);
2691
+ const a = utils$1.hasOwnProp(config1, prop) ? config1[prop] : undefined;
2692
+ const b = utils$1.hasOwnProp(config2, prop) ? config2[prop] : undefined;
2693
+ const configValue = merge(a, b, prop);
2666
2694
  (utils$1.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
2667
2695
  });
2668
2696
 
@@ -2672,12 +2700,24 @@
2672
2700
  var resolveConfig = (config) => {
2673
2701
  const newConfig = mergeConfig({}, config);
2674
2702
 
2675
- let { data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth } = newConfig;
2703
+ // Read only own properties to prevent prototype pollution gadgets
2704
+ // (e.g. Object.prototype.baseURL = 'https://evil.com'). See GHSA-q8qp-cvcw-x6jj.
2705
+ const own = (key) => (utils$1.hasOwnProp(newConfig, key) ? newConfig[key] : undefined);
2706
+
2707
+ const data = own('data');
2708
+ let withXSRFToken = own('withXSRFToken');
2709
+ const xsrfHeaderName = own('xsrfHeaderName');
2710
+ const xsrfCookieName = own('xsrfCookieName');
2711
+ let headers = own('headers');
2712
+ const auth = own('auth');
2713
+ const baseURL = own('baseURL');
2714
+ const allowAbsoluteUrls = own('allowAbsoluteUrls');
2715
+ const url = own('url');
2676
2716
 
2677
2717
  newConfig.headers = headers = AxiosHeaders$1.from(headers);
2678
2718
 
2679
2719
  newConfig.url = buildURL(
2680
- buildFullPath(newConfig.baseURL, newConfig.url, newConfig.allowAbsoluteUrls),
2720
+ buildFullPath(baseURL, url, allowAbsoluteUrls),
2681
2721
  config.params,
2682
2722
  config.paramsSerializer
2683
2723
  );
@@ -2716,10 +2756,18 @@
2716
2756
  // Specifically not if we're in a web worker, or react-native.
2717
2757
 
2718
2758
  if (platform.hasStandardBrowserEnv) {
2719
- withXSRFToken && utils$1.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(newConfig));
2759
+ if (utils$1.isFunction(withXSRFToken)) {
2760
+ withXSRFToken = withXSRFToken(newConfig);
2761
+ }
2720
2762
 
2721
- if (withXSRFToken || (withXSRFToken !== false && isURLSameOrigin(newConfig.url))) {
2722
- // Add xsrf header
2763
+ // Strict boolean check prevents proto-pollution gadgets (e.g. Object.prototype.withXSRFToken = 1)
2764
+ // and misconfigurations (e.g. "false") from short-circuiting the same-origin check and leaking
2765
+ // the XSRF token cross-origin. See GHSA-xx6v-rp6x-q39c.
2766
+ const shouldSendXSRF =
2767
+ withXSRFToken === true ||
2768
+ (withXSRFToken == null && isURLSameOrigin(newConfig.url));
2769
+
2770
+ if (shouldSendXSRF) {
2723
2771
  const xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName);
2724
2772
 
2725
2773
  if (xsrfValue) {
@@ -3140,18 +3188,20 @@
3140
3188
  test(() => {
3141
3189
  let duplexAccessed = false;
3142
3190
 
3143
- const body = new ReadableStream$1();
3144
-
3145
- const hasContentType = new Request(platform.origin, {
3146
- body,
3191
+ const request = new Request(platform.origin, {
3192
+ body: new ReadableStream$1(),
3147
3193
  method: 'POST',
3148
3194
  get duplex() {
3149
3195
  duplexAccessed = true;
3150
3196
  return 'half';
3151
3197
  },
3152
- }).headers.has('Content-Type');
3198
+ });
3199
+
3200
+ const hasContentType = request.headers.has('Content-Type');
3153
3201
 
3154
- body.cancel();
3202
+ if (request.body != null) {
3203
+ request.body.cancel();
3204
+ }
3155
3205
 
3156
3206
  return duplexAccessed && !hasContentType;
3157
3207
  });
@@ -3295,6 +3345,19 @@
3295
3345
  // see https://github.com/cloudflare/workerd/issues/902
3296
3346
  const isCredentialsSupported = isRequestSupported && 'credentials' in Request.prototype;
3297
3347
 
3348
+ // If data is FormData and Content-Type is multipart/form-data without boundary,
3349
+ // delete it so fetch can set it correctly with the boundary
3350
+ if (utils$1.isFormData(data)) {
3351
+ const contentType = headers.getContentType();
3352
+ if (
3353
+ contentType &&
3354
+ /^multipart\/form-data/i.test(contentType) &&
3355
+ !/boundary=/i.test(contentType)
3356
+ ) {
3357
+ headers.delete('content-type');
3358
+ }
3359
+ }
3360
+
3298
3361
  const resolvedOptions = {
3299
3362
  ...fetchOptions,
3300
3363
  signal: composedSignal,
@@ -3603,7 +3666,7 @@
3603
3666
  );
3604
3667
  }
3605
3668
 
3606
- const VERSION = "1.15.0";
3669
+ const VERSION = "1.15.2";
3607
3670
 
3608
3671
  const validators$1 = {};
3609
3672
 
@@ -3688,7 +3751,9 @@
3688
3751
  let i = keys.length;
3689
3752
  while (i-- > 0) {
3690
3753
  const opt = keys[i];
3691
- const validator = schema[opt];
3754
+ // Use hasOwnProperty so a polluted Object.prototype.<opt> cannot supply
3755
+ // a non-function validator and cause a TypeError. See GHSA-q8qp-cvcw-x6jj.
3756
+ const validator = Object.prototype.hasOwnProperty.call(schema, opt) ? schema[opt] : undefined;
3692
3757
  if (validator) {
3693
3758
  const value = options[opt];
3694
3759
  const result = value === undefined || validator(value, opt, options);
@@ -4478,8 +4543,132 @@
4478
4543
  HTML_PAGE_REQUEST: 'HTML_PAGE_REQUEST',
4479
4544
  HTML_PAGE_RESPONSE: 'HTML_PAGE_RESPONSE',
4480
4545
  HTML_PAGE_EVENT: 'HTML_PAGE_EVENT',
4546
+ HTML_PAGE_ENABLE_COMMENT_MODE: 'HTML_PAGE_ENABLE_COMMENT_MODE',
4547
+ HTML_PAGE_DISABLE_COMMENT_MODE: 'HTML_PAGE_DISABLE_COMMENT_MODE',
4548
+ HTML_PAGE_COMMENT_MODE_ELEMENT_HOVER: 'HTML_PAGE_COMMENT_MODE_ELEMENT_HOVER',
4549
+ HTML_PAGE_COMMENT_MODE_ELEMENT_SELECTED: 'HTML_PAGE_COMMENT_MODE_ELEMENT_SELECTED',
4481
4550
  WINDOW_EVENT: 'WINDOW_EVENT'
4482
4551
  };
4552
+
4553
+ const countSameTagSiblingsBefore = element => {
4554
+ let count = 0;
4555
+ let sibling = element.previousElementSibling;
4556
+ while (sibling) {
4557
+ if (sibling.tagName === element.tagName) count += 1;
4558
+ sibling = sibling.previousElementSibling;
4559
+ }
4560
+ return count;
4561
+ };
4562
+ const generateSelector = element => {
4563
+ if (!element || element === document.body) return null;
4564
+ const parts = [];
4565
+ let current = element;
4566
+ while (current && current !== document.body) {
4567
+ const tag = current.tagName.toLowerCase();
4568
+ const index = countSameTagSiblingsBefore(current) + 1;
4569
+ parts.unshift(`${tag}:nth-of-type(${index})`);
4570
+ current = current.parentElement;
4571
+ }
4572
+ return 'body > ' + parts.join(' > ');
4573
+ };
4574
+ const getHtmlHint = function (element) {
4575
+ let maxLen = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 180;
4576
+ const html = element.outerHTML || '';
4577
+ return html.length > maxLen ? html.slice(0, maxLen) : html;
4578
+ };
4579
+ const getCurrentText = function (element) {
4580
+ let maxLen = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 160;
4581
+ const text = (element.textContent || '').replace(/\s+/g, ' ').trim();
4582
+ return text.length > maxLen ? text.slice(0, maxLen) : text;
4583
+ };
4584
+ const computeStyle = element => {
4585
+ const style = window.getComputedStyle(element);
4586
+ return {
4587
+ backgroundColor: style.backgroundColor,
4588
+ color: style.color,
4589
+ fontSize: style.fontSize,
4590
+ fontWeight: style.fontWeight,
4591
+ fontFamily: style.fontFamily,
4592
+ lineHeight: style.lineHeight,
4593
+ borderRadius: style.borderRadius,
4594
+ paddingBottom: style.paddingBottom,
4595
+ paddingLeft: style.paddingLeft,
4596
+ paddingRight: style.paddingRight,
4597
+ paddingTop: style.paddingTop,
4598
+ marginBottom: style.marginBottom,
4599
+ marginLeft: style.marginLeft,
4600
+ marginRight: style.marginRight,
4601
+ marginTop: style.marginTop,
4602
+ textAlign: style.textAlign,
4603
+ display: style.display,
4604
+ width: style.width,
4605
+ height: style.height
4606
+ };
4607
+ };
4608
+ const generateLabel = element => {
4609
+ const tag = element.tagName.toLowerCase();
4610
+ const classes = element.classList.length > 0 ? '.' + Array.from(element.classList).join('.') : '';
4611
+ return `${tag}${classes}`;
4612
+ };
4613
+ class CommentModeAdapter {
4614
+ constructor() {
4615
+ this.isActive = false;
4616
+ this._handleEvent = this._handleEvent.bind(this);
4617
+ this.mouseEvents = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'contextmenu'];
4618
+ }
4619
+ enable() {
4620
+ if (this.isActive) return;
4621
+ this.isActive = true;
4622
+ this.mouseEvents.forEach(eventType => {
4623
+ window.addEventListener(eventType, this._handleEvent, true);
4624
+ });
4625
+ }
4626
+ disable() {
4627
+ if (!this.isActive) return;
4628
+ this.isActive = false;
4629
+ this.mouseEvents.forEach(eventType => {
4630
+ window.removeEventListener(eventType, this._handleEvent, true);
4631
+ });
4632
+ }
4633
+ _handleEvent(event) {
4634
+ if (!this.isActive) return;
4635
+ event.preventDefault();
4636
+ event.stopPropagation();
4637
+ event.stopImmediatePropagation();
4638
+ const target = event.target;
4639
+ if (event.type === 'mouseover') {
4640
+ const data = this.buildElementData(target);
4641
+ if (data) {
4642
+ window.parent.postMessage({
4643
+ type: POST_MESSAGE_TYPE.HTML_PAGE_COMMENT_MODE_ELEMENT_HOVER,
4644
+ data
4645
+ }, '*');
4646
+ }
4647
+ } else if (event.type === 'click') {
4648
+ const data = this.buildElementData(target);
4649
+ if (data) {
4650
+ window.parent.postMessage({
4651
+ type: POST_MESSAGE_TYPE.HTML_PAGE_COMMENT_MODE_ELEMENT_SELECTED,
4652
+ data
4653
+ }, '*');
4654
+ }
4655
+ }
4656
+ }
4657
+ buildElementData(target) {
4658
+ const selector = generateSelector(target) || null;
4659
+ return {
4660
+ selector,
4661
+ currentText: getCurrentText(target),
4662
+ htmlHint: getHtmlHint(target),
4663
+ computedStyle: computeStyle(target),
4664
+ label: generateLabel(target)
4665
+ };
4666
+ }
4667
+ destroy() {
4668
+ this.disable();
4669
+ }
4670
+ }
4671
+
4483
4672
  const POST_MESSAGE_REQUEST_TYPE = {
4484
4673
  GET_SERVER: 'get_server',
4485
4674
  GET_ACCESS_TOKEN: 'get_access_token',
@@ -4556,6 +4745,8 @@
4556
4745
  this.pendingRequests = {};
4557
4746
  this.eventHandlers = {};
4558
4747
  this.timeout = this.options.timeout || 10000;
4748
+ this.isCommentMode = false;
4749
+ this.commentModeAdapter = new CommentModeAdapter();
4559
4750
  this.setupMessageListener();
4560
4751
  }
4561
4752
  generatorRequestId() {
@@ -4583,47 +4774,57 @@
4583
4774
  }, this.targetOrigin);
4584
4775
  }
4585
4776
  setEventsListener() {
4586
- let rafId = null;
4587
- let pendingEvent = null;
4588
- [...SUPPORT_WINDOW_MOUSE_EVENT_TYPES, ...SUPPORT_WINDOW_KEYBOARD_EVENT_TYPES, ...SUPPORT_WINDOW_DRAG_EVENT_TYPES].forEach(eventType => {
4589
- window.addEventListener(eventType, event => {
4590
- if (event.source === WINDOW_EVENT_SOURCE_TYPE.APP) return;
4591
- const target = event.target;
4592
- if (target && INTERACTIVE_TAGS.includes(target.tagName)) return;
4593
- if (SUPPORT_WINDOW_KEYBOARD_EVENT_TYPES.includes(eventType)) {
4594
- const active = document.activeElement;
4595
- if (active && INTERACTIVE_TAGS.includes(active.tagName)) return;
4596
- }
4597
- if (HIGH_FREQUENCY_WINDOW_EVENT_TYPES.includes(eventType)) {
4598
- // High-frequency events that need throttling (use RAF to limit to 60fps)
4599
- // Use requestAnimationFrame for throttling high-frequency events
4600
- // Store the latest event with necessary data
4601
- pendingEvent = createWindowEventData({
4602
- eventType,
4603
- event
4604
- });
4605
-
4606
- // Only schedule a new frame if one isn't already scheduled
4607
- if (rafId === null) {
4608
- rafId = requestAnimationFrame(() => {
4609
- if (pendingEvent) {
4610
- this.postWindowEvent(pendingEvent);
4611
- pendingEvent = null;
4612
- rafId = null;
4613
- }
4614
- });
4615
- }
4616
- return;
4617
- }
4618
-
4619
- // Low-frequency events
4620
- this.postWindowEvent(createWindowEventData({
4621
- eventType,
4622
- event
4623
- }));
4624
- }, true);
4777
+ this._windowEventHandler = this._windowEventHandler.bind(this);
4778
+ this.interactiveEventTypes = [...SUPPORT_WINDOW_MOUSE_EVENT_TYPES, ...SUPPORT_WINDOW_KEYBOARD_EVENT_TYPES, ...SUPPORT_WINDOW_DRAG_EVENT_TYPES];
4779
+ this.rafId = null;
4780
+ this.pendingEvent = null;
4781
+ this.bindInteractiveEvents();
4782
+ }
4783
+ bindInteractiveEvents() {
4784
+ this.interactiveEventTypes.forEach(eventType => {
4785
+ window.addEventListener(eventType, this._windowEventHandler, true);
4625
4786
  });
4626
4787
  }
4788
+ unbindInteractiveEvents() {
4789
+ this.interactiveEventTypes.forEach(eventType => {
4790
+ window.removeEventListener(eventType, this._windowEventHandler, true);
4791
+ });
4792
+ if (this.rafId !== null) {
4793
+ cancelAnimationFrame(this.rafId);
4794
+ this.rafId = null;
4795
+ }
4796
+ this.pendingEvent = null;
4797
+ }
4798
+ _windowEventHandler(event) {
4799
+ if (event.source === WINDOW_EVENT_SOURCE_TYPE.APP) return;
4800
+ const target = event.target;
4801
+ if (target && INTERACTIVE_TAGS.includes(target.tagName)) return;
4802
+ const eventType = event.type;
4803
+ if (SUPPORT_WINDOW_KEYBOARD_EVENT_TYPES.includes(eventType)) {
4804
+ const active = document.activeElement;
4805
+ if (active && INTERACTIVE_TAGS.includes(active.tagName)) return;
4806
+ }
4807
+ if (HIGH_FREQUENCY_WINDOW_EVENT_TYPES.includes(eventType)) {
4808
+ this.pendingEvent = createWindowEventData({
4809
+ eventType,
4810
+ event
4811
+ });
4812
+ if (this.rafId === null) {
4813
+ this.rafId = requestAnimationFrame(() => {
4814
+ if (this.pendingEvent) {
4815
+ this.postWindowEvent(this.pendingEvent);
4816
+ this.pendingEvent = null;
4817
+ this.rafId = null;
4818
+ }
4819
+ });
4820
+ }
4821
+ return;
4822
+ }
4823
+ this.postWindowEvent(createWindowEventData({
4824
+ eventType,
4825
+ event
4826
+ }));
4827
+ }
4627
4828
  async request(method, params) {
4628
4829
  if (this.selfWindow) {
4629
4830
  return new Promise(resolve => {
@@ -4681,62 +4882,75 @@
4681
4882
  }
4682
4883
  } else if (type === POST_MESSAGE_TYPE.HTML_PAGE_EVENT) {
4683
4884
  this.emitEvent(eventType, payload);
4885
+ } else if (type === POST_MESSAGE_TYPE.HTML_PAGE_ENABLE_COMMENT_MODE) {
4886
+ this.isCommentMode = true;
4887
+ document.body.style.cursor = 'crosshair';
4888
+ this.unbindInteractiveEvents();
4889
+ if (this.commentModeAdapter) this.commentModeAdapter.enable();
4890
+ } else if (type === POST_MESSAGE_TYPE.HTML_PAGE_DISABLE_COMMENT_MODE) {
4891
+ this.isCommentMode = false;
4892
+ document.body.style.cursor = '';
4893
+ if (this.commentModeAdapter) this.commentModeAdapter.disable();
4894
+ this.bindInteractiveEvents();
4684
4895
  } else if (type === POST_MESSAGE_TYPE.WINDOW_EVENT) {
4685
- const eventData = data.event_data;
4686
- if (!eventData) return;
4687
- let syntheticEvent;
4688
- let targetElement;
4689
- if (SUPPORT_WINDOW_KEYBOARD_EVENT_TYPES.includes(eventData.type)) {
4690
- syntheticEvent = new KeyboardEvent(eventData.type, {
4691
- bubbles: true,
4692
- cancelable: true,
4693
- key: eventData.key,
4694
- code: eventData.code,
4695
- keyCode: eventData.keyCode,
4696
- ctrlKey: eventData.ctrlKey,
4697
- shiftKey: eventData.shiftKey,
4698
- altKey: eventData.altKey,
4699
- metaKey: eventData.metaKey,
4700
- repeat: eventData.repeat,
4701
- view: window
4702
- });
4703
- targetElement = document.activeElement || document.body;
4704
- } else if (SUPPORT_WINDOW_MOUSE_EVENT_TYPES.includes(eventData.type)) {
4705
- syntheticEvent = new MouseEvent(eventData.type, {
4706
- bubbles: true,
4707
- cancelable: true,
4708
- view: window,
4709
- clientX: eventData.x,
4710
- clientY: eventData.y,
4711
- screenX: eventData.x,
4712
- screenY: eventData.y,
4713
- button: eventData.button,
4714
- buttons: eventData.buttons
4715
- });
4716
- const elementAtPoint = document.elementFromPoint(eventData.x, eventData.y);
4717
- targetElement = elementAtPoint || document.body;
4718
- } else if (SUPPORT_WINDOW_DRAG_EVENT_TYPES.includes(eventData.type)) {
4719
- syntheticEvent = new DragEvent(eventData.type, {
4720
- bubbles: true,
4721
- cancelable: true,
4722
- view: window,
4723
- clientX: eventData.x,
4724
- clientY: eventData.y,
4725
- screenX: eventData.x,
4726
- screenY: eventData.y,
4727
- button: eventData.button,
4728
- buttons: eventData.buttons
4729
- });
4730
- const elementAtPoint = document.elementFromPoint(eventData.x, eventData.y);
4731
- targetElement = elementAtPoint || document.body;
4732
- }
4733
- if (!targetElement || !syntheticEvent) return;
4734
-
4735
- // Dispatch once on the target element, it will bubble up naturally
4736
- syntheticEvent.source = eventData.source;
4737
- targetElement.dispatchEvent(syntheticEvent);
4896
+ this.handleWindowEvent(data);
4738
4897
  }
4739
4898
  }
4899
+ handleWindowEvent(data) {
4900
+ const eventData = data.event_data;
4901
+ if (!eventData || this.isCommentMode) return;
4902
+ let syntheticEvent;
4903
+ let targetElement;
4904
+ if (SUPPORT_WINDOW_KEYBOARD_EVENT_TYPES.includes(eventData.type)) {
4905
+ syntheticEvent = new KeyboardEvent(eventData.type, {
4906
+ bubbles: true,
4907
+ cancelable: true,
4908
+ key: eventData.key,
4909
+ code: eventData.code,
4910
+ keyCode: eventData.keyCode,
4911
+ ctrlKey: eventData.ctrlKey,
4912
+ shiftKey: eventData.shiftKey,
4913
+ altKey: eventData.altKey,
4914
+ metaKey: eventData.metaKey,
4915
+ repeat: eventData.repeat,
4916
+ view: window
4917
+ });
4918
+ targetElement = document.activeElement || document.body;
4919
+ } else if (SUPPORT_WINDOW_MOUSE_EVENT_TYPES.includes(eventData.type)) {
4920
+ syntheticEvent = new MouseEvent(eventData.type, {
4921
+ bubbles: true,
4922
+ cancelable: true,
4923
+ view: window,
4924
+ clientX: eventData.x,
4925
+ clientY: eventData.y,
4926
+ screenX: eventData.x,
4927
+ screenY: eventData.y,
4928
+ button: eventData.button,
4929
+ buttons: eventData.buttons
4930
+ });
4931
+ const elementAtPoint = document.elementFromPoint(eventData.x, eventData.y);
4932
+ targetElement = elementAtPoint || document.body;
4933
+ } else if (SUPPORT_WINDOW_DRAG_EVENT_TYPES.includes(eventData.type)) {
4934
+ syntheticEvent = new DragEvent(eventData.type, {
4935
+ bubbles: true,
4936
+ cancelable: true,
4937
+ view: window,
4938
+ clientX: eventData.x,
4939
+ clientY: eventData.y,
4940
+ screenX: eventData.x,
4941
+ screenY: eventData.y,
4942
+ button: eventData.button,
4943
+ buttons: eventData.buttons
4944
+ });
4945
+ const elementAtPoint = document.elementFromPoint(eventData.x, eventData.y);
4946
+ targetElement = elementAtPoint || document.body;
4947
+ }
4948
+ if (!targetElement || !syntheticEvent) return;
4949
+
4950
+ // Dispatch once on the target element, it will bubble up naturally
4951
+ syntheticEvent.source = eventData.source;
4952
+ targetElement.dispatchEvent(syntheticEvent);
4953
+ }
4740
4954
  on(eventType, handler) {
4741
4955
  if (!hasOwnProperty(this.eventHandlers, eventType)) {
4742
4956
  this.eventHandlers[eventType] = [];
@@ -4771,6 +4985,9 @@
4771
4985
  });
4772
4986
  this.pendingRequests = {};
4773
4987
  this.eventHandlers = {};
4988
+ if (this.commentModeAdapter) {
4989
+ this.commentModeAdapter.destroy();
4990
+ }
4774
4991
  }
4775
4992
  }
4776
4993