@tightknitai/block-kitchen 0.10.11 → 0.10.13

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
@@ -1838,6 +1838,7 @@ function groupValidatorErrors(errors, blocks) {
1838
1838
  var SAFE_LINK_PROTOCOLS = /^(https?|ircs?|mailto|tel|sms|xmpp)$/i;
1839
1839
  var SAFE_IMAGE_PROTOCOLS = /^(https?)$/i;
1840
1840
  var SAFE_IMAGE_DATA_PREFIX = /^data:image\/(?:png|jpeg|jpg|gif|webp|avif|x-icon|vnd\.microsoft\.icon)(?:;[a-z0-9-]+(?:=[^,;]*)?)*,/i;
1841
+ var SAFE_EMBED_PROTOCOLS = /^(https?)$/i;
1841
1842
  function getScheme(value) {
1842
1843
  const trimmed = value.replace(/^\s+/, "");
1843
1844
  const colon = trimmed.indexOf(":");
@@ -1893,16 +1894,47 @@ function isSafeImageSrc(value) {
1893
1894
  }
1894
1895
  return SAFE_IMAGE_PROTOCOLS.test(scheme);
1895
1896
  }
1897
+ function isSafeEmbedSrc(value) {
1898
+ if (typeof value !== "string") {
1899
+ return false;
1900
+ }
1901
+ if (value.length === 0) {
1902
+ return true;
1903
+ }
1904
+ const scheme = getScheme(value);
1905
+ if (scheme === null) {
1906
+ return false;
1907
+ }
1908
+ return SAFE_EMBED_PROTOCOLS.test(scheme);
1909
+ }
1896
1910
  function sanitizeHref(value) {
1897
1911
  return isSafeHref(value) ? value ?? "" : "";
1898
1912
  }
1899
1913
  function sanitizeImageSrc(value) {
1900
1914
  return isSafeImageSrc(value) ? value ?? "" : "";
1901
1915
  }
1916
+ function sanitizeEmbedSrc(value) {
1917
+ return isSafeEmbedSrc(value) ? value ?? "" : "";
1918
+ }
1902
1919
 
1903
1920
  // src/lib/sanitize-blocks.ts
1904
- var HREF_KEYS = /* @__PURE__ */ new Set(["url"]);
1905
- var IMAGE_KEYS = /* @__PURE__ */ new Set(["image_url"]);
1921
+ var EMBED_KEYS = /* @__PURE__ */ new Set(["video_url"]);
1922
+ var URL_KEY = /(^|_)(url|uri|link|href|src)$/;
1923
+ var MAYBE_URL_KEY = /url|uri|link|href|src/i;
1924
+ var IMAGE_KEY_HINT = /image|thumb|icon|avatar|logo|photo|picture/;
1925
+ function classifyUrlKey(key) {
1926
+ if (!MAYBE_URL_KEY.test(key)) {
1927
+ return null;
1928
+ }
1929
+ const normalized = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
1930
+ if (EMBED_KEYS.has(normalized)) {
1931
+ return "embed";
1932
+ }
1933
+ if (!URL_KEY.test(normalized)) {
1934
+ return null;
1935
+ }
1936
+ return IMAGE_KEY_HINT.test(normalized) ? "image" : "href";
1937
+ }
1906
1938
  var RETRIEVAL_ONLY_KEYS = /* @__PURE__ */ new Map([
1907
1939
  // Covers both the image block and the image element — both `type: 'image'`.
1908
1940
  ["image", /* @__PURE__ */ new Set(["image_width", "image_height", "image_bytes", "fallback", "is_animated"])],
@@ -1938,10 +1970,13 @@ function sanitizeValue(value) {
1938
1970
  const original = src[key];
1939
1971
  let next = original;
1940
1972
  if (typeof original === "string") {
1941
- if (HREF_KEYS.has(key)) {
1973
+ const kind = classifyUrlKey(key);
1974
+ if (kind === "href") {
1942
1975
  next = sanitizeHref(original);
1943
- } else if (IMAGE_KEYS.has(key)) {
1976
+ } else if (kind === "image") {
1944
1977
  next = sanitizeImageSrc(original);
1978
+ } else if (kind === "embed") {
1979
+ next = sanitizeEmbedSrc(original);
1945
1980
  }
1946
1981
  } else if (typeof original === "object" && original !== null) {
1947
1982
  next = sanitizeValue(original);
@@ -3254,6 +3289,7 @@ function ChannelCombobox({
3254
3289
  )
3255
3290
  ] });
3256
3291
  }
3292
+ var EMBED_SELECTOR = "iframe[src], embed[src], object[data], source[src], video[src], audio[src], track[src]";
3257
3293
  function SlackBlockPreview({
3258
3294
  block,
3259
3295
  hooks,
@@ -3286,6 +3322,13 @@ function SlackBlockPreview({
3286
3322
  img.setAttribute("data-bk-blocked-src", "1");
3287
3323
  }
3288
3324
  }
3325
+ for (const el of root.querySelectorAll(EMBED_SELECTOR)) {
3326
+ const attr = el.tagName === "OBJECT" ? "data" : "src";
3327
+ if (!isSafeEmbedSrc(el.getAttribute(attr))) {
3328
+ el.removeAttribute(attr);
3329
+ el.setAttribute("data-bk-blocked-src", "1");
3330
+ }
3331
+ }
3289
3332
  });
3290
3333
  return (
3291
3334
  // The library scopes all of its CSS under `#slack_blocks_to_jsx` and
@@ -9138,7 +9181,14 @@ function SourcesField({
9138
9181
  ] })
9139
9182
  ] });
