@tightknitai/block-kitchen 0.10.12 → 0.10.14

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.cjs CHANGED
@@ -1868,6 +1868,7 @@ function groupValidatorErrors(errors, blocks) {
1868
1868
  var SAFE_LINK_PROTOCOLS = /^(https?|ircs?|mailto|tel|sms|xmpp)$/i;
1869
1869
  var SAFE_IMAGE_PROTOCOLS = /^(https?)$/i;
1870
1870
  var SAFE_IMAGE_DATA_PREFIX = /^data:image\/(?:png|jpeg|jpg|gif|webp|avif|x-icon|vnd\.microsoft\.icon)(?:;[a-z0-9-]+(?:=[^,;]*)?)*,/i;
1871
+ var SAFE_EMBED_PROTOCOLS = /^(https?)$/i;
1871
1872
  function getScheme(value) {
1872
1873
  const trimmed = value.replace(/^\s+/, "");
1873
1874
  const colon = trimmed.indexOf(":");
@@ -1923,16 +1924,54 @@ function isSafeImageSrc(value) {
1923
1924
  }
1924
1925
  return SAFE_IMAGE_PROTOCOLS.test(scheme);
1925
1926
  }
1927
+ function isSafeEmbedSrc(value) {
1928
+ if (typeof value !== "string") {
1929
+ return false;
1930
+ }
1931
+ if (value.length === 0) {
1932
+ return true;
1933
+ }
1934
+ const scheme = getScheme(value);
1935
+ if (scheme === null) {
1936
+ return false;
1937
+ }
1938
+ return SAFE_EMBED_PROTOCOLS.test(scheme);
1939
+ }
1926
1940
  function sanitizeHref(value) {
1927
1941
  return isSafeHref(value) ? value ?? "" : "";
1928
1942
  }
1929
1943
  function sanitizeImageSrc(value) {
1930
1944
  return isSafeImageSrc(value) ? value ?? "" : "";
1931
1945
  }
1946
+ function sanitizeEmbedSrc(value) {
1947
+ return isSafeEmbedSrc(value) ? value ?? "" : "";
1948
+ }
1932
1949
 
1933
1950
  // src/lib/sanitize-blocks.ts
1934
- var HREF_KEYS = /* @__PURE__ */ new Set(["url"]);
1935
- var IMAGE_KEYS = /* @__PURE__ */ new Set(["image_url"]);
1951
+ var EMBED_KEYS = /* @__PURE__ */ new Set(["video_url"]);
1952
+ var URL_KEY = /(^|_)(url|uri|link|href|src)$/;
1953
+ var MAYBE_URL_KEY = /url|uri|link|href|src/i;
1954
+ var IMAGE_KEY_HINT = /image|thumb|icon|avatar|logo|photo|picture/;
1955
+ var PROP_BAG_KEY = /(^|_)props$/;
1956
+ function normalizeKey(key) {
1957
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
1958
+ }
1959
+ function isRendererPropBag(key) {
1960
+ return PROP_BAG_KEY.test(normalizeKey(key));
1961
+ }
1962
+ function classifyUrlKey(key) {
1963
+ if (!MAYBE_URL_KEY.test(key)) {
1964
+ return null;
1965
+ }
1966
+ const normalized = normalizeKey(key);
1967
+ if (EMBED_KEYS.has(normalized)) {
1968
+ return "embed";
1969
+ }
1970
+ if (!URL_KEY.test(normalized)) {
1971
+ return null;
1972
+ }
1973
+ return IMAGE_KEY_HINT.test(normalized) ? "image" : "href";
1974
+ }
1936
1975
  var RETRIEVAL_ONLY_KEYS = /* @__PURE__ */ new Map([
1937
1976
  // Covers both the image block and the image element — both `type: 'image'`.
1938
1977
  ["image", /* @__PURE__ */ new Set(["image_width", "image_height", "image_bytes", "fallback", "is_animated"])],
@@ -1960,7 +1999,7 @@ function sanitizeValue(value) {
1960
1999
  const dropKeys = typeof src.type === "string" ? RETRIEVAL_ONLY_KEYS.get(src.type) : void 0;
1961
2000
  let copy = null;
1962
2001
  for (const key of Object.keys(src)) {
1963
- if (dropKeys?.has(key)) {
2002
+ if (dropKeys?.has(key) || isRendererPropBag(key)) {
1964
2003
  copy ??= { ...src };
1965
2004
  delete copy[key];
1966
2005
  continue;
@@ -1968,10 +2007,13 @@ function sanitizeValue(value) {
1968
2007
  const original = src[key];
1969
2008
  let next = original;
1970
2009
  if (typeof original === "string") {
1971
- if (HREF_KEYS.has(key)) {
2010
+ const kind = classifyUrlKey(key);
2011
+ if (kind === "href") {
1972
2012
  next = sanitizeHref(original);
1973
- } else if (IMAGE_KEYS.has(key)) {
2013
+ } else if (kind === "image") {
1974
2014
  next = sanitizeImageSrc(original);
2015
+ } else if (kind === "embed") {
2016
+ next = sanitizeEmbedSrc(original);
1975
2017
  }
1976
2018
  } else if (typeof original === "object" && original !== null) {
1977
2019
  next = sanitizeValue(original);
@@ -3284,6 +3326,7 @@ function ChannelCombobox({
3284
3326
  )
3285
3327
  ] });
3286
3328
  }
3329
+ var EMBED_SELECTOR = "iframe[src], embed[src], object[data], source[src], video[src], audio[src], track[src]";
3287
3330
  function SlackBlockPreview({
3288
3331
  block,
3289
3332
  hooks,
@@ -3316,6 +3359,17 @@ function SlackBlockPreview({
3316
3359
  img.setAttribute("data-bk-blocked-src", "1");
3317
3360
  }
3318
3361
  }
3362
+ for (const el of root.querySelectorAll(EMBED_SELECTOR)) {
3363
+ const attr = el.tagName === "OBJECT" ? "data" : "src";
3364
+ if (!isSafeEmbedSrc(el.getAttribute(attr))) {
3365
+ el.removeAttribute(attr);
3366
+ el.setAttribute("data-bk-blocked-src", "1");
3367
+ }
3368
+ }
3369
+ for (const frame of root.querySelectorAll("iframe[srcdoc]")) {
3370
+ frame.removeAttribute("srcdoc");
3371
+ frame.setAttribute("data-bk-blocked-srcdoc", "1");
3372
+ }
3319
3373
  });
3320
3374
  return (
3321
3375
  // The library scopes all of its CSS under `#slack_blocks_to_jsx` and
@@ -9168,7 +9222,14 @@ function SourcesField({
9168
9222
  ] })
9169
9223
  ] });
9170
9224
  }
9225
+ function UnsafeUrlNote({ children }) {
9226
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[11px] text-destructive", children });
9227
+ }
9171
9228
  function VideoEditor({ block, onChange }) {
9229
+ const videoUrlIsUnsafe = (block.video_url ?? "").length > 0 && !isSafeEmbedSrc(block.video_url);
9230
+ const thumbnailIsUnsafe = (block.thumbnail_url ?? "").length > 0 && !isSafeImageSrc(block.thumbnail_url);
9231
+ const titleUrlIsUnsafe = (block.title_url ?? "").length > 0 && !isSafeHref(block.title_url);
9232
+ const providerIconIsUnsafe = (block.provider_icon_url ?? "").length > 0 && !isSafeImageSrc(block.provider_icon_url);
9172
9233
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
9173
9234
  /* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Title", help: "Up to 199 characters. Shown above the embedded player.", htmlFor: "video-title", children: /* @__PURE__ */ jsxRuntime.jsx(
9174
9235
  Input,
@@ -9192,50 +9253,62 @@ function VideoEditor({ block, onChange }) {
9192
9253
  onChange: (e) => onChange({ ...block, alt_text: e.target.value })
9193
9254
  }
9194
9255
  ) }),
9195
- /* @__PURE__ */ jsxRuntime.jsx(
9256
+ /* @__PURE__ */ jsxRuntime.jsxs(
9196
9257
  EditorField,
9197
9258
  {
9198
9259
  label: "Video URL",
9199
9260
  help: "HTTPS embed URL. Must match an unfurl domain on your Slack app.",
9200
9261
  htmlFor: "video-url",
9201
- children: /* @__PURE__ */ jsxRuntime.jsx(
9202
- Input,
9203
- {
9204
- id: "video-url",
9205
- type: "url",
9206
- value: block.video_url ?? "",
9207
- placeholder: "e.g. https://www.youtube.com/embed/dQw4w9WgXcQ",
9208
- onChange: (e) => onChange({ ...block, video_url: e.target.value })
9209
- }
9210
- )
9262
+ children: [
9263
+ /* @__PURE__ */ jsxRuntime.jsx(
9264
+ Input,
9265
+ {
9266
+ id: "video-url",
9267
+ type: "url",
9268
+ value: block.video_url ?? "",
9269
+ placeholder: "e.g. https://www.youtube.com/embed/dQw4w9WgXcQ",
9270
+ onChange: (e) => onChange({ ...block, video_url: e.target.value }),
9271
+ "aria-invalid": videoUrlIsUnsafe || void 0
9272
+ }
9273
+ ),
9274
+ videoUrlIsUnsafe && /* @__PURE__ */ jsxRuntime.jsx(UnsafeUrlNote, { children: "The embedded player only accepts a full http(s) URL. This URL will be stripped before send and preview." })
9275
+ ]
9211
9276
  }
9212
9277
  ),
9213
- /* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Thumbnail URL", help: "Preview image shown before the video loads.", htmlFor: "video-thumbnail", children: /* @__PURE__ */ jsxRuntime.jsx(
9214
- Input,
9215
- {
9216
- id: "video-thumbnail",
9217
- type: "url",
9218
- value: block.thumbnail_url ?? "",
9219
- placeholder: "e.g. https://example.com/thumb.png",
9220
- onChange: (e) => onChange({ ...block, thumbnail_url: e.target.value })
9221
- }
9222
- ) }),
9223
- /* @__PURE__ */ jsxRuntime.jsx(
9278
+ /* @__PURE__ */ jsxRuntime.jsxs(EditorField, { label: "Thumbnail URL", help: "Preview image shown before the video loads.", htmlFor: "video-thumbnail", children: [
9279
+ /* @__PURE__ */ jsxRuntime.jsx(
9280
+ Input,
9281
+ {
9282
+ id: "video-thumbnail",
9283
+ type: "url",
9284
+ value: block.thumbnail_url ?? "",
9285
+ placeholder: "e.g. https://example.com/thumb.png",
9286
+ onChange: (e) => onChange({ ...block, thumbnail_url: e.target.value }),
9287
+ "aria-invalid": thumbnailIsUnsafe || void 0
9288
+ }
9289
+ ),
9290
+ thumbnailIsUnsafe && /* @__PURE__ */ jsxRuntime.jsx(UnsafeUrlNote, { children: "Only http(s) image URLs are allowed. This URL will be stripped before send and preview." })
9291
+ ] }),
9292
+ /* @__PURE__ */ jsxRuntime.jsxs(
9224
9293
  EditorField,
9225
9294
  {
9226
9295
  label: "Title link URL",
9227
9296
  help: "Optional. HTTPS link the title text opens (the non-embed page).",
9228
9297
  htmlFor: "video-title-url",
9229
- children: /* @__PURE__ */ jsxRuntime.jsx(
9230
- Input,
9231
- {
9232
- id: "video-title-url",
9233
- type: "url",
9234
- value: block.title_url ?? "",
9235
- placeholder: "e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ",
9236
- onChange: (e) => onChange({ ...block, title_url: e.target.value || void 0 })
9237
- }
9238
- )
9298
+ children: [
9299
+ /* @__PURE__ */ jsxRuntime.jsx(
9300
+ Input,
9301
+ {
9302
+ id: "video-title-url",
9303
+ type: "url",
9304
+ value: block.title_url ?? "",
9305
+ placeholder: "e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ",
9306
+ onChange: (e) => onChange({ ...block, title_url: e.target.value || void 0 }),
9307
+ "aria-invalid": titleUrlIsUnsafe || void 0
9308
+ }
9309
+ ),
9310
+ titleUrlIsUnsafe && /* @__PURE__ */ jsxRuntime.jsx(UnsafeUrlNote, { children: "Only http(s), mailto, tel, sms, and xmpp links are allowed. This URL will be stripped before send and preview." })
9311
+ ]
9239
9312
  }
9240
9313
  ),
9241
9314
  /* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Description", help: "Optional. Up to 199 characters.", htmlFor: "video-description", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -9279,22 +9352,26 @@ function VideoEditor({ block, onChange }) {
9279
9352
  )
9280
9353
  }
9281
9354
  ),
9282
- /* @__PURE__ */ jsxRuntime.jsx(
9355
+ /* @__PURE__ */ jsxRuntime.jsxs(
9283
9356
  EditorField,
9284
9357
  {
9285
9358
  label: "Provider icon URL",
9286
9359
  help: "Optional. Small icon shown next to the provider name.",
9287
9360
  htmlFor: "video-provider-icon",
9288
- children: /* @__PURE__ */ jsxRuntime.jsx(
9289
- Input,
9290
- {
9291
- id: "video-provider-icon",
9292
- type: "url",
9293
- value: block.provider_icon_url ?? "",
9294
- placeholder: "e.g. https://example.com/youtube-favicon.png",
9295
- onChange: (e) => onChange({ ...block, provider_icon_url: e.target.value || void 0 })
9296
- }
9297
- )
9361
+ children: [
9362
+ /* @__PURE__ */ jsxRuntime.jsx(
9363
+ Input,
9364
+ {
9365
+ id: "video-provider-icon",
9366
+ type: "url",
9367
+ value: block.provider_icon_url ?? "",
9368
+ placeholder: "e.g. https://example.com/youtube-favicon.png",
9369
+ onChange: (e) => onChange({ ...block, provider_icon_url: e.target.value || void 0 }),
9370
+ "aria-invalid": providerIconIsUnsafe || void 0
9371
+ }
9372
+ ),
9373
+ providerIconIsUnsafe && /* @__PURE__ */ jsxRuntime.jsx(UnsafeUrlNote, { children: "Only http(s) image URLs are allowed. This URL will be stripped before send and preview." })
9374
+ ]
9298
9375
  }
9299
9376
  )
9300
9377
  ] });