Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

사용자 위젯의 iframe 기능 권한

사용자 위젯의 iframe은 기본적으로 카메라와 팝업 같은 브라우저 기능을 제한합니다. 필요한 기능은 개발자가 구성에 요청하고 앱 제작자가 호스트에서 허용해야 합니다. 대부분의 위젯에는 추가 권한이 필요하지 않으므로 필요한 항목만 요청합니다.

카메라와 마이크 등은 최종 사용자도 브라우저 권한 요청을 명시적으로 허용해야 합니다. iframe 속성 허용은 접근을 요청할 수 있게 할 뿐 사용자 허용을 대신하지 않습니다.

요청할 수 있는 값

iframe 속성허용 요청값
allowcamera, microphone, autoplay
sandboxallow-downloads, allow-forms, allow-popups

위젯 구성의 permissions에 요청합니다.

import { defineConfig } from "@osdk/widget.client";

export default defineConfig({
  id: "widgetId",
  name: "A custom widget",
  description: "A widget description",
  type: "workshop",
  parameters: {},
  events: {},
  permissions: ["camera", "allow-popups"],
});

Workshop에서 허용

Widget setup의 Parameters와 Events 아래 Permissions에서 앱 제작자가 요청값을 허용합니다. 일부 항목은 사유 확인을 요구할 수 있습니다.

개발 중에는 playground나 VS Code 작업 공간에서 임시로 허용하여 확인할 수 있습니다. 이 개발 경로도 브라우저의 카메라/마이크 사용자 허용을 대신하지 않습니다.

카메라와 마이크

camera와 microphone을 요청하고 허용한 구성에서 사용자 동작에 연결하여 호출합니다.

const mediaStream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true,
});

성공하면 MediaStream을 받습니다. 거부나 장치 사용 불가 등 실패는 앱에서 안내하고 처리해야 합니다. 사용이 끝난 입력은 track을 중지하여 해제합니다.

mediaStream.getTracks().forEach(track => track.stop());

지원되는 브라우저에서는 Permissions API로 granted, prompt 또는 denied 상태를 확인할 수 있습니다.

navigator.permissions.query({ name: "camera" });
navigator.permissions.query({ name: "microphone" });

iframe 허용 상태, 브라우저 기능 지원과 사용자 권한을 각각 구분합니다.

자동 재생

autoplay 속성을 허용한 구성에서는 audio나 video에 자동 재생을 요청할 수 있습니다. 다음은 실제 미디어 URL을 myVideoSrc로 받는 React 예입니다.

export const VideoPreview = ({ myVideoSrc }: { myVideoSrc: string }) => (
  <video src={myVideoSrc} autoPlay controls />
);

자동 재생은 브라우저의 미디어 정책도 따릅니다. controls로 사용자 재생 동작을 제공할 수 있습니다.

다운로드

아래 다운로드에는 allow-downloads가 필요합니다. 다음 helper는 임시 링크로 요청을 시작합니다.

function triggerDownload(url: string, filename: string) {
  const anchor = document.createElement("a");
  anchor.download = filename;
  anchor.href = url;
  document.body.appendChild(anchor);
  anchor.click();
  anchor.remove();
}

CSV Blob

export const CsvDownload = () => (
  <button onClick={() => {
    const csv = "a,b,c\n1,2,3";
    const blob = new Blob([csv], { type: "text/csv" });
    const url = URL.createObjectURL(blob);
    triggerDownload(url, "data.csv");
    URL.revokeObjectURL(url);
  }}>
    Download CSV
  </button>
);

포함된 정적 자산의 직접 링크

예제의 img.png는 실제 위젯 프로젝트에 준비한 파일입니다. Vite로 URL을 import하여 download 속성에 연결할 수 있습니다.

import imgUrl from "./img.png";

export const ImageDownload = () => (
  <a href={imgUrl} download="img.png" target="_blank" rel="noreferrer">Download</a>
);

public/img.png는 루트 절대경로 /img.png로 참조합니다.

export const PublicImageDownload = () => (
  <a href="/img.png" download="img.png" target="_blank" rel="noreferrer">Download</a>
);

개발 모드에서는 자산이 localhost 같은 다른 origin에서 제공되어 다운로드 대신 새 탭으로 열릴 수 있습니다. 프로덕션 위젯의 포함 자산에서는 다운로드 동작을 사용합니다.

정적 자산을 Blob으로 받기

포함된 자산을 fetch하여 필요하면 가공한 뒤 Blob으로 다운로드할 수 있습니다. 이는 위젯에 포함한 정적 자산 경로이며 임의 외부 API 호출을 허용하는 것은 아닙니다.

Vite import에는 ?no-inline을 사용하여 data: URI로 바뀌면서 CSP에 막히는 것을 피합니다. 다음은 같은 다운로드 처리를 import URL과 public 경로에 사용하는 예입니다. 앞의 triggerDownload helper를 같은 모듈에서 사용합니다.

import imgUrl from "./img.png?no-inline";
import { useState } from "react";

async function downloadAsset(url: string) {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`Asset request failed: ${response.status}`);
  const blob = await response.blob();
  const objectUrl = URL.createObjectURL(blob);
  try {
    triggerDownload(objectUrl, "img.png");
  } finally {
    URL.revokeObjectURL(objectUrl);
  }
}

export const AssetDownloads = () => {
  const [error, setError] = useState<string>();
  const download = async (url: string) => {
    setError(undefined);
    try {
      await downloadAsset(url);
    } catch (cause) {
      setError(cause instanceof Error ? cause.message : String(cause));
    }
  };
  return (
    <div>
      <button onClick={() => void download(imgUrl)}>Download imported asset</button>
      <button onClick={() => void download("/img.png")}>Download public asset</button>
      {error && <p role="alert">{error}</p>}
    </div>
  );
};

새 탭과 창

Workshop에서는 호스트 Open URL 이벤트를 권장합니다. 추가 iframe 속성을 요청하지 않아도 이 경로로 이동할 수 있습니다.

위젯 안의 링크로 새 탭을 열려면 allow-popups를 허용해야 합니다.

<a href="https://palantir.com/docs" target="_blank" rel="noreferrer">Link</a>

이 방식으로 연 창은 위젯의 sandbox 제한을 상속합니다. 그 창에서 다운로드도 해야 한다면 위젯에 allow-downloads도 필요합니다.

배포 지원 범위는 Marketplace, 개발 버전 환경은 Node 관리, 응답성 문제는 문제 해결을 확인합니다.