9140
9183
  }
9184
+ function UnsafeUrlNote({ children }) {
9185
+ return /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-destructive", children });
9186
+ }
9141
9187
  function VideoEditor({ block, onChange }) {
9188
+ const videoUrlIsUnsafe = (block.video_url ?? "").length > 0 && !isSafeEmbedSrc(block.video_url);
9189
+ const thumbnailIsUnsafe = (block.thumbnail_url ?? "").length > 0 && !isSafeImageSrc(block.thumbnail_url);
9190
+ const titleUrlIsUnsafe = (block.title_url ?? "").length > 0 && !isSafeHref(block.title_url);
9191
+ const providerIconIsUnsafe = (block.provider_icon_url ?? "").length > 0 && !isSafeImageSrc(block.provider_icon_url);
9142
9192
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
9143
9193
  /* @__PURE__ */ jsx(EditorField, { label: "Title", help: "Up to 199 characters. Shown above the embedded player.", htmlFor: "video-title", children: /* @__PURE__ */ jsx(
9144
9194
  Input,
@@ -9162,50 +9212,62 @@ function VideoEditor({ block, onChange }) {
9162
9212
  onChange: (e) => onChange({ ...block, alt_text: e.target.value })
9163
9213
  }
9164
9214
  ) }),
9165
- /* @__PURE__ */ jsx(
9215
+ /* @__PURE__ */ jsxs(
9166
9216
  EditorField,
9167
9217
  {
9168
9218
  label: "Video URL",
9169
9219
  help: "HTTPS embed URL. Must match an unfurl domain on your Slack app.",
9170
9220
  htmlFor: "video-url",
9171
- children: /* @__PURE__ */ jsx(
9172
- Input,
9173
- {
9174
- id: "video-url",
9175
- type: "url",
9176
- value: block.video_url ?? "",
9177
- placeholder: "e.g. https://www.youtube.com/embed/dQw4w9WgXcQ",
9178
- onChange: (e) => onChange({ ...block, video_url: e.target.value })
9179
- }
9180
- )
9221
+ children: [
9222
+ /* @__PURE__ */ jsx(
9223
+ Input,
9224
+ {
9225
+ id: "video-url",
9226
+ type: "url",
9227
+ value: block.video_url ?? "",
9228
+ placeholder: "e.g. https://www.youtube.com/embed/dQw4w9WgXcQ",
9229
+ onChange: (e) => onChange({ ...block, video_url: e.target.value }),
9230
+ "aria-invalid": videoUrlIsUnsafe || void 0
9231
+ }
9232
+ ),
9233
+ videoUrlIsUnsafe && /* @__PURE__ */ jsx(UnsafeUrlNote, { children: "The embedded player only accepts a full http(s) URL. This URL will be stripped before send and preview." })
9234
+ ]
9181
9235
  }
9182
9236
  ),
9183
- /* @__PURE__ */ jsx(EditorField, { label: "Thumbnail URL", help: "Preview image shown before the video loads.", htmlFor: "video-thumbnail", children: /* @__PURE__ */ jsx(
9184
- Input,
9185
- {
9186
- id: "video-thumbnail",
9187
- type: "url",
9188
- value: block.thumbnail_url ?? "",
9189
- placeholder: "e.g. https://example.com/thumb.png",
9190
- onChange: (e) => onChange({ ...block, thumbnail_url: e.target.value })
9191
- }
9192
- ) }),
9193
- /* @__PURE__ */ jsx(
9237
+ /* @__PURE__ */ jsxs(EditorField, { label: "Thumbnail URL", help: "Preview image shown before the video loads.", htmlFor: "video-thumbnail", children: [
9238
+ /* @__PURE__ */ jsx(
9239
+ Input,
9240
+ {
9241
+ id: "video-thumbnail",
9242
+ type: "url",
9243
+ value: block.thumbnail_url ?? "",
9244
+ placeholder: "e.g. https://example.com/thumb.png",
9245
+ onChange: (e) => onChange({ ...block, thumbnail_url: e.target.value }),
9246
+ "aria-invalid": thumbnailIsUnsafe || void 0
9247
+ }
9248
+ ),
9249
+ thumbnailIsUnsafe && /* @__PURE__ */ jsx(UnsafeUrlNote, { children: "Only http(s) image URLs are allowed. This URL will be stripped before send and preview." })
9250
+ ] }),
9251
+ /* @__PURE__ */ jsxs(
9194
9252
  EditorField,
9195
9253
  {
9196
9254
  label: "Title link URL",
9197
9255
  help: "Optional. HTTPS link the title text opens (the non-embed page).",
9198
9256
  htmlFor: "video-title-url",
9199
- children: /* @__PURE__ */ jsx(
9200
- Input,
9201
- {
9202
- id: "video-title-url",
9203
- type: "url",
9204
- value: block.title_url ?? "",
9205
- placeholder: "e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ",
9206
- onChange: (e) => onChange({ ...block, title_url: e.target.value || void 0 })
9207
- }
9208
- )
9257
+ children: [
9258
+ /* @__PURE__ */ jsx(
9259
+ Input,
9260
+ {
9261
+ id: "video-title-url",
9262
+ type: "url",
9263
+ value: block.title_url ?? "",
9264
+ placeholder: "e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ",
9265
+ onChange: (e) => onChange({ ...block, title_url: e.target.value || void 0 }),
9266
+ "aria-invalid": titleUrlIsUnsafe || void 0
9267
+ }
9268
+ ),
9269
+ titleUrlIsUnsafe && /* @__PURE__ */ jsx(UnsafeUrlNote, { children: "Only http(s), mailto, tel, sms, and xmpp links are allowed. This URL will be stripped before send and preview." })
9270
+ ]
9209
9271
  }
9210
9272
  ),
9211
9273
  /* @__PURE__ */ jsx(EditorField, { label: "Description", help: "Optional. Up to 199 characters.", htmlFor: "video-description", children: /* @__PURE__ */ jsx(
@@ -9249,22 +9311,26 @@ function VideoEditor({ block, onChange }) {
9249
9311
  )
9250
9312
  }
9251
9313
  ),
9252
- /* @__PURE__ */ jsx(
9314
+ /* @__PURE__ */ jsxs(
9253
9315
  EditorField,
9254
9316
  {
9255
9317
  label: "Provider icon URL",
9256
9318
  help: "Optional. Small icon shown next to the provider name.",
9257
9319
  htmlFor: "video-provider-icon",
9258
- children: /* @__PURE__ */ jsx(
9259
- Input,
9260
- {
9261
- id: "video-provider-icon",
9262
- type: "url",
9263
- value: block.provider_icon_url ?? "",
9264
- placeholder: "e.g. https://example.com/youtube-favicon.png",
9265
- onChange: (e) => onChange({ ...block, provider_icon_url: e.target.value || void 0 })
9266
- }
9267
- )
9320
+ children: [
9321
+ /* @__PURE__ */ jsx(
9322
+ Input,
9323
+ {
9324
+ id: "video-provider-icon",
9325
+ type: "url",
9326
+ value: block.provider_icon_url ?? "",
9327
+ placeholder: "e.g. https://example.com/youtube-favicon.png",
9328
+ onChange: (e) => onChange({ ...block, provider_icon_url: e.target.value || void 0 }),
9329
+ "aria-invalid": providerIconIsUnsafe || void 0
9330
+ }
9331
+ ),
9332
+ providerIconIsUnsafe && /* @__PURE__ */ jsx(UnsafeUrlNote, { children: "Only http(s) image URLs are allowed. This URL will be stripped before send and preview." })
9333
+ ]
9268
9334
  }
9269
9335
  )
9270
9336
  ] });